Kafka Shallow Dive - 운영 — 모니터링, 튜닝, 장애 케이스

Published:

개념 다 끝났으니 마지막 주는 운영. 무엇을 봐야 하고, 어디를 튜닝하고, 어떤 장애가 자주 나는지. 이 글은 “나중에 새벽에 불려나왔을 때 다시 찾아볼 체크리스트” 용도로 정리.

무엇을 모니터링하나

Broker 레벨

지표 (JMX)의미임계
UnderReplicatedPartitionsISR이 RF보다 적은 파티션 수> 0 지속되면 문제
OfflinePartitionsCount사용 불가 파티션> 0 = 장애
ActiveControllerCount클러스터에 컨트롤러 수정확히 1이어야
RequestHandlerAvgIdlePercentI/O 스레드 여유율< 0.3 = 과부하
NetworkProcessorAvgIdlePercent네트워크 스레드 여유율< 0.3 = 과부하
BytesInPerSec / BytesOutPerSec초당 in/out 바이트용량 계획
LeaderCount이 브로커가 leader인 파티션 수브로커 간 균형 확인
PartitionCount이 브로커가 가진 파티션 총수균형 확인
IsrShrinksPerSec / IsrExpandsPerSecISR 변동 주기잦으면 네트워크/디스크 이슈

가장 먼저 봐야 할 빨간불 3개: UnderReplicatedPartitions, OfflinePartitionsCount, ActiveControllerCount.

Producer

  • record-error-rate / record-retry-rate — 재시도/에러 비율
  • request-latency-avg / -max — 브로커 응답 지연
  • buffer-available-bytes — accumulator 여유
  • batch-size-avg — 배치 크기 (너무 작으면 효율↓)

Consumer

  • Consumer lag — 가장 중요
  • records-consumed-rate — 초당 소비 레코드
  • fetch-latency-avg
  • commit-latency-avg
  • rebalance-rate-per-hour — 잦으면 문제

Lag은 kafka-consumer-groups.sh --describe 또는 Prometheus의 kafka-exporter / kafka-lag-exporter로 수집.

모니터링 스택 예시

Kafka brokers ── JMX ──▶ Prometheus JMX Exporter ──▶ Prometheus ──▶ Grafana
                                                          │
Consumer apps ── Micrometer ──────────────────────────────┤
                                                          │
kafka-exporter (lag/offsets) ─────────────────────────────┘

Grafana 대시보드는 Confluent/LinkedIn에서 공식적으로 공개한 템플릿들이 있고 대부분 그거 쓰면 됨 (Grafana 대시보드 ID 7589 같은 공개 템플릿).

용량 계획 감 잡기

대충의 계산:

필요 디스크 = (BytesInPerSec) × (retention 시간) × (replication factor)

예: 초당 50 MB 수신, 7일 보존, RF=3
   50 * 1024 * 1024 * 3600 * 24 * 7 * 3
   ≈ 90 TB (클러스터 전체)

노드 수는 “한 노드 I/O 한계”와 “파티션 수” 기준. 2025년 시점 기준 SSD 노드 한 대가 초당 100~300MB 정도 안정권. 여기에 replication 트래픽까지 더해지니 여유를 꽤 둬야 함.

파티션 수 가이드 (경험칙):

  • 브로커당 2,000~4,000 파티션이 안정권 (KRaft에선 더 가능)
  • 한 topic이 너무 많은 파티션(> 수백)은 피하기 — consumer rebalance 비용

튜닝 체크리스트

OS

  • 디스크: XFS 권장 (ext4도 OK). noatime 마운트
  • 스왑: vm.swappiness=1
  • 파일 핸들: ulimit -n 100000+
  • 네트워크: net.core.rmem_max, wmem_max 증대
  • 페이지 캐시를 크게 쓰니 메모리 넉넉히. Kafka JVM heap은 6~8GB면 충분, 나머진 OS 페이지 캐시로 (큰 힙은 GC 때문에 역효과)

JVM

-Xms6g -Xmx6g
-XX:MetaspaceSize=96m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=20
-XX:InitiatingHeapOccupancyPercent=35
-XX:G1HeapRegionSize=16M
-XX:MinMetaspaceFreeRatio=50
-XX:MaxMetaspaceFreeRatio=80

G1GC + 작은 힙. ZGC도 옵션이지만 운영 노하우는 G1이 많음.

Kafka

num.network.threads=8
num.io.threads=16
socket.send.buffer.bytes=1048576
socket.receive.buffer.bytes=1048576
socket.request.max.bytes=104857600

num.replica.fetchers=4         # follower fetch 병렬도
replica.fetch.max.bytes=10485760

log.flush.interval.messages=Long.MAX   # OS에 맡김 (기본)
log.segment.bytes=1073741824            # 1GB
log.retention.hours=168

Flush는 OS에 맡기는 게 기본. Kafka가 직접 fsync하지 않아도 replication으로 내구성 확보.

자주 나는 장애 패턴

1. 디스크 꽉 참

