학습 목표

이 도메인이 묻는 것

문항 지문은 대개 메트릭 값 + 로그 한 줄 + 사용자 불만의 조합으로 옵니다. 정답을 고르는 요령은 가역적이고 진단적인 조치를 먼저 고르는 것입니다. 확인·조회가 조치보다 먼저 오고, 되돌릴 수 없는 조치 (unclean 선출 활성화, 토픽 삭제, 오프셋 리셋)는 항상 마지막입니다.

증상 → 먼저 확인할 것 요약표
증상먼저 확인해당 절차
프로듀서가 NotEnoughReplicas를 받는다 UnderMinIsrPartitionCount와 죽은 브로커 수 시나리오 1
브로커가 기동하다 죽는다 / 쓰기가 전부 실패 디스크 사용률 시나리오 2
컨슈머가 계속 리밸런스만 한다 배치 처리 시간과 max.poll.interval.ms 시나리오 3
lag이 계속 늘어난다 컨슈머가 느린가, 복제가 느린가, 유입이 늘었나 시나리오 4
토픽을 만들 수 없다 / 설정 변경이 타임아웃 ActiveControllerCount 합계 시나리오 5
특정 파티션의 읽기·쓰기가 모두 안 된다 OfflinePartitionsCount 시나리오 6

공통 트리아지 — 계층을 좁히는 순서

어떤 증상이든 아래 순서로 계층을 좁힌 뒤 개별 절차로 들어갑니다. 운영자 시험이 요구하는 사고 순서이기도 합니다.

  1. 컨트롤러ActiveControllerCount 합계가 1인가. 아니면 메타데이터 변경이 전부 실패하며, 토픽 단위 증상으로 위장합니다.
  2. 브로커 — 몇 대가 살아 있는가. 디스크 여유가 있는가. RequestHandlerAvgIdlePercent가 0.3 이상인가.
  3. 파티션OfflinePartitionsCountUnderMinIsrPartitionCountUnderReplicatedPartitions 순으로 봅니다. 앞의 것이 더 심각합니다.
  4. 클라이언트 — 그룹 상태, lag, 인증·인가 오류. 여기까지 와서야 클라이언트를 의심합니다.
트러블슈팅 결정 트리 — 브로커 성능이 저하될 때 브로커 성능 저하를 메트릭에서 출발해 좁혀 가는 결정 트리입니다. RequestHandlerAvgIdlePercent 가 0.3 아래면 요청 처리 스레드가 부족하므로 num.io.threads 와 디스크 상태를 봅니다. NetworkProcessorAvgIdlePercent 가 낮으면 num.network.threads 를 검토합니다. UnderReplicatedPartitions 가 0 이 아니면 복제가 밀린 상태이므로 num.replica.fetchers 와 네트워크·디스크를 확인합니다. OfflinePartitionsCount 가 0 이 아니면 리더가 없는 파티션이 있어 읽기와 쓰기가 모두 막힙니다. ActiveControllerCount 의 클러스터 합이 1 이 아니면 컨트롤러 쿼럼 문제입니다. 로그 flush 지연이 크면 디스크가 병목이고, 리더가 한 브로커에 몰려 있으면 preferred 리더 선출로 균형을 되돌립니다. 브로커가 느리다 · 응답이 밀린다 추측하지 말고 메트릭부터 봅니다. 각 판단 아래가 확인할 지표입니다. RequestHandlerAvgIdle < 0.3? RequestHandlerPool 요청 처리 스레드 포화 · num.io.threads 를 올린다 · 디스크 지연이 원인이면 효과 없음 아니오 NetworkProcessorAvgIdle 낮음? SocketServer 메트릭 네트워크 스레드 포화 · num.network.threads 검토 · 리스너마다 별도 풀이다 아니오 UnderReplicatedPartitions > 0? --under-replicated 복제가 밀리는 중 · num.replica.fetchers · 대역폭 확인 · IsrShrinksPerSec 동반 상승 확인 아니오 OfflinePartitionsCount > 0? KafkaController 리더 없는 파티션 — 최우선 · 읽기·쓰기가 모두 막힌다 · --unavailable-partitions 로 범위 확인 아니오 ActiveControllerCount 합 ≠ 1? kafka-metadata-quorum 컨트롤러 쿼럼 이상 · 정확히 한 대만 1 이어야 한다 · describe --status 로 lag 확인 아니오 로그 flush 지연이 크다? LogFlushRateAndTimeMs 디스크가 병목 · kafka-log-dirs.sh --describe · 한 디스크에 로그 디렉터리 여러 개 금지 아니오 리더가 한 브로커에 몰렸다? --describe Leader 열 리더 편중 · kafka-leader-election.sh --election-type preferred · 재배치는 kafka-reassign-partitions.sh 정상 기준: URP 0 · UnderMinIsrPartitionCount 0 · OfflinePartitionsCount 0 · ActiveController 합 1. RequestHandlerAvgIdlePercent 는 0~1 값이며 공식 문서는 0.3 이상을 권합니다.
결정 트리 — 브로커 성능 저하: 디스크 · 스레드 · 복제 · 클라이언트 중 어디를 볼지 분기

