Kafka 파이프라인에서 높은 소비자 지연 시간 문제 해결

Apache Kafka 파이프라인에서 높은 소비자 지연 시간을 진단하고 해결합니다. 이 실용적인 가이드는 소비자 지연이 발생하는 방식과 가져오기 타이밍(`fetch.min.bytes`, `fetch.max.wait.ms`), 배치 크기(`max.poll.records`), 오프셋 커밋 전략과 같은 Kafka 소비자 속성에 대한 실행 가능한 구성 조정을 제공합니다. 낮은 지연 시간의 실시간 이벤트 처리를 유지하기 위해 소비자 병렬 처리를 효과적으로 확장하는 방법을 알아보세요.

Kafka 파이프라인에서 높은 소비자 지연 시간 문제 해결

높은 소비자 지연 시간은 애플리케이션이 레코드를 사용하기 전에 Kafka에 레코드가 이미 존재함을 의미합니다. 이러한 지연은 소비자 지연, 오래된 대시보드, 지연된 알림 또는 예상 기간을 놓친 다운스트림 작업으로 나타날 수 있습니다. 불편한 점은 Kafka가 정상이지만 파이프라인이 여전히 느릴 수 있다는 것입니다. 소비자가 데이터베이스를 기다리거나, 폴링당 너무 많은 작업을 수행하거나, 오프셋을 너무 자주 커밋하거나, 긴 처리 일시 중지로 인한 리밸런스와 씨름하고 있을 수 있습니다.

이 가이드는 대부분의 지연 시간 인시던트가 가시화되는 소비자 측을 먼저 살펴봅니다. 목표는 설정을 변경하기 전에 느린 세그먼트를 찾는 것입니다.

소비자 지연 및 대기 시간 이해

소비자 지연은 지연 시간 문제를 나타내는 주요 지표입니다. 이는 파티션에 생성된 최신 오프셋과 소비자 그룹이 성공적으로 읽고 커밋한 오프셋 간의 차이를 나타냅니다. 지연이 높다는 것은 소비자가 뒤처지고 있음을 의미합니다.

모니터링할 주요 지표:

  • 소비자 지연: 파티션당 읽지 않은 총 메시지 수입니다.
  • 가져오기 속도 대 생성 속도: 소비자 가져오기 속도가 지속적으로 생산자 속도를 따라가지 못하면 지연이 증가합니다.
  • 커밋 지연 시간: 소비자가 진행 상황을 체크포인트하는 데 걸리는 시간입니다.

1단계: 소비자 가져오기 동작 분석

높은 지연 시간의 가장 일반적인 원인은 비효율적인 데이터 검색입니다. 소비자는 브로커에서 데이터를 가져와야 하며, 구성이 최적이 아닌 경우 너무 오래 기다리거나 너무 적은 데이터를 가져오는 데 시간을 소비할 수 있습니다.

fetch.min.bytesfetch.max.wait.ms 조정

이 두 설정은 가져오기를 요청하기 전에 소비자가 데이터가 축적되기를 기다리는 시간에 직접적인 영향을 미쳐 지연 시간과 처리량의 균형을 맞춥니다.

  • fetch.min.bytes: 브로커가 반환해야 하는 최소 데이터 양(바이트)입니다. 값이 클수록 일괄 처리가 권장되어 처리량이 증가하지만 필요한 크기를 즉시 사용할 수 없는 경우 지연 시간이 약간 증가할 수 있습니다.
    • 모범 사례: 높은 처리량, 낮은 지연 시간 파이프라인의 경우 즉시 반환을 보장하기 위해 이 값을 상대적으로 낮게(예: 1바이트) 유지하거나 처리량 병목 현상이 관찰되면 높일 수 있습니다.
  • fetch.max.wait.ms: 브로커가 응답하기 전에 fetch.min.bytes를 축적하기 위해 기다리는 시간입니다. 대기 시간이 길수록 배치 크기가 최대화되지만 필요한 볼륨이 없는 경우 지연 시간이 직접적으로 추가됩니다.
    • 절충: 이 시간을 줄이면(예: 기본값 500ms에서 50ms로) 지연 시간이 크게 낮아지지만 가져오기 효율성이 떨어지는 더 작은 배치가 발생할 수 있습니다.

