CCAAK · 섹션 7
Troubleshooting
시나리오 문항의 본진입니다. 이 섹션의 문항은 거의 전부 "이 증상에서 무엇을 먼저 확인하는가" 형태이고, 선택지에는 결국 필요하지만 지금은 아닌 조치가 섞여 있습니다. 따라서 개별 지식이 아니라 순서를 외워야 합니다. 아래 여섯 시나리오의 번호 매긴 절차가 그 순서입니다.
학습 목표
- 증상에서 계층(컨트롤러 · 브로커 · 파티션 · 클라이언트)을 좁히는 순서를 적용할 수 있습니다.
- URP와 offline 파티션을 다른 심각도로 다루고 각각의 절차를 실행할 수 있습니다.
- 디스크 풀 상황에서 가장 먼저 해야 할 것과 하면 안 되는 것을 구분할 수 있습니다.
- 리밸런스 스톰의 원인 3가지를 나누고 각각에 맞는 설정을 조정할 수 있습니다.
- 느린 컨슈머와 느린 복제를 lag만 보고 혼동하지 않습니다.
이 도메인이 묻는 것
문항 지문은 대개 메트릭 값 + 로그 한 줄 + 사용자 불만의 조합으로 옵니다. 정답을 고르는 요령은 가역적이고 진단적인 조치를 먼저 고르는 것입니다. 확인·조회가 조치보다 먼저 오고, 되돌릴 수 없는 조치 (unclean 선출 활성화, 토픽 삭제, 오프셋 리셋)는 항상 마지막입니다.
| 증상 | 먼저 확인 | 해당 절차 |
|---|---|---|
프로듀서가 NotEnoughReplicas를 받는다 |
UnderMinIsrPartitionCount와 죽은 브로커 수 |
시나리오 1 |
| 브로커가 기동하다 죽는다 / 쓰기가 전부 실패 | 디스크 사용률 | 시나리오 2 |
| 컨슈머가 계속 리밸런스만 한다 | 배치 처리 시간과 max.poll.interval.ms |
시나리오 3 |
| lag이 계속 늘어난다 | 컨슈머가 느린가, 복제가 느린가, 유입이 늘었나 | 시나리오 4 |
| 토픽을 만들 수 없다 / 설정 변경이 타임아웃 | ActiveControllerCount 합계 |
시나리오 5 |
| 특정 파티션의 읽기·쓰기가 모두 안 된다 | OfflinePartitionsCount |
시나리오 6 |
공통 트리아지 — 계층을 좁히는 순서
어떤 증상이든 아래 순서로 계층을 좁힌 뒤 개별 절차로 들어갑니다. 운영자 시험이 요구하는 사고 순서이기도 합니다.
- 컨트롤러 —
ActiveControllerCount합계가 1인가. 아니면 메타데이터 변경이 전부 실패하며, 토픽 단위 증상으로 위장합니다. - 브로커 — 몇 대가 살아 있는가. 디스크 여유가 있는가.
RequestHandlerAvgIdlePercent가 0.3 이상인가. - 파티션 —
OfflinePartitionsCount→UnderMinIsrPartitionCount→UnderReplicatedPartitions순으로 봅니다. 앞의 것이 더 심각합니다. - 클라이언트 — 그룹 상태, lag, 인증·인가 오류. 여기까지 와서야 클라이언트를 의심합니다.
장애 시나리오와 대응
시나리오 1 — URP가 0이 아니고 프로듀서가 실패한다
먼저 "복제가 뒤처졌다"와 "쓰기가 거부됐다"를 구분합니다.
전자는 UnderReplicatedPartitions, 후자는 UnderMinIsrPartitionCount입니다.
| 단계 | 할 일 | 판단 |
|---|---|---|
| 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 — 디스크가 찼다
가장 급한 장애입니다. 디스크가 가득 차면 브로커는 쓰기를 할 수 없고, 재시작해도 로그 복구 단계에서 다시 실패할 수 있습니다.
-
무엇이 공간을 먹는지 먼저 확인합니다.
데이터 로그인지, 애플리케이션 로그(
state-change.log등)인지, 다른 프로세스인지 나눕니다. 애플리케이션 로그가 원인인 경우가 의외로 많고, 그렇다면 데이터를 건드리지 않고 해결됩니다. -
가장 큰 토픽의 리텐션을 동적으로 줄입니다.
kafka-configs.sh --entity-type topics --alter --add-config retention.ms=...는 재시작이 필요 없습니다. 재처리 여유를 잃는 대신 공간을 확보합니다. -
세그먼트가 굴러가야 삭제됩니다.
리텐션만 줄여도 공간이 안 줄면
segment.ms/segment.bytes를 줄여 활성 세그먼트를 봉인시킵니다. 활성 세그먼트는 삭제 대상이 아닙니다. -
정리 주기를 기다립니다.
log.retention.check.interval.ms는 기본 300000(5분)이라 설정 직후 즉시 줄지 않습니다. 이 지연을 모르면 "설정이 안 먹는다"고 오판합니다. -
여유가 생기면 근본 대책을 넣습니다.
retention.bytes는 기본값이 -1(무제한)이므로 크기 상한을 걸어 두면 같은 사고를 구조적으로 막습니다. -
장기 대책: 디스크·브로커 추가 후 파티션 재할당.
로그 디렉터리를 줄여야 한다면
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이 계속 늘어난다
먼저 컨슈머가 느린 것인지, 복제가 느린 것인지, 유입이 늘어난 것인지를 가릅니다. 세 경우의 조치가 전혀 다릅니다.
-
kafka-consumer-groups.sh --describe로 파티션별 lag을 봅니다. 특정 파티션만 크면 데이터 편향(키 쏠림)이거나 그 파티션 담당 컨슈머만 느린 것입니다. -
--members --verbose로 할당이 균등한지 확인합니다. 컨슈머 수가 파티션 수보다 많으면 남는 컨슈머는 유휴 상태입니다. - 복제가 뒤처졌는지 확인합니다. URP가 0보다 크면 high watermark가 뒤로 밀려 컨슈머가 최선을 다해도 lag이 0이 되지 않습니다. 이 경우는 컨슈머 문제가 아닙니다.
-
프로듀서 쪽 유입량(
BytesInPerSec)이 늘었는지 봅니다. 유입이 원인이면 컨슈머를 파티션 수까지 늘려 처리량을 올립니다. 그 이상 늘려도 남는 컨슈머는 일하지 않습니다. -
컨슈머 자체가 느리면 fetch 크기(
max.partition.fetch.bytes기본 1048576,fetch.max.bytes기본 52428800)와 처리 로직을 봅니다. 외부 API 호출이 병목인 경우가 가장 흔합니다. -
오프셋 리셋은 마지막 수단입니다.
--reset-offsets --to-latest는 미처리 데이터를 버리는 조치이며, 컨슈머가 모두 정지된 상태에서만 동작합니다.
시나리오 5 — 토픽 생성·설정 변경이 전부 타임아웃
-
ActiveControllerCount합계를 확인합니다. 0이면 액티브 컨트롤러가 없습니다. 기존 파티션의 읽기·쓰기는 한동안 계속되므로 사용자는 아직 모를 수 있습니다. -
kafka-metadata-quorum.sh --bootstrap-controller ... describe --status로 voter 목록과 리더를 확인합니다. 브로커가 응답하지 않으면 컨트롤러로 직접 붙습니다. - 과반이 살아 있는지 셉니다. 3대 중 2대, 5대 중 3대가 필요합니다. 과반을 잃으면 리더 선출 자체가 불가능하므로 노드 복구가 유일한 정답입니다.
-
선거가 반복되면(
IsrShrinksPerSec·IsrExpandsPerSec가 계속 non-zero) 컨트롤러 노드 간 네트워크와controller.quorum.fetch.timeout.ms(기본 2000)를 봅니다. -
MetadataErrorCount가 올라 있으면 메타데이터 처리 오류가 있었다는 뜻입니다. 이 상태에서 노드를 다시 포맷하기 전에 반드시describe --replication으로 과반이 커밋된 데이터를 보유하는지 확인합니다. 그러지 않으면 커밋된 데이터가 빠진 리더가 선출될 수 있습니다. -
컨트롤러를 교체·추가할 때는
배포 아키텍처의 순서를 따릅니다 —
프로비저닝 → 기동 → 따라잡기 확인 →
add-controller.
시나리오 6 — OfflinePartitionsCount가 0이 아니다
가장 심각한 상태입니다. 리더가 없는 파티션은 읽기와 쓰기가 모두 불가능합니다.
- 죽은 브로커 수와 해당 파티션의 RF를 비교합니다. RF보다 많이 죽었다면 offline은 당연한 결과입니다.
kafka-topics.sh --describe로 리더가 없는 파티션을 찾고, 그 레플리카가 어느 브로커에 있는지 확인합니다.- 그 브로커를 되살리는 것이 정답입니다. 디스크가 살아 있다면 기동만으로 회복됩니다.
- 브로커를 되살릴 수 없고 ISR이 비어 있다면 남은 선택지는 unclean 선출(데이터 유실)이거나 기다리는 것뿐입니다. 이 결정은 운영자 혼자 하지 않습니다.
- 회복 후
UncleanLeaderElectionsPerSec가 증가했는지 확인해 실제로 유실이 있었는지 기록합니다. 사후 보고에 필요합니다. - 재발 방지: RF 상향, 랙 인식,
min.insync.replicas=2,auto.create.topics.enable=false.
필수 CLI 명령어
# 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
# 브로커·토픽에 스로틀 설정이 남아 있는지
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
반드시 외워야 할 설정값
| 설정 | 기본값 | 언제 손대는가 | 주의 |
|---|---|---|---|
min.insync.replicasbroker topic |
1 | ISR 부족으로 쓰기가 막혔을 때 한시적으로 하향 | cluster-wide라 즉시 적용됩니다. 복구 후 원복 필수. |
unclean.leader.election.enablebroker topic |
false | offline 파티션에서 유실을 감수하고 가용성을 되찾을 때 | 데이터 유실 확정. 최후의 수단이며 즉시 되돌립니다. |
num.replica.fetchersbroker |
1 | 복제가 못 따라잡을 때 | cluster-wide. 디스크가 병목이면 효과가 없습니다. |
num.io.threadsbroker |
8 | RequestHandlerAvgIdlePercent가 0.3 미만일 때 |
cluster-wide. CPU가 이미 포화면 효과가 없습니다. |
retention.mstopic |
604800000 (7일) |
디스크 풀 완화 | 재처리 여유를 잃습니다. 세그먼트가 굴러야 실제로 줍니다. |
retention.bytestopic |
-1 (무제한) |
디스크 풀 재발 방지 | 기본적으로 꺼져 있습니다. 운영 토픽에는 걸어 두는 편이 안전합니다. |
log.retention.check.interval.msbroker |
300000 (5분) |
— | read-only. 리텐션이 즉시 듣지 않는 이유입니다. |
max.poll.recordsconsumer |
500 | 리밸런스 스톰 원인 ① | 배치를 줄이는 것이 타임아웃을 늘리는 것보다 안전합니다. |
max.poll.interval.msconsumer |
300000 | 처리 시간이 구조적으로 긴 경우 | 너무 크게 잡으면 죽은 컨슈머를 오래 감지하지 못합니다. |
session.timeout.msconsumer |
45000 | 하트비트 끊김 | 브로커의 그룹 허용 범위 안이어야 합니다. |
heartbeat.interval.msconsumer |
3000 | 동일 | 세션 타임아웃의 1/3 이하로 유지합니다. |
max.partition.fetch.bytesconsumer |
1048576 | 느린 컨슈머 튜닝 | 브로커의 message.max.bytes(1048588)와 함께 봐야 합니다. |
fetch.max.bytesconsumer |
52428800 (50 MiB) |
동일 | 메모리 사용량이 함께 올라갑니다. |
group.protocolconsumer |
classic |
리밸런스 구조 개선 | KIP-848은 4.0 GA이지만 기본값이 아닙니다. |
자주 나오는 함정
관련 케이스 스터디
- 케이스 2 · 컨슈머가 무한 리밸런스 루프에 빠졌다 — 시나리오 3의 실제 사례
- 케이스 1 · 배포 후 며칠치 데이터가 사라졌다 — 리텐션 축소의 대가
- 케이스 6 · RF=3인데 브로커 1대 죽자 유실됐다 —
min.insync.replicas와 offline - 케이스 10 · 큰 메시지가 무한 재시도로 쌓였다 — 크기 설정 불일치
결정 트리 전체는 트러블슈팅 치트시트, 메트릭 이름과 임계값은 Observability, 조치에 쓰는 명령의 상세 옵션은 CLI 치트시트와 Cluster Configuration에 있습니다.
미니 퀴즈
증상 → 조치 순서 배열이 중심입니다. 오답 선택지가 "결국 필요하지만 지금은 아닌 조치"임을 확인하세요.
공식 문서 출처
- Monitoring —
UnderReplicatedPartitions,UnderMinIsrPartitionCount,OfflinePartitionsCount,ActiveControllerCount,UncleanLeaderElectionsPerSec,RequestHandlerAvgIdlePercent,MetadataErrorCount - Basic Kafka Operations —
--reset-offsets시나리오와 제약, 스로틀 해제, preferred 리더 선출, cordon과 브로커 제거 - Broker Configs —
min.insync.replicas,unclean.leader.election.enable,num.replica.fetchers,log.retention.check.interval.ms기본값과 갱신 모드 - Topic Configs —
retention.ms,retention.bytes,segment.ms - Consumer Configs —
max.poll.records,max.poll.interval.ms,session.timeout.ms,fetch.max.bytes,group.protocol기본값 - KRaft — 쿼럼 과반,
kafka-metadata-quorum.sh, 컨트롤러 디스크 교체 시 확인 절차 - Hardware and OS — 디스크·파일시스템, 로그 복구