카프카 클러스터를 안정적으로 운영하기 위한 첫걸음은 시스템의 상태를 지속적으로 관찰하고 측정하는 모니터링입니다. "측정할 수 없으면 개선할 수 없다"는 말처럼, 주요 지표(Metric)를 통해 클러스터의 건강 상태를 파악하고 잠재적인 문제를 조기에 발견해야 합니다. 일반적으로 Prometheus로 지표를 수집하고 Grafana로 시각화하여 대시보드를 구성합니다.
수많은 지표 중에서, 이것만큼은 놓치면 안 되는 필수 지표들입니다.
| 대상 | 지표 이름 | 설명 및 임계치 |
|---|---|---|
| 브로커 | UnderReplicatedPartitions |
(가장 중요) 복제가 부족한 파티션의 수. 이 값이 0보다 크면 데이터가 유실될 위험이 있는 심각한 상태입니다. 반드시 0이어야 합니다. |
ActiveControllerCount |
클러스터 전체의 리더 역할을 하는 컨트롤러 브로커의 수. 반드시 1이어야 합니다. 0이거나 2 이상이면 클러스터가 분열된 상태입니다. | |
NetworkProcessorAvgIdlePercent |
네트워크 스레드의 유휴 비율. 이 값이 0에 가까워지면 브로커가 네트워크 요청을 처리하느라 매우 바쁘다는 신호이며, 과부하의 전조 증상일 수 있습니다. | |
| 컨슈머 | records-lag-max |
(가장 중요) 컨슈머 그룹이 처리해야 할 메시지 중 가장 뒤처진 오프셋의 차이(랙). 이 값이 지속적으로 증가하면 소비 속도가 생산 속도를 따라가지 못하는 것이므로, 서비스 지연의 직접적인 원인이 됩니다. |
commit-latency-avg |
오프셋 커밋 요청의 평균 지연 시간. 이 값이 급증하면 오프셋을 저장하는 내부 토픽(__consumer_offsets)에 문제가 발생했을 수 있습니다. |
|
| 프로듀서 | record-error-rate |
메시지 전송 실패율. 이 값이 0보다 크면 브로커 장애, 네트워크 문제, 설정 오류 등을 의심해야 합니다. |
request-latency-avg |
프로듀서가 보낸 요청에 대한 평균 응답 시간. 이 값이 증가하면 브로커 또는 네트워크에 병목이 발생했을 가능성이 있습니다. |
모니터링 시스템에서 경고 알림이 왔을 때, 원인을 진단하고 해결하는 능력은 시니어 개발자의 핵심 역량입니다. 가장 흔하게 발생하는 두 가지 시나리오를 통해 대응 방법을 알아봅니다.
증상: Grafana 대시보드에서 records-lag-max 그래프가 우상향하며, 서비스 사용자들은 데이터 처리 지연을 호소합니다.
진단 순서:
record-send-rate)을 확인합니다. 특정 이벤트로 인해 일시적으로 메시지 양이 폭증한 것인지 파악합니다.처방:
max.poll.records 값을 줄여 1회 처리 부담을 낮춥니다.증상: 다른 파티션의 랙은 0인데, 유독 하나의 파티션만 랙이 줄어들지 않습니다. 컨슈머 로그를 확인하니 동일한 오프셋의 메시지를 계속해서 재처리하려다 실패하고 있습니다.
진단: '독이 든 메시지(Poison Pill)' 문제입니다. 메시지 형식이 깨졌거나(e.g., 잘못된 JSON), 특정 값으로 인해 컨슈머 로직에서 처리할 수 없는 예외가 발생하는 경우입니다.
처방: DLT (Dead Letter Topic) 패턴 구축
'독이 든 메시지' 하나 때문에 전체 파티션의 처리가 멈추는 것을 막기 위한 필수적인 패턴입니다. 처리할 수 없는 메시지는 별도의 '죽은 메시지 토픽(DLT)'으로 격리하여, 나머지 정상적인 메시지 처리를 계속 이어나가는 방식입니다.
구현 핵심:
try-catch 블록으로 감싸 예외를 잡습니다.KafkaProducer 인스턴스를 생성하고 설정합니다.catch 블록에서, 처리 실패한 원본 메시지와 함께 진단에 필요한 메타데이터(원본 토픽/파티션/오프셋, 에러 메시지, 발생 시간 등)를 헤더(Header)에 추가하여 DLT로 전송합니다.DLT는 단순히 메시지를 버리는 휴지통이 아니라, '응급 환자 격리실'과 같습니다. 격리된 환자는 다음과 같은 절차를 거쳐 처리됩니다.
이러한 전체 프로세스를 통해, '독이 든 메시지'로 인한 서비스 장애를 막는 동시에, 유실된 데이터 없이 모든 비즈니스 로직을 결국에는 처리할 수 있게 됩니다.
DLT 패턴을 더욱 견고하게 만들려면 몇 가지 추가적인 전략을 고려해야 합니다.
NullPointerException처럼 재시도가 무의미한 에러는 즉시 DLT로 보내고, 외부 API 호출 실패(e.g., HTTP 5xx)와 같이 일시적인 문제일 가능성이 높은 에러는 재시도 로직을 우선적으로 타도록 에러 종류에 따라 다르게 처리하는 것이 좋습니다.장애 대응뿐만 아니라, 평상시에 카프카의 성능을 최적화하는 것도 중요합니다. 몇 가지 핵심 파라미터 튜닝을 통해 시스템의 처리량과 안정성을 크게 향상시킬 수 있습니다.
linger.ms: 메시지를 보내기 전 대기하는 시간. 0(기본값)은 지연시간에 유리하지만, 5~100ms 정도로 설정하면 여러 메시지를 배치로 묶어 보내므로 처리량이 크게 향상됩니다.batch.size: 배치의 최대 크기. linger.ms와 함께 적절히 늘려주면(e.g., 32KB, 64KB) 네트워크 효율이 좋아집니다.compression.type: 메시지를 압축하여 전송합니다. (snappy, lz4, gzip 등) 네트워크 대역폭을 아끼고 브로커의 디스크 사용량을 줄여주므로, 대부분의 경우 사용하는 것이 이득입니다.fetch.min.bytes: 브로커로부터 한 번에 가져올 최소 데이터 크기. 이 값을 늘리면 컨슈머가 브로커에 요청하는 횟수가 줄어들어 효율적이지만, 지연시간은 약간 늘어날 수 있습니다.fetch.max.wait.ms: fetch.min.bytes를 만족하지 못하더라도, 이 시간이 지나면 브로커가 데이터를 반환하게 하는 최대 대기 시간입니다.