max.poll.records 조정

이 설정은 단일 Consumer.poll() 호출에서 반환되는 레코드 수를 제어합니다.

max.poll.records=500 

max.poll.records가 너무 낮게 설정되면 소비자는 상당한 양의 데이터를 처리하지 않고 poll() 호출을 반복하는 데 과도한 시간을 소비하여 오버헤드가 증가합니다. 너무 높으면 큰 배치를 처리하는 데 세션 시간 제한보다 오래 걸려 불필요한 리밸런스가 발생할 수 있습니다.

실행 가능한 팁: 100에서 500 사이의 적당한 값으로 시작하고 각 폴링의 실제 처리 시간을 관찰하십시오. 추측으로 이 값을 조정하지 마십시오. 각 레코드가 느린 API에 쓰기 때문에 500개의 레코드 배치를 처리하는 데 4분이 걸리는 경우 max.poll.records를 늘리면 소비자가 더 빨라지지 않고 불안정해집니다.

2단계: 처리 시간 및 커밋 조사

데이터가 빠르게 가져와지더라도 가져온 배치를 처리하는 데 소요되는 시간이 가져오기 사이의 시간을 초과하면 높은 지연 시간이 발생합니다.

처리 로직의 병목 현상

소비자 애플리케이션 로직에 소비 루프 내에서 병렬화되지 않은 무거운 외부 호출(예: 데이터베이스 쓰기, API 조회)이 포함된 경우 처리 시간이 급증합니다.

문제 해결 단계:

  1. 처리 시간 측정: 배치를 수신하고 오프셋을 커밋하기 전에 모든 다운스트림 작업을 완료하는 사이의 경과 시간을 추적하는 메트릭을 사용합니다.
  2. 병렬화: 처리가 느린 경우 소비자 애플리케이션 내에서 내부 스레드 풀을 사용하여 레코드를 폴링한 후 오프셋을 커밋하기 전에 동시에 처리하는 것을 고려하십시오.

커밋 전략 검토

오프셋 커밋이 너무 자주 발생하면 각 커밋에 Kafka와의 조정이 필요하므로 지연 시간이 발생할 수 있습니다. 그러나 더 큰 위험은 일반적으로 정확성입니다. 너무 일찍 커밋하면 충돌 후 작업이 손실될 수 있습니다. 너무 늦게 커밋하면 충돌 후 작업이 다시 실행될 수 있습니다.

  • enable.auto.commit: 간단한 리더, 실험 및 중요하지 않은 파이프라인에 적합합니다. 데이터베이스를 업데이트하거나, API를 호출하거나, 파생 이벤트를 게시하는 프로덕션 소비자의 경우 수동 커밋이 일반적으로 이해하기 더 쉽습니다.
  • auto.commit.interval.ms: 오프셋이 커밋되는 빈도를 지정합니다(기본값은 5초).

처리가 빠르고 안정적인 경우 더 긴 간격(예: 10-30초)은 커밋 오버헤드를 줄입니다. 그러나 애플리케이션이 자주 충돌하는 경우 더 짧은 간격은 진행 중인 작업을 더 많이 보존하지만 네트워크 트래픽과 잠재적 지연 시간이 증가합니다.

수동 커밋에 대한 경고: 수동 커밋(enable.auto.commit=false)을 사용하는 경우 commitSync()를 드물게 사용해야 합니다. commitSync()는 커밋이 확인될 때까지 소비자 스레드를 차단하므로 모든 단일 메시지 또는 소규모 배치 후에 호출하면 지연 시간에 심각한 영향을 미칩니다.

3단계: 확장 및 리소스 할당

