알림을 딱 5개만 걸어야 한다면

JMX 켜기

Apache Kafka는 원격 JMX를 기본적으로 비활성화합니다. CLI로 기동하는 프로세스는 JMX_PORT 환경변수로 켤 수 있습니다. 공식 문서는 원격 JMX를 켤 때 반드시 보안을 함께 설정하라고 명시합니다 — Kafka에서 JMX 인증은 기본적으로 꺼져 있습니다.

로컬 개발 — 인증 없이 (운영 금지)
export JMX_PORT=9999
bin/kafka-server-start.sh config/server.properties

# 확인
bin/kafka-run-class.sh kafka.tools.JmxTool \
  --jmx-url service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi \
  --object-name 'kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions'
운영 — 인증·TLS를 켜서
export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.password.file=/etc/kafka/jmxremote.password \
-Dcom.sun.management.jmxremote.access.file=/etc/kafka/jmxremote.access \
-Djava.rmi.server.hostname=$(hostname -f)"
export JMX_PORT=9999
bin/kafka-server-start.sh config/server.properties

브로커 메트릭

브로커 필수 JMX 메트릭 — 정상 범위는 Apache Kafka 4.3.1 Monitoring 문서의 "Normal value" 열
MBean 의미 정상 범위 이상 시 의미 조치
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions ISR 크기가 전체 레플리카 수보다 작은 파티션 수 0 복제가 뒤처졌거나 브로커가 죽었습니다. 아직 쓰기는 되지만 여유가 사라졌습니다 --describe --under-replicated-partitions로 어느 파티션인지 확인 → 특정 브로커에 몰려 있으면 그 브로커의 디스크·네트워크·GC를 봅니다. 전 브로커에 퍼져 있으면 num.replica.fetchers 상향
kafka.server:type=ReplicaManager,name=UnderMinIsrPartitionCount ISR이 min.insync.replicas 미달인 파티션 수 0 acks=all 쓰기가 거부됩니다. 프로듀서는 NotEnoughReplicasException을 받습니다 즉시 대응 대상. 죽은 브로커를 되살리거나, 정말 급하면 토픽의 min.insync.replicas를 한시적으로 낮춥니다(내구성을 포기하는 선택)
kafka.server:type=ReplicaManager,name=AtMinIsrPartitionCount ISR 크기가 정확히 min.insync.replicas인 파티션 수 0 한 대만 더 잃으면 쓰기가 멈춥니다. 조기 경보에 가장 유용한 메트릭 URP 대응과 같지만 여유 시간이 있는 상태입니다. 여기서 처리하세요
kafka.controller:type=KafkaController,name=OfflinePartitionsCount 리더가 없는 (내부 토픽 제외) 파티션 수 0 해당 파티션은 읽기·쓰기 모두 불가합니다. 최고 심각도 --describe --unavailable-partitions → 모든 레플리카가 오프라인인지 확인. 복구 불가면 --election-type unclean이 마지막 수단(유실 감수)
kafka.controller:type=KafkaController,name=ActiveControllerCount 이 노드가 활성 컨트롤러인지 (0 또는 1) 클러스터 전체에서 정확히 하나1 합계 0이면 컨트롤러가 없어 메타데이터 변경이 전부 실패합니다. 2 이상이면 split-brain 의심 kafka-metadata-quorum.sh describe --status로 쿼럼 상태 확인. 컨트롤러 노드의 디스크·네트워크 점검
kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent 요청 처리(I/O) 스레드가 유휴인 시간의 평균 비율 0~1, 이상적으로 > 0.3 0.3 아래면 I/O 스레드가 포화입니다. 요청이 큐에서 대기하며 지연이 늘어납니다 num.io.threads 상향(디스크 수 이상). 디스크 자체가 병목이면 스레드를 늘려도 개선되지 않습니다
kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent 네트워크 프로세서 스레드의 평균 유휴 비율 0~1, 이상적으로 > 0.3 0.3 아래면 네트워크 스레드가 포화입니다. TLS 핸드셰이크·압축 해제가 많을 때 자주 발생 num.network.threads 상향. 연결 수가 과도하면 클라이언트 쪽 커넥션 풀링도 검토
kafka.server:type=ReplicaManager,name=IsrShrinksPerSec ISR에서 레플리카가 빠지는 비율 브로커 장애가 없으면 0 지속적으로 튀면 복제가 replica.lag.time.max.ms 안에 따라오지 못합니다 IsrExpandsPerSec와 함께 보세요. 둘이 번갈아 튀면 "ISR 플래핑"입니다 → 복제 대역폭·GC·디스크 점검
kafka.server:type=ReplicaManager,name=IsrExpandsPerSec ISR에 레플리카가 복귀하는 비율 브로커 장애가 없으면 0 shrink와 짝을 이룹니다 위와 동일
kafka.server:type=ReplicaManager,name=FailedIsrUpdatesPerSec ISR 갱신 요청이 실패한 비율 0 컨트롤러와의 통신 또는 epoch 불일치 문제 컨트롤러 로그와 MetadataErrorCount를 함께 확인
kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec ISR 밖 레플리카가 리더로 승격된 비율 0 데이터 유실이 발생했습니다. 승격된 레플리카에 없는 레코드는 사라집니다 unclean.leader.election.enable=false인지 확인. true였다면 왜 켜져 있는지 추적
kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec ISR 밖 레플리카가 리더가 된 횟수 (데이터 유실 동반) 항상 0 0이 아니면 이미 유실이 일어난 것입니다. 리더 교체 빈도 자체는 IsrShrinksPerSec/IsrExpandsPerSec로 봅니다. LeaderElectionRateAndTimeMs는 KRaft에서 제거되었습니다 브로커 GC 로그와 broker.session.timeout.ms 확인
kafka.controller:type=KafkaController,name=PreferredReplicaImbalanceCount 리더가 선호 리더가 아닌 파티션 수 문서에 기준 없음 — 0에 가까워야 정상 리더가 일부 브로커에 몰려 부하가 불균형해집니다 kafka-leader-election.sh --election-type preferred --all-topic-partitions
kafka.controller:type=KafkaController,name=MetadataErrorCount 이 컨트롤러 노드가 메타데이터 로그 처리 중 만난 오류 횟수 문서에 기준 없음 — 0이어야 정상 메타데이터 로그가 손상되었거나 버전 불일치 컨트롤러 로그 확인. kafka-dump-log.sh --cluster-metadata-decoder로 로그 직접 검사
kafka.log:type=LogManager,name=OfflineLogDirectoryCount 오프라인 상태인 로그 디렉터리 수 0 디스크 장애입니다. 해당 디렉터리의 파티션이 오프라인이 됩니다 디스크 교체. JBOD면 나머지 디렉터리로 재할당. kafka-log-dirs.sh --describe로 영향 범위 확인
kafka.log:type=LogManager,name=LogDirectoryOffline 로그 디렉터리별 오프라인 여부 (1=오프라인) 0 위와 동일하되 디렉터리 단위로 식별할 수 있습니다 위와 동일
kafka.server:type=ReplicaManager,name=OfflineReplicaCount 오프라인 레플리카 수 0 디스크 장애 또는 로그 디렉터리 문제 위와 동일
kafka.server:type=ReplicaManager,name=PartitionCount 이 브로커가 호스팅하는 파티션 수 브로커 간에 대체로 균등 한 브로커에 몰려 있으면 그 브로커가 병목이 됩니다 kafka-reassign-partitions.sh로 재배치
kafka.server:type=ReplicaManager,name=LeaderCount 이 브로커가 리더인 파티션 수 브로커 간에 대체로 균등 리더 편중. 쓰기 부하가 한 브로커에 집중됩니다 preferred 리더 선거 실행. auto.leader.rebalance.enable 확인
kafka.server:type=ReplicaFetcherManager,name=MaxLag,clientId=Replica 팔로워와 리더 간 최대 메시지 lag produce 요청의 최대 배치 크기에 비례하는 정도 지속적으로 크면 복제가 따라오지 못합니다. URP의 선행 지표 num.replica.fetchers·replica.fetch.max.bytes 확인. 복제 throttle이 남아 있는지도 확인
kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec,topic=… 클라이언트로부터의 유입 바이트 비율 (토픽별) 문서에 기준 없음 — 용량 계획의 입력값 급증은 트래픽 폭주 또는 재처리, 급감은 프로듀서 장애 BytesOutPerSec·ReplicationBytesInPerSec와 함께 보세요
kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec,topic=… 클라이언트로 나가는 바이트 비율 (토픽별) 문서에 기준 없음 유입 대비 배수가 컨슈머 그룹 수를 반영합니다 디스크 읽기가 페이지 캐시에서 처리되는지 OS 지표와 대조
kafka.server:type=BrokerTopicMetrics,name=ReplicationBytesInPerSec 다른 브로커에서 들어오는 복제 트래픽 문서에 기준 없음 재할당·복구 중 급증합니다 throttle 없이 재할당을 돌리면 여기가 튑니다
kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec,topic=… 유입 메시지 비율 (토픽별) 문서에 기준 없음 바이트 대비 메시지 수가 급변하면 레코드 크기 분포가 바뀐 것
kafka.server:type=BrokerTopicMetrics,name=BytesRejectedPerSec,topic=… 레코드 배치가 max.message.bytes를 넘어 거부된 바이트 비율 문서 설명 기준 0이어야 정상 프로듀서가 RecordTooLargeException을 받고 있습니다 토픽 max.message.bytes, 프로듀서 max.request.size, 브로커 replica.fetch.max.bytes를 함께 올려야 합니다 (케이스 10)
kafka.server:type=BrokerTopicMetrics,name=FailedProduceRequestsPerSec,topic=… 실패한 produce 요청 비율 문서에 기준 없음 — 0이어야 정상 ISR 부족, 권한 오류, 레코드 크기 초과 등 ErrorsPerSecerror= 태그로 원인 코드를 확인하세요
kafka.server:type=BrokerTopicMetrics,name=FailedFetchRequestsPerSec,topic=… 실패한 fetch 요청 비율 문서에 기준 없음 — 0이어야 정상 오프셋 범위 초과, 권한 오류 등 컨슈머 쪽 OffsetOutOfRangeException과 대조
kafka.network:type=RequestMetrics,name=ErrorsPerSec,request=…,error=… 요청 타입별·에러 코드별 오류 수. error=NONE은 성공 문서 설명 기준 NONE 이외는 0이어야 정상 에러 코드가 원인을 그대로 알려 줍니다 가장 먼저 볼 진단 메트릭. 코드 의미는 트러블슈팅 치트시트 참조
kafka.network:type=RequestChannel,name=RequestQueueSize 요청 큐에 쌓인 요청 수 문서에 기준 없음 queued.max.requests에 근접하면 새 요청이 블록됩니다 RequestHandlerAvgIdlePercent와 함께 봐야 의미가 있습니다
kafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=Produce produce purgatory에서 대기 중인 요청 수 acks=-1(all)을 쓰면 0이 아닌 것이 정상 급증은 복제가 느려 acks=all 응답이 지연되는 것 복제 지연 대응과 동일
kafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=Fetch fetch purgatory에서 대기 중인 요청 수 컨슈머의 fetch.max.wait.ms에 따라 달라짐 정상적으로 커집니다. 오해하기 쉬운 메트릭 단독으로 알림을 걸지 마세요
kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMs 로그 flush 비율과 소요 시간 문서에 기준 없음 flush 시간이 길면 디스크가 병목입니다 디스크 교체 또는 파티션 분산. flush.messages를 임의로 낮추지 마세요
kafka.log:type=Log,name=Size,topic=…,partition=… 파티션의 디스크 크기(바이트) 문서에 기준 없음 특정 파티션만 크면 키 분포가 편향된 것 파티셔닝 전략 재검토 (케이스 4)
kafka.log:type=Log,name=RetentionSizeInPercent,topic=…,partition=… 파티션 크기를 retention.bytes 대비 비율로 표시 문서 설명 기준: retention.bytes 미설정 또는 Tiered Storage 사용 시 0을 반환 100%에 가까우면 오래된 데이터가 곧 삭제됩니다 재처리 여유가 필요하면 리텐션 상향
kafka.log:type=Log,name=NumLogSegments,topic=…,partition=… 파티션의 세그먼트 파일 수 문서에 기준 없음 과도하면 파일 디스크립터와 인덱스 메모리를 많이 씁니다 segment.bytes·segment.ms 조정
kafka.log:type=Log,name=LogEndOffset,topic=…,partition=… 파티션의 마지막 오프셋 (LEO) 단조 증가 멈춰 있으면 그 파티션에 쓰기가 없습니다 lag 계산의 분자. 컨슈머 커밋 오프셋과의 차이가 lag입니다
kafka.log:type=Log,name=LogStartOffset,topic=…,partition=… 파티션의 첫 오프셋 (리텐션 삭제 경계) 단조 증가 급증은 대량 삭제가 일어난 것 컨슈머 커밋 오프셋이 이보다 작으면 OffsetOutOfRangeException이 납니다
kafka.log:type=LogManager,name=remainingLogsToRecover 재시작 시 복구가 남은 로그 수 정상 기동 후 0 비정상 종료 후 복구가 진행 중입니다 num.recovery.threads.per.data.dir를 올리면 복구가 빨라집니다
kafka.server:type=ReplicaManager,name=ReassigningPartitions 이 브로커에서 재할당 중인 리더 파티션 수 재할당이 없으면 0 0이 아니면 재할당이 진행 중입니다 --verify로 완료를 확인하고 throttle을 제거하세요
kafka.server:type=BrokerTopicMetrics,name=ReassignmentBytesInPerSec 재할당 유입 트래픽 재할당이 없으면 0 재할당 진행 중에만 0이 아닙니다 throttle이 실제로 걸렸는지 확인하는 지표
kafka.server:type=AddPartitionsToTxnManager,name=VerificationFailureRate 트랜잭션 파티션 검증 실패 비율 정상 상태에서 0 (일시적 오류는 예상 범위) 지속되면 트랜잭션 코디네이터와의 통신 문제 VerificationTimeMs와 함께 확인
kafka.server:type=ReplicaManager,name=ProducerIdCount 이 브로커의 레플리카가 보유한 producer ID 수 문서에 기준 없음 계속 증가하면 트랜잭션·멱등 프로듀서가 ID를 계속 새로 만들고 있습니다 transactional.id가 인스턴스마다 안정적인지, producer.id.expiration.ms가 적절한지 확인
kafka.server:type=group-coordinator-metrics,name=partition-load-time-max 그룹 메타데이터·오프셋 로드 최대 소요 시간(ms) 문서에 기준 없음 크면 코디네이터 이동 후 그룹 복구가 오래 걸립니다 __consumer_offsets 파티션 크기와 컴팩션 상태 확인
kafka.server:type=transaction-coordinator-metrics,name=partition-load-time-max 트랜잭션 메타데이터 로드 최대 소요 시간(ms) 문서에 기준 없음 크면 트랜잭션 코디네이터 페일오버가 느립니다 __transaction_state 컴팩션 확인
kafka.server:type={Produce|Fetch},user=…,client-id=… 쿼터 메트릭. throttle-time·byte-rate 속성 throttle-time이상적으로 0 0이 아니면 클라이언트가 쿼터로 인해 지연되고 있습니다 클라이언트가 lag를 호소하는데 브로커가 한가하면 여기를 먼저 보세요
kafka.server:type=Request,user=…,client-id=… 요청 쿼터. request-time 백분율 throttle-time이상적으로 0 위와 동일 request_percentage 쿼터 확인

