6. 카프카 운영과 트러블슈팅: 실무 핵심 지표와 장애 대응

1. 운영의 시작, 모니터링: "카프카 건강검진하기"

카프카 클러스터를 안정적으로 운영하기 위한 첫걸음은 시스템의 상태를 지속적으로 관찰하고 측정하는 모니터링입니다. "측정할 수 없으면 개선할 수 없다"는 말처럼, 주요 지표(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 프로듀서가 보낸 요청에 대한 평균 응답 시간. 이 값이 증가하면 브로커 또는 네트워크에 병목이 발생했을 가능성이 있습니다.

2. 실전 트러블슈팅: "응급 상황! 어떻게 대처할까?"

모니터링 시스템에서 경고 알림이 왔을 때, 원인을 진단하고 해결하는 능력은 시니어 개발자의 핵심 역량입니다. 가장 흔하게 발생하는 두 가지 시나리오를 통해 대응 방법을 알아봅니다.

상황 1: "컨슈머 랙(Lag)이 계속해서 증가합니다!"

증상: Grafana 대시보드에서 records-lag-max 그래프가 우상향하며, 서비스 사용자들은 데이터 처리 지연을 호소합니다.

진단 순서:

  1. 프로듀서 처리량 확인: 먼저 프로듀서의 메시지 발송량(record-send-rate)을 확인합니다. 특정 이벤트로 인해 일시적으로 메시지 양이 폭증한 것인지 파악합니다.
  2. 컨슈머 병목 분석: 컨슈머 애플리케이션의 CPU, Memory 사용률을 확인하고, 로그에서 에러나 긴 처리 시간을 유발하는 부분을 찾습니다. (e.g., 느린 DB 쿼리, 외부 API 호출 지연)
  3. 리밸런싱 확인: 컨슈머 로그에 "Rebalancing", "revoked", "assigned"와 같은 메시지가 반복적으로 나타나는지 확인합니다. 잦은 리밸런싱은 컨슈머 그룹의 처리를 중단시켜 랙을 유발합니다.

처방:

상황 2: "특정 파티션만 랙이 줄지 않고 멈춰있습니다!"

증상: 다른 파티션의 랙은 0인데, 유독 하나의 파티션만 랙이 줄어들지 않습니다. 컨슈머 로그를 확인하니 동일한 오프셋의 메시지를 계속해서 재처리하려다 실패하고 있습니다.

진단: '독이 든 메시지(Poison Pill)' 문제입니다. 메시지 형식이 깨졌거나(e.g., 잘못된 JSON), 특정 값으로 인해 컨슈머 로직에서 처리할 수 없는 예외가 발생하는 경우입니다.

처방: DLT (Dead Letter Topic) 패턴 구축

'독이 든 메시지' 하나 때문에 전체 파티션의 처리가 멈추는 것을 막기 위한 필수적인 패턴입니다. 처리할 수 없는 메시지는 별도의 '죽은 메시지 토픽(DLT)'으로 격리하여, 나머지 정상적인 메시지 처리를 계속 이어나가는 방식입니다.

컨슈머
1. 메시지 처리 시도
처리 실패! (Poison Pill)
컨슈머
2. 재시도 (N회)
계속 실패!
컨슈머
3. DLT로 메시지 전송
Dead Letter Topic
컨슈머
4. 원본 토픽 오프셋 커밋
카프카 브로커

구현 핵심:

  1. 에러 핸들링: 컨슈머의 메시지 처리 로직 전체를 try-catch 블록으로 감싸 예외를 잡습니다.
  2. DLT 프로듀서 생성: 컨슈머 코드 내에, DLT로 메시지를 보낼 수 있는 별도의 KafkaProducer 인스턴스를 생성하고 설정합니다.
  3. 메타데이터 추가 및 전송: catch 블록에서, 처리 실패한 원본 메시지와 함께 진단에 필요한 메타데이터(원본 토픽/파티션/오프셋, 에러 메시지, 발생 시간 등)를 헤더(Header)에 추가하여 DLT로 전송합니다.
  4. (가장 중요) 오프셋 커밋: DLT로 메시지를 성공적으로 보낸 후, 원본 토픽의 오프셋은 정상적으로 커밋하여 다음 메시지를 처리할 수 있도록 합니다.

DLT 메시지의 생명주기 (Lifecycle)

DLT는 단순히 메시지를 버리는 휴지통이 아니라, '응급 환자 격리실'과 같습니다. 격리된 환자는 다음과 같은 절차를 거쳐 처리됩니다.

  1. 1. 모니터링 및 알림: DLT 토픽에 메시지가 들어오면, 즉시 개발팀에 알림(e.g., 슬랙, 이메일)이 가도록 모니터링 시스템을 구축합니다.
  2. 2. 원인 분석: 개발자는 DLT에 쌓인 메시지의 내용과 헤더에 담긴 메타데이터를 확인하여, 어떤 데이터가 어떤 에러를 발생시켰는지 분석하고 근본적인 원인을 찾아 버그를 수정합니다.
  3. 3. 재처리(Reprocessing): 버그가 수정된 코드를 배포한 후, DLT에 격리되어 있던 메시지들을 다시 원본 토픽으로 재발행(re-produce)하여 누락되었던 비즈니스 로직을 마저 처리합니다. 이 과정은 별도의 스크립트나 배치 잡을 통해 수행될 수 있습니다.

이러한 전체 프로세스를 통해, '독이 든 메시지'로 인한 서비스 장애를 막는 동시에, 유실된 데이터 없이 모든 비즈니스 로직을 결국에는 처리할 수 있게 됩니다.

DLT 구현 시 실무 고려사항

DLT 패턴을 더욱 견고하게 만들려면 몇 가지 추가적인 전략을 고려해야 합니다.

  1. 재시도(Retry) 전략과 함께 사용하기: 일시적인 네트워크 오류나 DB 타임아웃과 같이 재시도하면 성공할 수 있는 에러들이 있습니다. 이런 경우, 한 번 실패했다고 바로 DLT로 보내기보다는, 3회 정도 재시도를 먼저 시도하고 그래도 실패할 경우에만 DLT로 보내는 것이 효율적입니다. 재시도 사이에 일정한 시간 간격(Backoff)을 두는 것도 중요합니다.
  2. 에러 종류에 따른 분기 처리: 모든 에러를 동일하게 취급해서는 안 됩니다. 메시지 포맷 오류(e.g., JSON 파싱 실패)나 NullPointerException처럼 재시도가 무의미한 에러는 즉시 DLT로 보내고, 외부 API 호출 실패(e.g., HTTP 5xx)와 같이 일시적인 문제일 가능성이 높은 에러는 재시도 로직을 우선적으로 타도록 에러 종류에 따라 다르게 처리하는 것이 좋습니다.
  3. DLT 토픽 자체에 대한 관리: DLT도 결국 카프카 토픽이므로 관리 정책이 필요합니다. DLT에 쌓인 메시지를 언제까지 보관할지(Retention Period), 파티션은 몇 개로 할지 등을 미리 정해두어야 합니다. 보통 "최대 14일 보관"과 같이 데이터의 중요도와 복구 필요성에 따라 정책을 수립합니다.

3. 성능 튜닝: 더 빠르고 안정적으로

장애 대응뿐만 아니라, 평상시에 카프카의 성능을 최적화하는 것도 중요합니다. 몇 가지 핵심 파라미터 튜닝을 통해 시스템의 처리량과 안정성을 크게 향상시킬 수 있습니다.

프로듀서 성능 튜닝

컨슈머 성능 튜닝