구성이 최적화된 것으로 보이면 근본적인 문제는 불충분한 병렬 처리 또는 리소스 포화 상태일 수 있습니다.

소비자 스레드 확장

Kafka 소비자는 소비하는 파티션 수까지 그룹 내 소비자 인스턴스 수를 늘려 확장합니다. 20개의 파티션과 5개의 소비자 인스턴스가 있는 경우 Kafka는 일반적으로 각 소비자에게 여러 파티션을 할당합니다. 이는 완전히 정상일 수 있습니다. 한계는 하나의 소비자 그룹에 있는 하나의 파티션이 한 번에 하나의 소비자에 의해서만 처리된다는 것입니다. 따라서 단일 핫 파티션은 그룹 구성원을 더 추가한다고 해결되지 않습니다.

경험 법칙: 소비자 인스턴스 수는 일반적으로 구독하는 모든 주제의 파티션 수를 초과해서는 안 됩니다. 인스턴스가 파티션보다 많으면 유휴 스레드가 발생합니다.

브로커 및 네트워크 상태

지연 시간은 소비자 코드 외부에서 발생할 수 있습니다.

  1. 브로커 CPU/메모리: 브로커에 과부하가 걸리면 가져오기 요청에 대한 응답 시간이 증가하여 소비자 시간 초과 및 지연이 발생합니다.
  2. 네트워크 포화: 소비자와 브로커 간의 높은 네트워크 트래픽은 특히 큰 배치를 가져올 때 TCP 전송 속도를 저하시킬 수 있습니다.

모니터링 도구를 사용하여 지연 시간이 높은 기간 동안 브로커 CPU 사용률과 네트워크 I/O를 확인하십시오.

지연의 형태 읽기

지연의 형태는 어디를 봐야 하는지 알려줍니다. 단일 파티션의 지연은 일반적으로 문제가 좁다는 것을 의미합니다. 키가 한 파티션으로 너무 많은 트래픽을 라우팅할 수 있습니다. 하나의 레코드가 느린 코드 경로를 트리거할 수 있습니다. 해당 파티션 할당을 실행하는 호스트가 비정상일 수 있습니다. 이러한 상황에서 소비자를 더 추가해도 Kafka는 동일한 그룹의 여러 소비자에게 해당 파티션 하나를 분할할 수 없기 때문에 아무런 효과가 없을 수 있습니다.

모든 파티션에 걸친 균일한 지연은 공유된 한계를 가리킵니다. 서비스에 더 많은 인스턴스가 필요하거나, 다운스트림 데이터베이스가 포화 상태이거나, 브로커가 가져오기 제공 속도가 느릴 수 있습니다. 매 시간 같은 시간에 지연이 급증하는 경우 예약된 작업, 배치 생산자, 압축 압력, 백업 또는 자동 확장 이벤트를 찾아보십시오. Kafka 지연 시간은 종종 Kafka 외부의 무언가의 부작용입니다.

또한 "레코드 뒤처짐"과 "시간 뒤처짐"을 구분하십시오. 작은 이벤트가 있는 주제는 무서운 레코드 수를 보여줄 수 있지만 몇 초 만에 따라잡을 수 있습니다. 큰 레코드 또는 값비싼 처리가 있는 주제는 더 작은 지연 수를 보여줄 수 있지만 몇 분의 비즈니스 지연을 나타냅니다. 모니터링 스택이 레코드 타임스탬프에서 지연 시간을 추정할 수 있는 경우 오프셋 지연 옆에 그래프로 표시하십시오. 그렇지 않은 경우 임시 그룹에서 kafka-console-consumer.sh로 몇 개의 레코드를 샘플링하고 이벤트 타임스탬프를 벽시계 시간과 비교하십시오.

역효과를 내는 일반적인 수정