장애 시나리오와 대응

시나리오 1 — URP가 0이 아니고 프로듀서가 실패한다

먼저 "복제가 뒤처졌다"와 "쓰기가 거부됐다"를 구분합니다. 전자는 UnderReplicatedPartitions, 후자는 UnderMinIsrPartitionCount입니다.

URP 대응 절차
단계할 일판단
1 UnderMinIsrPartitionCount를 확인합니다. 0보다 크면 이미 쓰기가 실패하는 중입니다. 최우선 대응 대상.
2 살아 있는 브로커 수를 셉니다. 브로커 다운이면 복구가 정답입니다. 설정으로 우회하지 않습니다.
3 kafka-topics.sh --describe로 어느 파티션·브로커인지 좁힙니다. 특정 브로커에 몰려 있으면 그 노드 문제, 흩어져 있으면 클러스터 전반 문제입니다.
4 남아 있는 복제 스로틀을 확인합니다. 재할당 후 --verify를 생략했으면 여기서 걸립니다. 해제하면 즉시 회복됩니다.
5 대상 브로커의 디스크 I/O, RequestHandlerAvgIdlePercent, ReplicaFetcherManager MaxLag를 봅니다. 디스크 포화면 시나리오 2로 갑니다.
6 복제 속도가 부족하면 num.replica.fetchers(기본 1)를 올립니다. cluster-wide라 재시작 없이 적용됩니다.
7 서비스가 멈춰 있고 브로커 복구에 시간이 걸린다면 해당 토픽만 min.insync.replicas를 한시적으로 낮춥니다. 내구성을 낮추는 거래입니다. 복구 후 반드시 되돌립니다.

시나리오 2 — 디스크가 찼다

가장 급한 장애입니다. 디스크가 가득 차면 브로커는 쓰기를 할 수 없고, 재시작해도 로그 복구 단계에서 다시 실패할 수 있습니다.

  1. 무엇이 공간을 먹는지 먼저 확인합니다. 데이터 로그인지, 애플리케이션 로그(state-change.log 등)인지, 다른 프로세스인지 나눕니다. 애플리케이션 로그가 원인인 경우가 의외로 많고, 그렇다면 데이터를 건드리지 않고 해결됩니다.
  2. 가장 큰 토픽의 리텐션을 동적으로 줄입니다. kafka-configs.sh --entity-type topics --alter --add-config retention.ms=...는 재시작이 필요 없습니다. 재처리 여유를 잃는 대신 공간을 확보합니다.
  3. 세그먼트가 굴러가야 삭제됩니다. 리텐션만 줄여도 공간이 안 줄면 segment.ms/segment.bytes를 줄여 활성 세그먼트를 봉인시킵니다. 활성 세그먼트는 삭제 대상이 아닙니다.
  4. 정리 주기를 기다립니다. log.retention.check.interval.ms는 기본 300000(5분)이라 설정 직후 즉시 줄지 않습니다. 이 지연을 모르면 "설정이 안 먹는다"고 오판합니다.
  5. 여유가 생기면 근본 대책을 넣습니다. retention.bytes는 기본값이 -1(무제한)이므로 크기 상한을 걸어 두면 같은 사고를 구조적으로 막습니다.
  6. 장기 대책: 디스크·브로커 추가 후 파티션 재할당. 로그 디렉터리를 줄여야 한다면 cordoned.log.dirs로 신규 배치를 막고 재할당으로 비운 뒤 제거합니다(절차는 Cluster Configuration).