증상: 쓰기 실패, broker 다운

  • log.retention.* 가 topic 증가율보다 여유 있는지 확인
  • 특정 topic이 예상 외로 커졌는지 (파이프라인 버그 가능성)
  • compacted topic인데 compaction이 안 돌고 있진 않은지 (LogCleanerManager)

대응: 긴급엔 retention 축소 후 삭제 대기. 근본은 파티션 이동(kafka-reassign-partitions.sh) + 노드 추가.

2. Under-replicated 폭증

증상: UnderReplicatedPartitions > 0이 계속

  • 네트워크 병목 (특히 follower fetch)
  • 디스크 I/O 병목 (특히 한 브로커)
  • follower GC pause
  • num.replica.fetchers 부족

대응: 병목 노드 식별 → num.replica.fetchers 증대, rack 분포 재확인, 필요 시 파티션 재분배.

3. Consumer rebalance loop

증상: consumer group이 계속 rebalance 반복, lag이 톱니파

  • max.poll.interval.ms 초과 — 처리 시간이 한계 넘음
  • session.timeout.ms / heartbeat.interval.ms 조합 이상
  • Eager assignor를 쓰고 있어서 스케일링마다 stop-the-world
  • consumer OOM/크래시 반복

대응: cooperative sticky로 전환, max.poll.records 축소, 긴 작업은 별도 스레드+pause/resume, 인스턴스 메모리 확보.

4. Hot partition

증상: 특정 파티션에만 lag, 한 브로커 CPU/디스크만 100%

  • Key가 편중됨 (예: 특정 tenant가 메시지 대부분)
  • partitioner가 잘 못 분배함

대응: key 설계 재검토 (예: user_iduser_id:<random_salt>로 일부 분산), 파티션 수 조정, custom partitioner.

5. 프로듀서가 터짐 — “Broker failed the request due to duplicate sequence number”

  • 주로 idempotent producer 쓰면서 retry 설정이 꼬였을 때
  • producer 재시작 간격이 transactional.id.expiration.ms 보다 길어서 coordinator가 다른 tx를 진행 중일 때
  • transactional.id를 인스턴스마다 다르게 부여 + 안정적 이름 전략 필요

6. ZK → KRaft 마이그레이션 실패

  • 절차 건너뛰면 cluster id 불일치
  • 공식 문서의 단계대로. production 이전엔 staging에서 반드시 리허설
  • 롤백 경로 사전 테스트

운영 도구

공식 CLI

# 파티션 재분배 계획 생성
kafka-reassign-partitions.sh --bootstrap-server ... --generate ...

# 실행
kafka-reassign-partitions.sh --bootstrap-server ... --execute --reassignment-json-file plan.json

# consumer lag
kafka-consumer-groups.sh --bootstrap-server ... --describe --group <g>

# topic 정책 변경 (retention 등)
kafka-configs.sh --bootstrap-server ... --alter --entity-type topics --entity-name orders \
  --add-config retention.ms=86400000

# under-replicated partition 한 방 확인
kafka-topics.sh --bootstrap-server ... --describe --under-replicated-partitions

UI/모니터링

  • Kafka UI (provectus) — 무료, 가볍고 실무에 충분
  • Confluent Control Center — 상용, 기능 풍부
  • AKHQ — 오픈소스 UI
  • CMAK (구 Kafka Manager) — 전통적이지만 관리 중심

로그 compaction 주의

Compacted topic(cleanup.policy=compact)은 다르게 동작:

  • 같은 key의 오래된 값을 삭제
  • tombstone(value=null)으로 명시적 삭제 표현
  • __consumer_offsets, __transaction_state, Streams의 changelog 등이 전부 compacted
  • Log cleaner 스레드가 따로 돌아서 compact 수행 — 밀리면 디스크 폭주 원인

모니터링: LogCleanerRunning, log-cleaner-work-queue-size

시리즈 정리

10주간 Kafka를 처음부터 끝까지 훑었다. 큰 그림:

  1. 개요 / 아키텍처 — Kafka가 뭐고, 내부 구조(log, partition)가 어떻게 생겼는지
  2. Producer / Consumer — API와 그 뒤의 튜닝 포인트
  3. Replication / Exactly-once — 분산 시스템으로서의 보장
  4. Streams / Connect — Kafka 위에 얹히는 도구
  5. Schema Registry — 포맷/스키마 관리
  6. 운영 — 모니터링, 튜닝, 장애 케이스

출발점에서 “로그인 실패했을 때 오프셋이 왜 꼬이지” 같은 단편 질문에 머물렀다면, 이제는 “이 파이프라인에서 지키고 싶은 보장이 뭐고, 거기에 맞는 acks/min.isr/consumer 전략/스키마 모드는 뭔가”로 질문이 바뀌었다. 개별 옵션이 아니라 조합으로 보게 되는 게 핵심.

앞으로 할 것:

  • 직접 장애 시뮬레이션 (tc로 네트워크 지연, 디스크 꽉 채우기)
  • Outbox 패턴으로 end-to-end EOS 구현해보기
  • Kafka Streams로 실제 집계 파이프라인 만들기

시리즈 끝.