LabHub
学习 学习路径 课程

Apache Flink — 스트림을 엔진으로 돌린다 · 클러스터 한 벌 · 讲解

클러스터 한 벌 — JobManager 가 정하고 TaskManager 가 돈다

在 LabHub 中继续学习

한 줄 요약

Flink 클러스터는 정하는 프로세스(JobManager) 하나와 도는 프로세스(TaskManager) 여럿이다. SQL 한 줄은 잡 그래프가 되고, JobManager 가 그것을 TaskManager 의 슬롯에 나눠 싣는다. 무엇이 어디서 돌았는지, 왜 죽었는지는 전부 REST API 에 남는다.

分层图: 슬롯 · 엔진의 구조 · JobManager · 있는 TaskManager 의 슬롯을 나눌 뿐

왜 이게 필요했나

스트림 처리를 처음 맡으면 대개 "SQL 을 쓰면 결과가 나온다" 에서 출발한다. 그런데 운영에서 받는 질문은 SQL 이 아니다. "잡이 왜 멈췄나", "병렬도를 올렸는데 왜 그대로인가", "TaskManager 메모리를 4GB 로 줬는데 힙은 왜 1GB 뿐인가", "재시작이 왜 안 됐나". 이 질문들은 모두 엔진의 구조에 대한 것이다.

배치 스크립트는 죽으면 다시 돌리면 된다. 스트리밍 잡은 몇 주씩 떠 있고, 그 사이 상태를 쥐고 있다. 그래서 "어느 프로세스가 무엇을 책임지는가" 를 알아야 장애 때 어디를 볼지 정할 수 있다. 이 모듈은 그 지도를 먼저 깐다.

어떻게 동작하나

JobManager 안에 Dispatcher·ResourceManager·JobMaster 가 있고, TaskManager 하나에 슬롯 두 개가 있다. SQL 클라이언트가 Dispatcher 에 잡을 제출하면 JobMaster 가 생기고, ResourceManager 가 슬롯을 내주어 원천에서 집계를 거쳐 싱크까지 이어진 태스크가 슬롯에 실린다. REST 8081 은 Dispatcher 가 연다

JobManager 는 세 부품으로 이루어진다(공식 문서의 Flink Architecture).

| 부품 | 하는 일 |
| --- | --- |
| Dispatcher | REST 인터페이스(기본 8081)와 웹 UI 를 열고, 잡이 들어오면 JobMaster 를 하나 만든다 |
| ResourceManager | 슬롯을 관리한다. 독립(standalone) 배포에서는 있는 TaskManager 의 슬롯을 나눌 뿐 새 TaskManager 를 띄우지 못한다 |
| JobMaster | 잡 하나의 실행을 맡는다. 잡마다 하나씩 생긴다 |

TaskManager 는 실제로 연산을 돌리고 데이터를 주고받는다. 자원 배정의 최소 단위가 태스크 슬롯이고, 슬롯 수가 곧 동시에 돌 수 있는 태스크 수다. 주의할 점이 둘 있다. 첫째, 슬롯은 CPU 를 나누지 않는다 — 문서의 표현대로 지금의 슬롯은 관리 메모리(managed memory)만 가른다. 둘째, 연산자 여러 개가 체인으로 묶여 한 스레드에서 돌 수 있다. 원천 → 필터 → 변환이 한 태스크가 되는 이유다. 스레드 사이 넘김과 버퍼링이 사라져 처리량이 오른다.

병렬도는 연산자 하나를 몇 벌로 복제해 돌릴지다. 병렬도 2 인 집계는 서브태스크 둘로 나뉘고, 각각 슬롯 하나를 차지한다. 여기서 배치와 스트리밍이 갈린다. 배치 잡은 기본 스케줄러가 적응형 배치 스케줄러라서, 따로 지정하지 않은 연산자의 병렬도를 들어오는 자료 크기로 다시 정한다. 기준은 태스크 하나당 평균 16MB(execution.batch.adaptive.auto-parallelism.avg-data-volume-per-task) 다. 작은 파일에 parallelism.default = 2 를 줘도 병렬도가 1 로 나오는 것은 버그가 아니라 이 설계다. 자동 결정을 끄는 스위치는 execution.batch.adaptive.auto-parallelism.enabled 이다.