시나리오 3 — 컨슈머가 계속 리밸런스만 한다

원인은 세 갈래이고, 각각 조정할 설정이 다릅니다.

리밸런스 스톰의 세 원인
원인증거조치
① 처리가 느려 poll 간격 초과 한 배치 처리 시간이 max.poll.interval.ms(기본 300000)에 근접. 로그에 그룹 이탈 메시지 max.poll.records(기본 500)를 줄여 배치를 작게 하거나 max.poll.interval.ms를 늘립니다. 배치를 줄이는 쪽이 우선입니다.
② 컨슈머 프로세스가 자꾸 재시작 --members의 멤버 수가 오르내림. 배포·OOM·헬스체크 실패 static membership(group.instance.id)으로 재시작 시 리밸런스를 회피합니다. 근본 대책은 프로세스 안정화입니다.
③ 하트비트가 끊김 GC 정지나 네트워크로 session.timeout.ms(기본 45000) 초과 GC·네트워크를 먼저 봅니다. heartbeat.interval.ms(기본 3000)는 세션 타임아웃의 1/3 이하로 유지합니다.

시나리오 4 — lag이 계속 늘어난다

먼저 컨슈머가 느린 것인지, 복제가 느린 것인지, 유입이 늘어난 것인지를 가릅니다. 세 경우의 조치가 전혀 다릅니다.