URP · AtMinIsr · UnderMinIsr 구분

이 세 지표는 이름이 비슷해 자주 혼동되지만 조건이 다르고, 무엇보다 "쓰기가 가능한지"의 경계가 다릅니다. 알림 심각도를 다르게 두어야 하는 이유입니다.

RF=3 · min.insync.replicas=2인 파티션을 예로 든 비교
지표 조건 RF=3·minISR=2에서 ISR이 acks=all 쓰기 읽기 심각도
UnderReplicatedPartitions ISR 크기 < 전체 레플리카 수 2 또는 1일 때 카운트 ISR ≥ 2면 가능 가능 경고 — 롤링 재시작 중에는 정상. 지속될 때만 의미가 있습니다
AtMinIsrPartitionCount ISR 크기 = min.insync.replicas 정확히 2일 때 카운트 아직 가능 가능 경고 — 한 대만 더 잃으면 쓰기가 멈춥니다. 조기 경보로 가장 유용
UnderMinIsrPartitionCount ISR 크기 < min.insync.replicas 1 또는 0일 때 카운트 불가 — NotEnoughReplicasException 가능 심각 — 이미 쓰기가 실패하고 있습니다
OfflinePartitionsCount 리더가 없음 (내부 토픽 제외) ISR이 0이거나 모든 레플리카 오프라인 불가 불가 최고 심각도 — 읽기·쓰기 모두 불가

