← 학습 카테고리

Learn

Kafka

42개 모듈 · 현재 40번째

Kafka 모듈 40/42 kafka-learn-40

클러스터 운영 — 컨트롤러, 랙 어웨어니스, 파티션 설계, 멀티테넌시

50 Kafka Interview Questions for Data Engineers (2026) — (블로그, 발행처 미상 — 데이터 엔지니어링 교육 콘텐츠 사이트) 50 Kafka Interview Questions for Data Engineers (2026), Q5, Q21, Q29, Q33, Q40, Q43, Q45, Q49, FAQ('How does Kafka handle replication?'). Q21의 컨트롤러 설명(ZK 시대의 'LeaderAndIsr 요청 전송' 서술)과 FAQ의 'ZooKeeper or KRaft가 새 리더를 선출' 서술을 현재(4.0+) KRaft 단일 체제 기준으로 정정. Q49의 '2-3배 과잉 파티셔닝' 서술에 과잉 파티셔닝의 실제 비용을 보강.

이 모듈을 다 읽으면

  • 컨트롤러가 파티션 리더 선출·토픽 관리를 처리하는 방식이 ZooKeeper 시대와 KRaft 시대에 어떻게 다른지 설명할 수 있다
  • 랙 어웨어니스, 롤링 리스타트, 파티션 재배치가 각각 어떤 장애/확장 시나리오에 대응하는지 설명할 수 있다
  • 파티션 수를 정하는 T/c 계산식과, 과잉 파티셔닝이 공짜가 아닌 이유를 판단할 수 있다
  • Kafka가 멀티테넌시 환경에서 자원 격리를 보장하는 메커니즘(SASL/ACL, 쿼터)을 설명할 수 있다

클러스터를 실제로 운영할 때 마주치는 컨트롤러의 역할, 무중단 재시작과 파티션 재배치 절차, 랙 단위 장애 대비, 멀티테넌시 자원 격리, 그리고 파티션 수 설계라는 다섯 가지 실전 주제를 정리한다. 특히 컨트롤러 동작 설명 중 ZooKeeper 시대에 머물러 있는 서술을 KRaft 기준으로 바로잡는다.

컨트롤러의 역할 — '정정': LeaderAndIsr 요청에서 메타데이터 로그 전파로

컨트롤러는 파티션 리더 선출, 토픽 생성/삭제, 복제본 관리 같은 클러스터 전체의 관리 작업을 담당하는 핵심 역할이다. 이 부분까지는 ZooKeeper 시대와 KRaft 시대가 같다. 다만 브로커 장애가 감지된 뒤 새 리더 정보가 나머지 브로커들에게 전파되는 구체적인 메커니즘은 두 시대가 다르다 — 여러 자료(이 문서 포함)가 "컨트롤러가 장애를 감지하면 나머지 브로커들에게 LeaderAndIsr 요청을 보내 리더십을 재배정한다"고만 설명하는데, 이는 정확히는 ZooKeeper 시대의 메커니즘이다.

— 정정: KRaft 체제(Kafka 3.3 프로덕션 준비, 4.0부터 유일한 모드)에서는 활성 컨트롤러가 리더/ISR 변경 사항을 브로커들에게 개별 RPC로 능동적으로 "보내는" 것이 아니라, 그 변경 사항을 __cluster_metadata라는 내부 토픽(메타데이터 로그)에 기록하고, 각 브로커가 이 로그를 구독·재생(replay)하며 스스로 최신 상태를 반영한다. 즉 정보 전파의 방향이 "컨트롤러가 브로커에게 밀어넣는(push) 개별 요청"에서 "브로커가 공유 로그를 따라가며 당겨오는(pull에 가까운) 복제" 구조로 바뀐 것이며, 이 차이 덕분에 메타데이터 전파가 훨씬 빠르고 컨트롤러 페일오버도 서브초 단위로 짧아진다.

핵심 포인트

  • 컨트롤러는 파티션 리더 선출·토픽 관리를 담당하는 클러스터 핵심 역할이며 이 책임 자체는 ZK/KRaft 공통이다
  • ZooKeeper 시대는 컨트롤러가 LeaderAndIsr 요청을 브로커에 개별적으로 보내는 방식이었다
  • KRaft 시대는 변경 사항을 __cluster_metadata 로그에 기록하고 브로커가 이를 재생해 스스로 반영하는 방식이다
  • 이 구조 변화로 메타데이터 전파 속도와 컨트롤러 페일오버 속도가 크게 개선됐다

