빠른참조 · 메트릭
메트릭 치트시트
MBean 이름 · 의미 · 정상 범위 · 이상 시 의미 · 조치를 한 표에 담았습니다. "정상 범위" 열은 공식 문서가 명시한 값만 적었고, 문서에 기준이 없는 메트릭은 그렇게 표시했습니다 — 이런 메트릭은 임계값을 남의 숫자로 정할 수 없고 자기 클러스터의 베이스라인으로 정해야 합니다.
알림을 딱 5개만 걸어야 한다면
OfflinePartitionsCount> 0 — 지금 데이터 유실 중일 수 있습니다. 최우선.ActiveControllerCount합계 ≠ 1 — 컨트롤러가 없거나 둘입니다.UnderMinIsrPartitionCount> 0 —acks=all쓰기가 막혀 있습니다.UnderReplicatedPartitions가 지속 > 0 — 복제가 따라오지 못합니다.- Consumer lag의 증가 추세 — 절대값보다 기울기가 중요합니다.
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'
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
브로커 메트릭
| 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 부족, 권한 오류, 레코드 크기 초과 등 | ErrorsPerSec의 error= 태그로 원인 코드를 확인하세요 |
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·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와 클러스터 메타데이터에 있습니다.
| 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는 아래 네 구간의 합으로 분해됩니다.
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를 설정하지 않으면 식별이 사실상 불가능하므로 반드시 지정하세요.
| 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 확인 |
컨슈머 클라이언트 메트릭
| 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가지 방법
| 방법 | 무엇을 재는가 | 장점 | 함정 |
|---|---|---|---|
① 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 본체 기능이 아니라 도구 선택·운영 책임이 생깁니다 |
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 값입니다.
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 설정에 따라 달라지는 부분이므로 공식 문서의 대상이 아닙니다.
- Monitoring — 브로커 메트릭 표 전체, JMX 보안 주의사항, rate/total 규칙
- KRaft Monitoring — 컨트롤러 메트릭
- Producer Monitoring —
producer-metrics·producer-topic-metrics - Consumer Fetch Metrics —
records-lag-max·records-lead-min·preferred-read-replica - Quotas —
throttle-time해석 - Apache Kafka 4.3.1 배포 아카이브 —
kafka_2.13-4.3.1-site-docs.tgz