KRaft 컨트롤러 메트릭

ZooKeeper가 없는 4.x에서는 클러스터 메타데이터가 __cluster_metadata 토픽에 있고, 컨트롤러 쿼럼이 그것을 Raft로 복제합니다. 배경은 3장 KRaft와 클러스터 메타데이터에 있습니다.

KRaft 컨트롤러 메트릭 (10개)
MBean 의미 정상 범위 조치
kafka.controller:type=KafkaController,name=ActiveControllerCount 이 노드의 활성 컨트롤러 수. 유효값은 0 또는 1 클러스터 합계 1 합계가 1이 아니면 최우선 대응
kafka.controller:type=KafkaController,name=ActiveBrokerCount 이 컨트롤러가 보는 활성 브로커 수 실제 브로커 수와 일치 부족하면 어느 브로커가 fenced인지 확인
kafka.controller:type=KafkaController,name=FencedBrokerCount fenced 상태인 브로커 수 0 브로커가 하트비트를 못 보내고 있습니다. broker.session.timeout.ms·네트워크 확인
kafka.controller:type=KafkaController,name=GlobalTopicCount 전체 토픽 수 기준 없음 급증은 auto.create.topics.enable로 오타 토픽이 생기는 신호일 수 있습니다
kafka.controller:type=KafkaController,name=GlobalPartitionCount 전체 파티션 수 기준 없음 파티션 총수는 컨트롤러 메모리·페일오버 시간에 직접 영향 (케이스 5)
kafka.controller:type=KafkaController,name=OfflinePartitionsCount 리더가 없는 (내부 토픽 제외) 파티션 수 0 최고 심각도 알림
kafka.controller:type=KafkaController,name=PreferredReplicaImbalanceCount 리더가 선호 리더가 아닌 파티션 수 0에 가까워야 정상 preferred 리더 선거
kafka.controller:type=KafkaController,name=MetadataErrorCount 메타데이터 로그 처리 중 발생한 오류 횟수 0이어야 정상 컨트롤러 로그 확인
kafka.controller:type=ControllerEventManager,name=EventQueueTimeMs 컨트롤러 이벤트 큐 대기 시간 히스토그램 기준 없음 커지면 컨트롤러가 밀리고 있습니다 → 메타데이터 변경(토픽 생성 등)이 느려집니다
kafka.controller:type=ControllerEventManager,name=EventQueueProcessingTimeMs 컨트롤러 이벤트 처리 시간 히스토그램 기준 없음 위와 함께 봅니다. 처리 시간이 길면 파티션 총수를 줄이는 것을 검토