롤링 리스타트와 파티션 재배치 — 계획된 변경을 안전하게 적용하기

롤링 리스타트는 클러스터를 통째로 내리지 않고 브로커를 한 번에 하나씩 재시작하는 절차다. 한 브로커를 내리기 전에 클러스터가 건강한지(모든 파티션에 충분한 ISR이 있는지) 확인하고, 브로커를 내린 뒤 그 브로커가 리더였던 파티션들은 다른 ISR 멤버가 리더를 이어받는다. 재시작한 브로커가 다시 온라인이 되면 담당 파티션들의 ISR에 재합류하고 복제를 따라잡을 때까지 기다린 뒤, 다음 브로커로 넘어간다 — 이 순서를 지키지 않고 여러 브로커를 동시에 내리면 특정 파티션의 ISR이 모두 사라져 가용성이 끊길 위험이 있다.

파티션 재배치는 다른 종류의 변경이다 — 새 브로커를 클러스터에 추가해도 Kafka는 기존 파티션을 자동으로 새 브로커로 옮기지 않는다(신규 토픽부터만 새 브로커를 활용). 기존 데이터를 재분산해 디스크·CPU 부하를 고르게 하려면 kafka-reassign-partitions 도구로 특정 파티션의 복제본을 어느 브로커로 옮길지 명시적으로 지정해야 한다. 이 작업은 대상 파티션의 데이터를 네트워크로 복사하는 무거운 작업이므로, throttle(대역폭 제한) 옵션과 함께 트래픽이 적은 시간대에 점진적으로 수행하는 것이 일반적이다.

핵심 포인트

  • 롤링 리스타트는 브로커를 하나씩 재시작하며 각 단계마다 ISR 상태를 확인해 가용성을 지킨다
  • 새 브로커를 추가해도 기존 파티션은 자동으로 이동하지 않는다 — 재분산은 별도 작업이다
  • kafka-reassign-partitions로 파티션 복제본의 이동을 명시적으로 지정해야 하며, 대역폭 제한과 함께 점진적으로 수행하는 것이 안전하다

랙 어웨어니스와 멀티테넌시 — 물리적 장애와 소음 이웃 문제 다루기

랙 어웨어니스(broker.rack 설정)는 각 브로커가 어느 물리적 랙(또는 클라우드 가용영역)에 있는지 클러스터에 알려주는 기능이다. 이 정보가 있으면 Kafka는 같은 파티션의 복제본들을 서로 다른 랙에 분산 배치해, 랙 스위치 장애나 전원 이슈처럼 랙 단위로 발생하는 장애가 그 파티션의 모든 복제본을 동시에 앗아가지 않도록 한다.

멀티테넌시 환경(여러 팀·서비스가 하나의 클러스터를 공유)에서는 접근 제어와 자원 격리를 함께 갖춰야 한다. SASL(인증)과 ACL(토픽/컨슈머 그룹 단위 권한 부여)은 "누가 무엇을 할 수 있는가"를 통제하고, 쿼터(quota)는 "허용된 사용자가 자원을 얼마나 쓸 수 있는가"를 통제한다 — 프로듀서/컨슈머별 바이트 처리율(byte-rate)이나 요청 처리 시간 비율에 상한을 걸어, 특정 클라이언트가 과도한 트래픽을 발생시켜 같은 클러스터의 다른 팀에 영향을 주는 소음 이웃(noisy neighbor) 문제를 예방한다.

핵심 포인트

  • broker.rack 기반 랙 어웨어니스는 같은 파티션의 복제본을 서로 다른 랙/AZ로 분산해 랙 단위 장애로부터 보호한다
  • SASL/ACL은 접근 권한을, 쿼터는 자원 사용량 상한을 통제해 각각 다른 문제를 해결한다
  • 쿼터는 특정 클라이언트의 과도한 트래픽이 같은 클러스터를 쓰는 다른 팀에 영향을 주는 소음 이웃 문제를 막는다

파티션 수 설계 — T/c 공식과 '정정': 과잉 파티셔닝은 공짜가 아니다