메모리는 이 모듈에서 가장 자주 오해되는 부분이다. taskmanager.memory.process.size 는 JVM 프로세스 전체의 크기다. 그 안에서 JVM 메타스페이스와 JVM 오버헤드를 먼저 떼고, 남은 Flink 메모리를 다시 프레임워크 힙·태스크 힙·관리 메모리·네트워크 메모리로 나눈다.

프로세스 전체 = Flink 메모리 + JVM 메타스페이스 + JVM 오버헤드Flink 메모리 = 프레임워크 힙 + 태스크 힙 + 프레임워크 오프힙 + 태스크 오프힙 + 관리 메모리 + 네트워크 메모리

이 파드는 한도가 2Gi 라 TaskManager 를 1024MiB 로 잡았다. 그러면 태스크 힙은 겨우 200MiB 남짓이다. "메모리를 줬는데 OOM 이 난다" 는 신고는 대개 이 표를 거꾸로 읽은 데서 나온다. 실제 값은 /taskmanagers 응답의 memoryConfiguration 에 바이트 단위로 나온다.

실패와 재시작. 잡이 죽으면 JobMaster 가 재시작 전략을 본다. 문서(Task Failure Recovery)에 따르면 체크포인트를 켜지 않으면 기본은 재시작하지 않음이고, 켜면 기본이 exponential-delay 다. 그래서 체크포인트 없는 잡의 예외 기록 맨 위에는 원인이 아니라 Recovery is suppressed by NoRestartBackoffTimeStrategy 가 찍힌다. 진짜 원인은 그 아래 Caused by: 사슬의 끝에 있다.

curl -s localhost:8081/overview                  # TaskManager 수 · 슬롯 수 · 버전curl -s localhost:8081/jobs/overview             # 잡 목록과 상태(FINISHED · FAILED · RUNNING)curl -s localhost:8081/jobs/<jid>                # 정점(vertex)별 병렬도curl -s localhost:8081/jobs/<jid>/exceptions     # 실패 기록(exceptionHistory)

현장에서 만나는 모습

가장 흔한 장면은 "병렬도를 올렸는데 빨라지지 않는다" 다. 원인은 대개 셋 중 하나다. 슬롯이 모자라 잡이 자원을 기다리고 있거나, 배치 잡이라 적응형 스케줄러가 자료 크기로 병렬도를 깎았거나, 원천의 분할(파일 수·파티션 수)이 병렬도보다 적어 서브태스크 일부가 놀고 있다. 셋 다 /jobs/<jid> 의 정점별 병렬도와 서브태스크 수를 보면 가려진다. 대시보드의 처리량 그래프만 보고 있으면 셋이 똑같아 보인다.

두 번째는 설정을 바꿨는데 반영이 안 되는 경우다. numberOfTaskSlots 나 메모리 크기처럼 프로세스가 뜰 때 읽는 값은 파일만 고쳐서는 아무 일도 일어나지 않는다. TaskManager 를 다시 띄워야 한다. 반대로 SET 'parallelism.default' 처럼 잡 제출 때 읽는 값은 다음 잡부터 바로 먹는다. 어느 쪽인지 모르면 "재시작했더니 됐다" 는 경험담만 쌓인다.

세 번째는 장애 보고서다. 알림은 "잡 FAILED" 한 줄로 온다. 예외 기록의 맨 위 문장을 그대로 붙여 넣으면 모두가 재시작 전략을 원인으로 오해한다. 사슬 끝의 Caused by 를 찾아 적는 습관이 보고서의 질을 가른다.

다음 실습에서 할 것

클러스터를 띄워 REST 로 TaskManager 와 슬롯을 확인하고, 메모리 예산을 숫자로 옮긴다. 주문 파일을 배치로 집계하는 잡을 돌려 잡 목록에서 찾아내고, 같은 잡을 병렬도 2 로 다시 돌려 정점 병렬도를 확인한다. 슬롯을 4개로 바꿔 다시 띄운 뒤, 문자열을 정수로 바꾸다 죽는 잡을 일부러 만들어 진짜 원인과 재시작 전략을 보고서로 정리한다.