요청 지연 분해

"produce가 느리다"는 신고를 받았을 때 어디서 느린지를 가르는 표입니다. 모두 request={Produce|FetchConsumer|FetchFollower} 태그를 가집니다. TotalTimeMs는 아래 네 구간의 합으로 분해됩니다.

요청 지연 구간별 메트릭 (6개) — kafka.network:type=RequestMetrics
MBean name= 이 구간이 크면 조치
TotalTimeMs 전체 지연. 아래 구간의 합입니다 먼저 이 값을 보고 어느 구간이 지배적인지 내려가세요
RequestQueueTimeMs 요청이 큐에서 기다렸습니다 — I/O 스레드 부족 num.io.threads 상향. RequestHandlerAvgIdlePercent 확인
LocalTimeMs 리더에서의 처리 시간 — 디스크 또는 CPU 디스크 I/O 지표 확인. 압축 해제·포맷 변환도 여기 포함됩니다
RemoteTimeMs 팔로워를 기다린 시간. acks=all이면 0이 아닌 것이 정상 급증은 복제 지연입니다. MaxLag·URP 확인
ResponseQueueTimeMs 응답이 큐에서 기다렸습니다 — 네트워크 스레드 부족 num.network.threads 상향
ResponseSendTimeMs 응답 전송 시간 — 클라이언트 네트워크 또는 대역폭 클라이언트 쪽 네트워크와 쿼터 throttle-time 확인