첫 번째 잘못된 수정은 리밸런스가 중지될 때까지 max.poll.interval.ms를 높이는 것입니다. 이는 처리가 자연스럽게 긴 경우 유효할 수 있지만 중단된 소비자를 더 오래 숨길 수도 있습니다. 소비자가 20분 동안 다운스트림 호출에 멈춰 있는 경우 더 큰 간격은 복구를 지연시킵니다.

두 번째 잘못된 수정은 키잉 모델을 확인하지 않고 인시던트 중에 파티션을 늘리는 것입니다. 더 많은 파티션은 미래의 병렬 처리를 개선할 수 있지만 새 레코드에 대한 파티션 할당을 변경하고 순서 가정에 영향을 줄 수 있습니다. 또한 기존 파티션에 이미 있는 레코드를 분할하지 않습니다.

세 번째 잘못된 수정은 대시보드를 녹색으로 만들기 위해 --to-latest 오프셋 재설정으로 전환하는 것입니다. 이는 작업을 건너뜁니다. 비즈니스에서 이를 수용하는 경우도 있습니다(예: 중단 중 일회성 분석 이벤트). 청구, 이행, 보안 알림 또는 사용자에게 표시되는 상태 변경의 경우 지연된 레코드를 건너뛰면 지연 시간 자체보다 훨씬 더 큰 인시던트가 발생할 수 있습니다.

소비자 확장이 도움이 되는 경우

확장은 그룹에 활성 소비자보다 더 많은 파티션이 있고 작업이 해당 파티션에 합리적으로 균형을 이루고 있을 때 도움이 됩니다. 주제에 24개의 파티션과 6개의 소비자가 있는 경우 12개의 소비자로 이동하면 각 인스턴스가 더 적은 수의 파티션을 처리하므로 지연 시간이 줄어들 수 있습니다. 24명의 소비자에서 40명의 소비자로 이동하면 동일한 그룹에 도움이 되지 않습니다. 할당할 파티션이 24개뿐이므로 추가 소비자는 유휴 상태가 됩니다.

모든 소비자가 동일한 포화 종속성을 기다리고 있을 때는 확장이 별로 도움이 되지 않습니다. 모든 소비자가 이미 잠금 바운드인 하나의 데이터베이스 테이블에 쓰는 경우 소비자를 더 추가하면 경합이 증가하고 지연 시간이 악화될 수 있습니다. 이러한 경우 쓰기 일괄 처리, 인덱스 변경, 백프레셔 추가 또는 핫 워크로드 분리가 Kafka 설정보다 더 중요할 수 있습니다.

확장하는 동안 리밸런스를 주시하십시오. 소비자를 너무 공격적으로 시작 및 중지하는 롤링 배포는 최종 복제본 수가 정확하더라도 지연 시간 급증을 만들 수 있습니다. group.instance.id를 사용한 정적 멤버십은 일부 장기 실행 서비스에서 불필요한 파티션 이동을 줄일 수 있지만 신중한 인스턴스 ID 관리가 필요합니다. 또한 협력적 리밸런싱은 클라이언트 및 할당자 구성에 따라 즉시 리밸런싱에 비해 중단을 줄일 수 있습니다.

지연 시간이 실제로 보존 위험인 경우

지연이 주제 보존 기간에 근접하면 높은 지연 시간이 시급해집니다. Kafka는 모든 소비자가 읽었는지 여부가 아니라 보존 정책에 따라 이전 세그먼트를 제거합니다. 소비자가 7일 동안 데이터를 유지하는 주제에서 6시간 뒤쳐진 경우 애플리케이션을 복구할 시간이 있습니다. 동일한 주제에서 6일 뒤쳐진 경우 가장 오래된 읽지 않은 레코드가 만료되기 전에 복구 계획이 필요합니다.

이러한 인시던트 중에는 따라잡기 속도를 추정하십시오. 그룹이 분당 50,000개의 레코드 지연을 줄이고 500만 개의 레코드가 뒤쳐진 경우 실행 가능한 기간 내에 따라잡을 수 있습니다. 지연이 여전히 증가하는 경우 그룹이 복구되지 않는 것입니다. 생산자를 일시 중지하거나, 임시 소비자 용량을 추가하거나, 핫 경로에서 느린 다운스트림 종속성을 제거하거나, 건너뛸 수 있는 데이터에 대해 의식적인 결정을 내려야 할 수 있습니다.