트러블슈팅 결정 트리 — 컨슈머가 메시지를 읽지 않을 때 컨슈머가 읽지 않는 상황을 위에서 아래로 좁혀 가는 결정 트리입니다. 마름모는 판단, 사각형은 조치이며 각 판단에는 확인할 CLI 명령이 함께 적혀 있습니다. 첫째, 그룹에 멤버가 있는지 kafka-consumer-groups.sh --describe --members 로 확인합니다. 멤버가 없으면 프로세스 상태와 group.id, subscribe 대상을 봅니다. 둘째, LOG-END-OFFSET 이 멈춰 있으면 읽을 데이터가 없는 것이므로 프로듀서 쪽 문제입니다. 셋째, 그룹 상태가 PreparingRebalance 를 반복하면 리밸런스 루프이므로 max.poll.interval.ms 와 max.poll.records 를 조정합니다. 넷째, 컨슈머 수가 파티션 수보다 많으면 남는 컨슈머는 유휴 상태이므로 파티션을 늘립니다. 다섯째, LAG 이 0 인데 기대한 데이터가 없으면 새 group.id 와 auto.offset.reset=latest 조합으로 과거 데이터를 건너뛴 경우이므로 오프셋을 되돌립니다. 여섯째, isolation.level 이 read_committed 이면 진행 중인 트랜잭션이 LSO 를 막고 있을 수 있습니다. 마지막으로 권한 오류를 확인합니다. 컨슈머가 메시지를 읽지 않는다 마름모 = 판단 · 사각형 = 조치. 위에서 아래로 순서대로 좁혀 갑니다. 그룹에 멤버가 없다? --describe --members 컨슈머가 붙지 못한 상태 · 프로세스 생존 · group.id 오타 확인 · subscribe 대상 토픽 확인 · bootstrap.servers 확인 → D-101 아니오 LOG-END-OFFSET 이 안 늘어난다? kafka-get-offsets.sh 읽을 데이터 자체가 없다 · 프로듀서 쪽 문제 → D-121 · 토픽·파티션을 잘못 보고 있는지 확인 아니오 PreparingRebalance 반복? --describe --state 리밸런스 루프 · max.poll.records 를 줄인다 · max.poll.interval.ms 를 올린다 · 원인 분석은 D-111 (case02) 아니오 컨슈머 수 > 파티션 수? --describe --members 남는 컨슈머는 유휴 상태 · 한 파티션 = 한 컨슈머 · kafka-topics.sh --alter --partitions N · 파티션은 줄일 수 없다 아니오 LAG 0 인데 데이터가 없다? --describe --group G 과거 데이터를 건너뛴 상태 · 새 group.id + latest 조합 → D-110 · --reset-offsets --to-earliest --execute 아니오 isolation.level=read_committed? 컨슈머 설정 확인 트랜잭션이 LSO 를 막는 중 · 커밋 안 된 트랜잭션 뒤는 안 보임 · 커밋·중단 또는 타임아웃을 기다림 아니오 여기까지 모두 아니라면 kafka-acls.sh --list 권한을 확인한다 · TOPIC / GROUP_AUTHORIZATION_FAILED · 토픽 Read + 그룹 Read 둘 다 필요 모든 kafka-consumer-groups.sh 명령에는 --bootstrap-server 가 필요합니다. 원인은 대개 클라이언트 쪽입니다 — 브로커 장애부터 의심하지 마세요.
결정 트리 — 컨슈머가 데이터를 못 읽는다: 권한 · 오프셋 · 리밸런스 · 복제 중 어디를 볼지 분기
  1. kafka-consumer-groups.sh --describe파티션별 lag을 봅니다. 특정 파티션만 크면 데이터 편향(키 쏠림)이거나 그 파티션 담당 컨슈머만 느린 것입니다.
  2. --members --verbose할당이 균등한지 확인합니다. 컨슈머 수가 파티션 수보다 많으면 남는 컨슈머는 유휴 상태입니다.
  3. 복제가 뒤처졌는지 확인합니다. URP가 0보다 크면 high watermark가 뒤로 밀려 컨슈머가 최선을 다해도 lag이 0이 되지 않습니다. 이 경우는 컨슈머 문제가 아닙니다.
  4. 프로듀서 쪽 유입량(BytesInPerSec)이 늘었는지 봅니다. 유입이 원인이면 컨슈머를 파티션 수까지 늘려 처리량을 올립니다. 그 이상 늘려도 남는 컨슈머는 일하지 않습니다.
  5. 컨슈머 자체가 느리면 fetch 크기(max.partition.fetch.bytes 기본 1048576, fetch.max.bytes 기본 52428800)와 처리 로직을 봅니다. 외부 API 호출이 병목인 경우가 가장 흔합니다.
  6. 오프셋 리셋은 마지막 수단입니다. --reset-offsets --to-latest는 미처리 데이터를 버리는 조치이며, 컨슈머가 모두 정지된 상태에서만 동작합니다.