프로듀서 클라이언트 메트릭

전부 kafka.producer:type=producer-metrics,client-id="{client-id}" 아래에 있습니다 (토픽별 메트릭은 type=producer-topic-metrics). client.id를 설정하지 않으면 식별이 사실상 불가능하므로 반드시 지정하세요.

프로듀서 메트릭 (17개)
Attribute 의미 이상 시 의미 조치
record-send-rate초당 전송 레코드 수기대보다 낮으면 애플리케이션이 send()를 덜 호출하거나 버퍼가 막혀 있습니다buffer-available-bytes·record-queue-time-avg 확인
record-error-rate오류로 끝난 전송 비율0이 아니면 유실 가능성입니다. 콜백에서 예외를 삼키고 있지 않은지 확인브로커 ErrorsPerSec의 에러 코드와 대조
record-retry-rate재시도된 전송 비율지속적으로 높으면 리더 이동·ISR 부족·타임아웃이 반복되고 있습니다URP·리더 선거 메트릭 확인
batch-size-avg요청당 파티션별 평균 전송 바이트batch.size보다 훨씬 작으면 배치가 차기 전에 전송되고 있습니다linger.ms를 올려야 배치가 커집니다. batch.size만 올려도 효과 없음
batch-size-max요청당 최대 전송 바이트
batch-split-rate배치가 분할된 비율0이 아니면 배치가 브로커의 max.message.bytes를 넘어 나뉘고 있습니다batch.size를 브로커 한계 아래로 조정
records-per-request-avg요청당 평균 레코드 수1에 가까우면 배치가 전혀 되지 않고 있습니다linger.ms·batch.size
compression-rate-avg압축 후/전 크기 비율 (작을수록 잘 압축됨)1에 가까우면 압축 효과가 없습니다 (이미 압축된 데이터 등)compression.type=none으로 CPU를 아끼는 편이 나을 수 있습니다
record-queue-time-avg레코드가 전송 버퍼에서 대기한 평균 시간(ms)linger.ms보다 훨씬 크면 Sender 스레드가 밀리고 있습니다in-flight 한계·브로커 지연 확인
record-queue-time-max같은 값의 최대치꼬리 지연을 볼 때 사용
request-latency-avg요청 왕복 지연 평균(ms)브로커 TotalTimeMs보다 훨씬 크면 네트워크 구간 문제두 값을 비교하는 것이 요점입니다
request-latency-max요청 왕복 지연 최대치
requests-in-flight응답을 기다리는 요청 수max.in.flight.requests.per.connection에 붙어 있으면 브로커 응답이 병목멱등성이 켜져 있으면 이 값은 5를 넘을 수 없습니다
produce-throttle-time-avg브로커 쿼터로 지연된 평균 시간(ms)0이 아니면 쿼터에 걸려 있습니다producer_byte_rate 쿼터 확인
produce-throttle-time-max같은 값의 최대치
record-size-avg평균 레코드 크기급변은 페이로드 구조 변경 신호용량 계획에 반영
metadata-age현재 사용 중인 메타데이터의 나이(초)metadata.max.age.ms보다 훨씬 크면 메타데이터 갱신이 실패하고 있습니다브로커 도달성·advertised.listeners 확인

