← 학습 카테고리

Learn

Kafka

42개 모듈 · 현재 38번째

Kafka 모듈 38/42 kafka-learn-38

프로듀서 튜닝 디테일 — 재시도, 배치, 압축, 메타데이터 캐싱

50 Kafka Interview Questions for Data Engineers (2026) — (블로그, 발행처 미상 — 데이터 엔지니어링 교육 콘텐츠 사이트) 50 Kafka Interview Questions for Data Engineers (2026), Q9, Q22-26, Q37, Q41

이 모듈을 다 읽으면

  • 프로듀서 재시도 시 지터(jitter)가 왜 필요한지, delivery.timeout.ms가 무엇을 제한하는지 설명할 수 있다
  • batch.size/linger.ms가 만드는 처리량-지연 트레이드오프와 압축 알고리즘별 특성을 구분할 수 있다
  • 클라이언트가 클러스터 메타데이터를 캐싱하는 방식과 NotLeaderForPartition 오류 발생 시 동작을 설명할 수 있다

프로듀서가 처리량과 지연, 그리고 장애 상황에서의 안정성 사이에서 어떤 설정 레버를 쥐고 있는지를 재시도/지터, 배치/압축, 메타데이터 캐싱 세 축으로 정리한다.

재시도와 지터 — thundering herd를 피하는 법

프로듀서가 브로커로부터 응답을 받지 못하거나 재시도 가능한 오류(NotEnoughReplicas, 일시적 네트워크 오류 등)를 만나면 자동으로 재전송을 시도한다. 이때 delivery.timeout.ms는 재시도를 포함해 한 레코드가 최종적으로 성공하거나 실패로 확정되기까지 허용되는 전체 시간의 상한이다 — 이 시간을 넘기면 재시도 여부와 무관하게 프로듀서는 해당 레코드를 실패로 처리하고 콜백에 예외를 전달한다. retries(재시도 횟수)나 retry.backoff.ms(재시도 간격)를 아무리 늘려도 delivery.timeout.ms를 넘어서는 재시도는 발생하지 않으므로, 이 값은 사실상 다른 재시도 관련 설정들의 총 예산 역할을 한다.

브로커 장애처럼 다수의 프로듀서가 동시에 영향을 받는 상황에서는, 모든 프로듀서가 똑같은 backoff 간격으로 동시에 재연결·재시도를 시도하면 회복 중인 브로커에 순간적으로 요청이 몰려 회복을 오히려 방해하는 thundering herd 현상이 생길 수 있다. 지터(jitter)는 이 재시도 간격에 작은 무작위 값을 더해 프로듀서들의 재시도 타이밍을 흩뜨리는 기법으로, Kafka 클라이언트의 retry.backoff.ms 기반 backoff에는 기본적으로 지수 백오프와 함께 무작위 지터가 적용되어 있다.

핵심 포인트

  • delivery.timeout.ms는 재시도를 포함해 한 레코드의 성공/실패가 최종 확정되기까지의 전체 시간 상한이다
  • retries·retry.backoff.ms를 아무리 크게 잡아도 delivery.timeout.ms를 넘는 재시도는 일어나지 않는다
  • 지터는 재시도 간격에 무작위성을 더해 다수 프로듀서의 동시 재시도가 만드는 thundering herd를 완화한다

배치와 압축 — 처리량을 사는 대가로 무엇을 지불하는가

배치(batching)는 여러 레코드를 하나의 요청으로 묶어 전송해 네트워크 왕복 횟수와 브로커 쪽 디스크 쓰기 횟수를 줄이는 핵심 처리량 레버다. batch.size는 파티션별 배치의 최대 바이트 크기를 정하고, linger.ms는 배치가 batch.size에 다 차지 않았더라도 전송 전에 추가로 기다릴 최대 시간을 정한다 — linger.ms의 기본값 0은 "채워지지 않았어도 지체 없이 보낸다"는 뜻이며, 이 값을 늘리면 배치가 더 잘 채워져 처리량은 오르지만 개별 레코드가 배치가 찰 때까지 기다려야 하므로 지연은 늘어난다.

압축(compression.type)은 배치를 브로커로 보내기 전에 통째로 압축해 네트워크 대역폭과 브로커의 디스크 사용량을 줄인다. Kafka는 gzip, snappy, lz4, zstd를 지원하며, 이 중 압축률과 CPU 비용은 서로 트레이드오프 관계다 — snappy는 압축률은 낮지만 CPU 오버헤드가 작아 지연에 민감한 워크로드에 자주 쓰이고, zstd는 압축률이 가장 높은 대신 CPU 사이클을 더 소모한다. 배치가 클수록 압축 대상 데이터가 많아져 압축 효율(압축률)도 함께 좋아지므로, batch.size·linger.ms·compression.type 세 설정은 서로 독립적이지 않고 함께 튜닝해야 하는 세트다.

핵심 포인트

  • linger.ms를 늘리면 배치가 더 잘 채워져 처리량은 오르지만 개별 레코드의 지연은 늘어난다
  • snappy는 CPU 오버헤드가 작고, zstd는 압축률이 높은 대신 CPU를 더 쓴다 — 압축률과 CPU 비용은 트레이드오프다
  • 배치가 클수록 압축 효율도 함께 좋아지므로 batch.size·linger.ms·compression.type은 함께 튜닝해야 한다

클라이언트 메타데이터 캐싱과 NotLeaderForPartition

Kafka 클라이언트(프로듀서·컨슈머)는 최초 연결 시 부트스트랩 브로커로부터 클러스터 메타데이터(브로커 목록, 각 파티션의 현재 리더가 어느 브로커인지)를 가져와 로컬에 캐싱한다. 이후의 모든 produce/fetch 요청은 이 캐시를 참조해 해당 파티션의 리더 브로커로 직접 전송되므로, 매 요청마다 메타데이터를 조회할 필요가 없어 지연이 크게 줄어든다.

문제는 이 캐시가 오래될 수 있다는 점이다 — 리더 재선출(브로커 장애, 롤링 리스타트, 파티션 재배치 등)이 일어나면 캐시된 리더 정보가 더 이상 유효하지 않게 된다. 클라이언트가 오래된 캐시를 근거로 이제는 리더가 아닌 브로커에 요청을 보내면 해당 브로커는 NotLeaderForPartition(또는 최신 클라이언트/프로토콜 기준 NOT_LEADER_OR_FOLLOWER) 오류로 응답하고, 클라이언트는 이를 신호로 메타데이터를 강제로 새로고침한 뒤 올바른 새 리더에게 요청을 재전송한다. 즉 이 오류 자체는 장애가 아니라 캐시 무효화와 자동 복구가 정상적으로 작동하고 있다는 신호이며, metadata.max.age.ms는 이런 강제 새로고침과 별개로 캐시를 주기적으로 갱신하는 상한을 정한다.

핵심 포인트

  • 클라이언트는 브로커 목록과 파티션별 리더 정보를 로컬에 캐싱해 매 요청마다 조회하지 않는다
  • 리더가 바뀌어 캐시가 오래되면 클라이언트는 NotLeaderForPartition 오류를 받고 메타데이터를 강제로 새로고침한다
  • 이 오류는 장애 신호가 아니라 정상적인 캐시 무효화·자동 복구 메커니즘의 일부다