시나리오 5 — 토픽 생성·설정 변경이 전부 타임아웃

  1. ActiveControllerCount 합계를 확인합니다. 0이면 액티브 컨트롤러가 없습니다. 기존 파티션의 읽기·쓰기는 한동안 계속되므로 사용자는 아직 모를 수 있습니다.
  2. kafka-metadata-quorum.sh --bootstrap-controller ... describe --status로 voter 목록과 리더를 확인합니다. 브로커가 응답하지 않으면 컨트롤러로 직접 붙습니다.
  3. 과반이 살아 있는지 셉니다. 3대 중 2대, 5대 중 3대가 필요합니다. 과반을 잃으면 리더 선출 자체가 불가능하므로 노드 복구가 유일한 정답입니다.
  4. 선거가 반복되면(IsrShrinksPerSec·IsrExpandsPerSec가 계속 non-zero) 컨트롤러 노드 간 네트워크와 controller.quorum.fetch.timeout.ms(기본 2000)를 봅니다.
  5. MetadataErrorCount가 올라 있으면 메타데이터 처리 오류가 있었다는 뜻입니다. 이 상태에서 노드를 다시 포맷하기 전에 반드시 describe --replication으로 과반이 커밋된 데이터를 보유하는지 확인합니다. 그러지 않으면 커밋된 데이터가 빠진 리더가 선출될 수 있습니다.
  6. 컨트롤러를 교체·추가할 때는 배포 아키텍처의 순서를 따릅니다 — 프로비저닝 → 기동 → 따라잡기 확인 → add-controller.

시나리오 6 — OfflinePartitionsCount가 0이 아니다

가장 심각한 상태입니다. 리더가 없는 파티션은 읽기와 쓰기가 모두 불가능합니다.

  1. 죽은 브로커 수와 해당 파티션의 RF를 비교합니다. RF보다 많이 죽었다면 offline은 당연한 결과입니다.
  2. kafka-topics.sh --describe로 리더가 없는 파티션을 찾고, 그 레플리카가 어느 브로커에 있는지 확인합니다.
  3. 그 브로커를 되살리는 것이 정답입니다. 디스크가 살아 있다면 기동만으로 회복됩니다.
  4. 브로커를 되살릴 수 없고 ISR이 비어 있다면 남은 선택지는 unclean 선출(데이터 유실)이거나 기다리는 것뿐입니다. 이 결정은 운영자 혼자 하지 않습니다.
  5. 회복 후 UncleanLeaderElectionsPerSec가 증가했는지 확인해 실제로 유실이 있었는지 기록합니다. 사후 보고에 필요합니다.
  6. 재발 방지: RF 상향, 랙 인식, min.insync.replicas=2, auto.create.topics.enable=false.

필수 CLI 명령어

1분 안에 상태를 파악하는 순서
# 1) 메타데이터 계층이 살아 있는가
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status

# 2) 브로커가 응답하지 않으면 컨트롤러로 직접
bin/kafka-metadata-quorum.sh --bootstrap-controller localhost:9093 describe --status

# 3) 복제 이상을 파티션 단위로 (Isr 가 Replicas 보다 짧은 줄을 찾습니다)
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe

# 4) 소비 지연
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group order-workers

# 5) 그룹 멤버가 오르내리는지 (리밸런스 스톰 판별)
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
    --describe --group order-workers --members --verbose
디스크 풀 — 즉시 쓸 수 있는 완화책
# 토픽 리텐션을 임시로 축소 (재시작 불필요)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name bulk-events --alter --add-config retention.ms=3600000

# 세그먼트를 더 빨리 굴려 삭제 대상이 되게
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name bulk-events --alter --add-config segment.ms=600000

# 크기 상한을 걸어 재발 방지 (기본값은 -1 = 무제한)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name bulk-events --alter --add-config retention.bytes=53687091200

# 상황이 끝나면 원복
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics \
    --entity-name bulk-events --alter --delete-config retention.ms,segment.ms
남은 스로틀 확인과 해제 — URP 만성화의 1순위 원인
# 브로커·토픽에 스로틀 설정이 남아 있는지
bin/kafka-configs.sh --describe --bootstrap-server localhost:9092 --entity-type brokers
bin/kafka-configs.sh --describe --bootstrap-server localhost:9092 --entity-type topics

# 원래 재할당 JSON 이 있으면 정상 경로로 해제
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
    --reassignment-json-file reassign.json --verify

# JSON 을 잃었다면 직접 삭제
bin/kafka-configs.sh --bootstrap-server localhost:9092 --entity-type brokers --entity-name 1 \
    --alter --delete-config leader.replication.throttled.rate,follower.replication.throttled.rate