컨슈머 클라이언트 메트릭

컨슈머 메트릭 (13개) — kafka.consumer:type=consumer-fetch-manager-metrics
Attribute 의미 이상 시 의미 조치
records-lag-max 이 윈도 안 모든 파티션 중 최대 lag. 커밋 오프셋이 아니라 현재 위치 기준 증가 추세면 소비가 생산을 따라가지 못합니다 절대값이 아니라 기울기로 알림을 거세요. 이 메트릭은 컨슈머가 발행하므로 컨슈머가 죽으면 사라집니다
records-lag 파티션별 파티션별 최신 lag (partition=·topic= 태그) 특정 파티션만 크면 그 파티션에 핫키가 있거나 처리가 느립니다 파티션 단위로 좁혀 들어갈 때 사용
records-lag-avg 파티션별 파티션별 평균 lag 순간값보다 추세를 보기 좋습니다
records-lead-min lead = 현재 위치 − log-start-offset. 최소값 0에 가까워지면 리텐션 삭제에 잡아먹히기 직전입니다 lag만 보면 놓치는 위험입니다. OffsetOutOfRangeException의 선행 지표
records-consumed-rate 초당 소비 레코드 수 0으로 떨어지면 poll 루프가 멈췄거나 리밸런스 중입니다 애플리케이션 스레드 덤프 확인
bytes-consumed-rate 초당 소비 바이트 레코드 수와 함께 보면 평균 크기 변화를 알 수 있습니다
fetch-rate 초당 fetch 요청 수 매우 높으면 fetch.min.bytes가 너무 작습니다 브로커 부하를 줄이려면 fetch.min.bytes 상향
fetch-latency-avg fetch 요청 평균 소요 시간(ms) fetch.max.wait.ms에 붙어 있으면 데이터가 부족해 기다린 것 (정상) 오해하기 쉬운 메트릭. 단독으로 판단하지 마세요
fetch-latency-max fetch 최대 소요 시간
fetch-size-avg 요청당 평균 fetch 바이트 max.partition.fetch.bytes에 붙어 있으면 상한에 걸려 있습니다 큰 레코드를 다루면 상향 검토
records-per-request-avg 요청당 평균 레코드 수 max.poll.records와 비교
fetch-throttle-time-avg 쿼터로 지연된 평균 시간(ms) 0이 아니면 consumer_byte_rate 쿼터에 걸려 있습니다 "컨슈머가 느린데 브로커는 한가하다"의 흔한 원인
preferred-read-replica 파티션별 현재 읽고 있는 레플리카. -1이면 리더에서 읽는 중 랙 인식 읽기를 켰는데 계속 -1이면 설정이 적용되지 않았습니다 client.rack과 브로커 replica.selector.class 확인