최상의 소비자 지연 시간 모니터링은 운영 지연과 보존 여유 공간을 모두 보여줍니다. "이 그룹은 20분 뒤쳐져 있습니다"는 유용합니다. "이 그룹은 읽지 않은 데이터가 만료되기까지 18시간이 남았습니다"는 적절한 사람들을 회의실로 불러들이는 숫자입니다.

실용적인 지연 시간 런북

전체 지연이 아닌 파티션 수준 지연부터 시작하십시오.

kafka-consumer-groups.sh --bootstrap-server kafka-1:9092 --describe --group realtime-enricher

지연이 하나의 파티션에 집중된 경우 키 편향 또는 다른 소비자 인스턴스보다 느린 하나의 소비자 인스턴스를 찾으십시오. 지연이 고르게 분포된 경우 공유 병목 현상(너무 적은 소비자, 느린 다운스트림 호출, 브로커 가져오기 지연 시간 또는 정상 용량을 초과한 생산자 급증)을 찾으십시오. 명령을 1~2분 간격으로 두 번 실행하여 그룹이 따라잡고 있는지 아니면 더 뒤쳐지고 있는지 확인하십시오.

그런 다음 애플리케이션 내에서 네 가지 타이밍을 측정하십시오: poll()에서 대기하는 시간, 반환된 레코드를 처리하는 데 소요된 시간, 다운스트림 시스템에 쓰는 데 소요된 시간, 오프셋을 커밋하는 데 소요된 시간. 이 숫자는 어떤 설정이 중요한지 알려줍니다. 트래픽이 드문 동안 poll()이 너무 오래 기다리는 경우 fetch.max.wait.ms를 줄이거나 fetch.min.bytes를 낮게 유지하십시오. 처리가 지배적인 경우 Kafka 가져오기 설정은 방해가 됩니다. 커밋이 지배적인 경우 동기 커밋으로 모든 레코드를 커밋하는 것을 중지하십시오.

저지연 서비스의 경우 일반적으로 보수적인 가져오기 일괄 처리로 시작하고 브로커 또는 네트워크 오버헤드가 명확히 문제인 경우에만 늘립니다.

fetch.min.bytes=1
fetch.max.wait.ms=50
max.poll.records=100
enable.auto.commit=false

이는 보편적인 최상의 구성이 아닙니다. 읽을 수 있는 시작점입니다. 배치 ETL 소비자는 더 큰 가져오기와 더 큰 max.poll.records를 선호할 수 있습니다. 사기 점수 서비스는 하나의 느린 API 호출이 전체 배치를 지연시킬 수 있으므로 더 작은 배치를 선호할 수 있습니다.

poll() 후에 작업자 스레드를 추가할 때 특히 주의하십시오. 병렬 처리는 도움이 될 수 있지만 오프셋은 관련 파티션의 모든 이전 레코드가 안전하게 처리된 후에만 커밋되어야 합니다. 작업자 스레드가 순서 없이 완료되고 가장 높은 오프셋을 너무 일찍 커밋하면 충돌 시 진행 중이던 레코드가 자동으로 건너뛰어질 수 있습니다. 일반적인 패턴은 파티션별 완료를 추적하고 가장 높은 연속 완료 오프셋만 커밋하는 것입니다.

체크리스트는 간단합니다: 파티션별로 지연을 검사하고, 애플리케이션 단계를 측정하고, 가져오기 동작이 문제인 경우에만 가져오기 동작을 조정하고, 추가 인스턴스를 사용할 수 있는 충분한 파티션이 있는 경우에만 소비자를 확장하십시오. 이 순서는 대부분의 낭비되는 튜닝 작업을 방지합니다.