리더 균형 회복과 조사용 로그 레벨 상향
# 롤링 재시작 후 리더가 한쪽에 쏠렸을 때
bin/kafka-leader-election.sh --bootstrap-server localhost:9092 \
    --election-type preferred --all-topic-partitions

# 조사용 로그 레벨 상향 (조사 후 반드시 원복)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --broker-logger 0 \
    --alter --add-config kafka.server.ReplicaManager=DEBUG
오프셋 리셋 — 컨슈머가 모두 정지된 상태에서만
# 먼저 dry-run: 옵션 없이 실행하면 "무엇을 바꿀지"만 출력합니다
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
    --reset-offsets --group order-workers --topic orders --to-latest

# 확인 후 실제 적용
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
    --reset-offsets --group order-workers --topic orders --to-latest --execute

# 특정 시각으로 되감기 (재처리)
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
    --reset-offsets --group order-workers --topic orders \
    --to-datetime '2026-07-27T00:00:00.000' --execute

반드시 외워야 할 설정값

장애 대응에서 손대는 설정들 (Kafka 4.3 기준)
설정 기본값 언제 손대는가 주의
min.insync.replicas
broker topic
1 ISR 부족으로 쓰기가 막혔을 때 한시적으로 하향 cluster-wide라 즉시 적용됩니다. 복구 후 원복 필수.
unclean.leader.election.enable
broker topic
false offline 파티션에서 유실을 감수하고 가용성을 되찾을 때 데이터 유실 확정. 최후의 수단이며 즉시 되돌립니다.
num.replica.fetchers
broker
1 복제가 못 따라잡을 때 cluster-wide. 디스크가 병목이면 효과가 없습니다.
num.io.threads
broker
8 RequestHandlerAvgIdlePercent가 0.3 미만일 때 cluster-wide. CPU가 이미 포화면 효과가 없습니다.
retention.ms
topic
604800000
(7일)
디스크 풀 완화 재처리 여유를 잃습니다. 세그먼트가 굴러야 실제로 줍니다.
retention.bytes
topic
-1
(무제한)
디스크 풀 재발 방지 기본적으로 꺼져 있습니다. 운영 토픽에는 걸어 두는 편이 안전합니다.
log.retention.check.interval.ms
broker
300000
(5분)
read-only. 리텐션이 즉시 듣지 않는 이유입니다.
max.poll.records
consumer
500 리밸런스 스톰 원인 ① 배치를 줄이는 것이 타임아웃을 늘리는 것보다 안전합니다.
max.poll.interval.ms
consumer
300000 처리 시간이 구조적으로 긴 경우 너무 크게 잡으면 죽은 컨슈머를 오래 감지하지 못합니다.
session.timeout.ms
consumer
45000 하트비트 끊김 브로커의 그룹 허용 범위 안이어야 합니다.
heartbeat.interval.ms
consumer
3000 동일 세션 타임아웃의 1/3 이하로 유지합니다.
max.partition.fetch.bytes
consumer
1048576 느린 컨슈머 튜닝 브로커의 message.max.bytes(1048588)와 함께 봐야 합니다.
fetch.max.bytes
consumer
52428800
(50 MiB)
동일 메모리 사용량이 함께 올라갑니다.
group.protocol
consumer
classic 리밸런스 구조 개선 KIP-848은 4.0 GA이지만 기본값이 아닙니다.

자주 나오는 함정

관련 케이스 스터디

결정 트리 전체는 트러블슈팅 치트시트, 메트릭 이름과 임계값은 Observability, 조치에 쓰는 명령의 상세 옵션은 CLI 치트시트Cluster Configuration에 있습니다.

미니 퀴즈

증상 → 조치 순서 배열이 중심입니다. 오답 선택지가 "결국 필요하지만 지금은 아닌 조치"임을 확인하세요.

공식 문서 출처