Consumer lag 측정 3가지 방법

lag 측정 방법 비교 — 세 방법의 "lag"은 서로 다른 것을 셉니다
방법 무엇을 재는가 장점 함정
kafka-consumer-groups.sh --describe LEO − 커밋된 오프셋 컨슈머가 죽어도 측정됩니다. 그룹 전체를 한눈에 봅니다. 브로커만 있으면 됩니다 커밋 주기만큼 늦게 보입니다(자동 커밋 기본 5초). 커밋이 만료되면 -로 나옵니다. 폴링 비용이 있어 초당 수집은 부적합
② 컨슈머 JMX records-lag-max LEO − 컨슈머의 현재 위치 (커밋 아님) 실시간이고 오버헤드가 거의 없습니다. 처리 지연을 가장 정확히 반영합니다 컨슈머가 죽으면 메트릭이 사라집니다. 파티션을 할당받지 못한 컨슈머는 lag을 보고하지 않습니다. "값 없음"을 "정상"으로 오독하기 쉽습니다
③ 외부 exporter (예: Kafka Lag Exporter류) __consumer_offsets를 읽거나 Admin API로 조회해 LEO와 비교. 시간 단위 lag 추정도 제공 컨슈머 생존과 무관합니다. "몇 초 뒤처졌는가"를 시간으로 환산할 수 있어 SLO에 쓰기 좋습니다 별도 컴포넌트가 늘어납니다. Apache Kafka 본체 기능이 아니라 도구 선택·운영 책임이 생깁니다
①로 lag 큰 파티션만 뽑기
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group my-group \
  | awk 'NR>1 && $6 ~ /^[0-9]+$/ { print $6, $1"/"$2"/"$3 }' \
  | sort -rn | head -20