새 토픽의 파티션 수를 정하는 가장 기본적인 접근은 목표 처리량(T)과 컨슈머 하나가 처리할 수 있는 처리량(c)의 비율, 즉 최소 T/c개 이상의 파티션이 필요하다는 계산이다. 예를 들어 초당 100MB를 처리해야 하고 컨슈머 하나가 초당 10MB를 처리할 수 있다면 최소 10개 파티션이 필요하다.

일부 자료는 여기에 향후 트래픽 증가에 대비해 "2~3배 정도 여유 있게 과잉 파티셔닝하라"는 경험칙을 덧붙이는데, 이 경험칙을 무비판적으로 따르는 것은 — 정정: 과잉 파티셔닝에는 실질적인 비용이 따른다는 점을 놓치는 것이다. 파티션이 많아지면 (1) 브로커가 유지해야 하는 열린 파일 핸들과 메모리(각 파티션 복제본이 별도 로그 세그먼트 파일을 가짐)가 늘어나고, (2) 컨트롤러가 관리해야 하는 메타데이터가 늘어 리더 선출/페일오버 시간이 길어질 수 있으며, (3) 파티션이 늘어난 뒤에는 same-key 라우팅이 바뀌어 기존 키의 순서 보장이 깨지고, (4) end-to-end 지연이 미세하게 늘어날 수 있다(각 파티션이 별도의 fetch 요청·복제 오버헤드를 유발하므로). 따라서 "일단 넉넉하게"보다는 실제 목표 처리량과 예상 성장 곡선을 근거로 필요한 만큼만 늘리고, 파티션 수는 늘릴 수만 있고 줄일 수 없다는 점(그리고 늘리면 키 매핑이 바뀐다는 점)을 감안해 신중하게 설계하는 것이 더 정확한 조언이다.

핵심 포인트

  • 파티션 수는 최소 T/c(목표 처리량/컨슈머당 처리량) 이상이어야 한다는 것이 기본 계산이다
  • '미래 대비 2~3배 과잉 파티셔닝'은 흔한 경험칙이지만 공짜가 아니다
  • 파티션이 많아지면 열린 파일 핸들·메모리·메타데이터 오버헤드가 늘고 리더 선출 시간이 길어질 수 있다
  • 파티션을 늘리면 키-파티션 매핑이 바뀌어 기존 키의 순서 보장이 깨지므로, 과잉 파티셔닝은 목표 처리량과 성장 곡선에 근거해 신중히 결정해야 한다

Compact와 Delete 클린업 정책, 그리고 톰스톤

cleanup.policy=delete(기본값)는 시간(log.retention.hours)이나 크기(log.retention.bytes) 조건에 따라 오래된 로그 세그먼트를 통째로 삭제한다. cleanup.policy=compact는 다른 기준으로 동작한다 — 같은 키를 가진 레코드들 중 가장 최근 값만 남기고 오래된 값을 백그라운드 로그 클리너가 제거해, 그 토픽을 "키별 최신 상태"만 남는 체인지로그로 만든다. 두 정책은 cleanup.policy=compact,delete처럼 동시에 지정할 수도 있는데, 이 경우 컴팩션으로 키별 최신값을 유지하되 그마저도 retention.ms를 넘으면 삭제하는 조합이 된다.

컴팩션 토픽에서 특정 키의 데이터를 "삭제"하고 싶을 때는 그 키에 값이 null인 레코드, 즉 톰스톤(tombstone)을 써넣는다. 로그 클리너는 톰스톤을 만나면 곧바로 지우지 않고 delete.retention.ms 동안 보존한 뒤(그 사이에 컨슈머들이 "이 키는 삭제됐다"는 사실을 읽어갈 시간을 준 뒤) 그 키의 이전 값들과 톰스톤 자체를 함께 제거한다. 이 유예 기간을 두지 않으면 톰스톤이 컴팩션으로 사라지기 전에 아직 그 키를 못 읽은 컨슈머는 삭제 사실 자체를 영영 알 수 없게 된다.

핵심 포인트

  • delete 정책은 시간/크기 기준으로 오래된 세그먼트를 통째로 삭제하고, compact 정책은 키 기준으로 최신 값만 남긴다
  • compact,delete를 함께 지정하면 컴팩션 + 시간 기반 삭제를 동시에 적용할 수 있다
  • 톰스톤(null 값 레코드)은 컴팩션 토픽에서 키를 삭제하는 신호이며, delete.retention.ms 동안 유예 기간을 두고서야 실제로 제거된다