# 출력 예: 사람 눈으로 편중을 바로 확인할 수 있습니다
# 184320 orders/orders/7
# 152011 orders/orders/3
#      0 orders/orders/0

PromQL 알림 룰

JMX Exporter로 수집한다고 가정한 예시입니다. 메트릭 이름은 exporter 설정에 따라 달라지므로 아래 이름은 자기 환경에 맞게 바꿔야 합니다. 여기서 봐야 할 것은 조건과 for입니다.

alerts.yml — 최소 5개
groups:
  - name: kafka-critical
    rules:
      # 1) 리더 없는 파티션 — 읽기·쓰기 모두 불가. for 를 짧게 둡니다.
      - alert: KafkaOfflinePartitions
        expr: sum(kafka_controller_kafkacontroller_offlinepartitionscount) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "오프라인 파티션 {{ $value }}개 — 읽기·쓰기 불가"
          runbook: "kafka-topics.sh --describe --unavailable-partitions"

      # 2) 활성 컨트롤러가 정확히 하나가 아님 (0 = 없음, 2+ = split-brain 의심)
      - alert: KafkaControllerCountAbnormal
        expr: sum(kafka_controller_kafkacontroller_activecontrollercount) != 1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "활성 컨트롤러 수 = {{ $value }} (정상은 1)"
          runbook: "kafka-metadata-quorum.sh describe --status"

      # 3) min.insync.replicas 미달 — acks=all 쓰기가 이미 실패하고 있습니다
      - alert: KafkaUnderMinIsr
        expr: sum(kafka_server_replicamanager_underminisrpartitioncount) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "min.insync.replicas 미달 파티션 {{ $value }}개 — acks=all 쓰기 거부"

  - name: kafka-warning
    rules:
      # 4) URP 지속. 순간적인 URP는 정상이므로 for 를 길게 둡니다.
      - alert: KafkaUnderReplicatedPartitions
        expr: sum(kafka_server_replicamanager_underreplicatedpartitions) > 0
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "URP {{ $value }}개가 15분 이상 지속"

      # 5) 요청 처리 스레드 포화 — 공식 문서 권장 하한이 0.3 입니다
      - alert: KafkaRequestHandlerSaturated
        expr: kafka_server_kafkarequesthandlerpool_requesthandleravgidlepercent < 0.3
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} I/O 스레드 유휴율 {{ $value }} (권장 > 0.3)"

      # 6) unclean 리더 선거 = 데이터 유실. 발생 자체가 사건입니다.
      - alert: KafkaUncleanLeaderElection
        expr: increase(kafka_controller_controllerstats_uncleanleaderelectionspersec_total[10m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "unclean 리더 선거 발생 — 데이터 유실 가능"

      # 7) Consumer lag 은 절대값이 아니라 기울기로 봅니다.
      #    5분간 계속 증가하면 소비가 생산을 못 따라가고 있습니다.
      - alert: KafkaConsumerLagGrowing
        expr: deriv(kafka_consumer_fetch_manager_records_lag_max[10m]) > 0
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.client_id }} lag 이 15분간 증가 추세"

시험 포인트

공식 문서 출처

MBean 이름과 "정상 범위" 열은 Apache Kafka 4.3.1 공식 문서에서 그대로 확인했습니다. 문서에 정상 범위가 명시되지 않은 메트릭은 표에 그렇게 적었고, 임의의 임계값을 넣지 않았습니다. PromQL 예시의 메트릭 이름은 exporter 설정에 따라 달라지는 부분이므로 공식 문서의 대상이 아닙니다.