백엔드 데이터 인프라 86편 — Kafka Design: Producer (Partition 선택·ACK·Idempotent)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 86편. Kafka Producer 설계 — partition 선택 (round-robin·sticky·key hash), ACK 3가지 정책 (acks=0/1/all), idempotent producer 의 exactly-once 보장, retries·linger 같은 핵심 파라미터까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 86편 — Kafka Design: Producer (Partition 선택·ACK·Idempotent)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 86편이에요. 85편 까지 디스크·zero-copy·batching·compression 의 효율 비밀을 잡았다면, 이번 86편은 Producer 측 핵심 설계 — partition 선택·ACK·idempotent 를 다뤄요.

Kafka Producer 설계가 어렵게 느껴지는 이유

Producer 는 "메시지 보내기 끝" 처럼 간단해 보이지만, 실제로는 4가지 결정이 숨어 있어요.

첫째, partition 선택. partition 은 topic 안에서 메시지를 나눠 담는 칸인데, 같은 topic 에 N개가 있는 상황에서 어디로 보낼지가 순서 보장과 부하 분산을 좌우해요.

둘째, ACK 정책 trade-off. ACK 는 broker 가 메시지를 받았다고 알려주는 응답이고, acks=0/1/all 이 각각 정확히 무슨 뜻인지·어떤 데이터 손실 위험이 있는지 알아야 해요. 잘못 설정하면 메시지가 조용히 사라져요.

셋째, Idempotent Producer 와 exactly-once. idempotent 는 같은 요청을 여러 번 보내도 결과가 한 번과 같은 성질을 말해요. retries 가 발생할 때 중복 메시지 위험을 어떻게 막는지가 핵심.

이 글에서 4가지 Producer 설계 결정을 깊이 정리할게요.

Partition 선택 — 3가지 알고리즘

Producer 가 같은 topic 의 N개 partition 중 어디로 보낼지 정하는 방식이 3가지 있어요.

(1) Key 기반 — 같은 key = 같은 partition

producer.send(new ProducerRecord<>("orders", "user42", "buy item"));

key 가 있으면:

partition = hash(key) % num_partitions

같은 user42 의 모든 이벤트는 항상 같은 partition 으로 가요. 순서 보장의 핵심.

(2) Round-Robin (Key 없음)

key 가 null 이면 partition 들에 균등 분산돼요. 부하 분산은 좋지만 순서 보장은 안 돼요.

(3) Sticky Partitioner — Kafka 2.4+ 기본

순수 round-robin 의 문제는 batch 효율이 떨어진다는 거예요. 한 batch 안 메시지가 여러 partition 으로 흩어지면 각 partition 의 batch 가 작아지거든요.

Sticky Partitioner 는 메시지를 보내는 partitioner(분배기) 중 하나로, 한 batch 가 채워질 때까지 한 partition 에 몰아 넣어요. batch 가 다 차면 다음 partition 으로 넘어가요. batching 효율과 균등 분산을 둘 다 잡는 방식.

시간 0~10ms: partition 0 에 batch 가득
시간 10~20ms: partition 1 에 batch 가득
시간 20~30ms: partition 2 에 batch 가득
...

key 없는 환경에서 처리량이 30% 이상 올라가요.

Custom Partitioner

public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        // 비즈니스 로직 기반 partition 선택
        return ...;
    }
}

특수 환경 (예: VIP 사용자는 별도 partition) 에 써요.

ACK 정책 — 3가지 trade-off

가장 중요한 trade-off 가 여기에 있어요. 데이터 안전성과 지연 시간이 정면으로 부딪치는 자리.

acks=0 — Fire-and-Forget

Producer → Broker (응답 안 기다림) → 다음 메시지
  • 장점 — 최고 처리량, 최저 지연
  • 단점 — broker 죽으면 메시지 손실, 메시지 도착 보장 X
  • 사용 — 로그·메트릭처럼 손실이 OK 인 데이터

acks=1 (기본) — Leader 확인

Producer → Leader 가 자신 디스크에 write → ACK → 다음 메시지
  • 장점 — 빠르고 일반적으로 안전
  • 단점 — Leader 가 ACK 후 죽고 replica 로 복제가 안 됐으면 메시지 손실
  • 사용 — 대부분의 일반 데이터

acks=all (또는 -1) — 모든 ISR 확인

Producer → Leader → 모든 ISR(in-sync replicas) 가 받음 → ACK → 다음
  • 장점 — 가장 안전, replica 까지 보장
  • 단점 — 지연 시간 ↑, 처리량 ↓
  • 사용 — 금융·결제·법적 자료처럼 손실이 X 여야 하는 데이터

min.insync.replicas 와의 조합

여기서 정말 중요한 자리예요. acks=all 만으로는 부족하고, broker 설정 min.insync.replicas 와 함께 써야 해요.

replication.factor=3
min.insync.replicas=2
acks=all

최소 2개 ISR 가 확인해야 ACK 가 떨어진다는 뜻이에요. 1개만 살아 있으면 producer 가 NotEnoughReplicasException 을 던지면서 쓰기에 실패해요.

trade-off:

  • min.insync.replicas=1 + acks=all = leader 만 확인 (사실상 acks=1)
  • min.insync.replicas=2 + replication.factor=3 = 권장 안전 설정
  • min.insync.replicas=3 + replication.factor=3 = broker 한 대만 down 돼도 쓰기 실패

Idempotent Producer — 중복 방지

문제

Producer 가 메시지를 보내요. broker 가 받고 ACK 를 보냈는데, 네트워크 에러로 Producer 가 ACK 를 못 받아요. 그러면 Producer 가 retry 를 하고, broker 는 같은 메시지를 두 번 받게 돼요.

기본 동작에서는 중복 메시지가 broker 에 두 번 저장돼요.

해법 — Idempotent Producer

enable.idempotence=true

설정 시:

  • Producer 가 unique Producer ID (PID) 받음
  • 각 메시지에 sequence number 부여
  • Broker 가 (PID, sequence) 추적 → 중복 거부

같은 메시지를 두 번 전송해도 한 번만 저장.

자동으로 활성화되는 설정:

  • acks=all
  • retries > 0
  • max.in.flight.requests.per.connection ≤ 5

Kafka 3.0+ 부터 enable.idempotence=true 가 기본값.

Exactly-Once 보장

Idempotent + Transactions (다음 글) 를 조합하면 exactly-once semantics (EOS) 가 돼요. 정말 정확히 한 번을 보장하는 자리. 결제·금융 같은 데이터가 여기 들어가요.

Retries — 일시적 실패 자동 복구

retries=2147483647         # 거의 무한 (기본)
retry.backoff.ms=100        # 재시도 사이 100ms
delivery.timeout.ms=120000  # 총 2분 안에 ACK 못 받으면 실패

일시적 네트워크 에러나 broker 일시 다운은 자동으로 retry 가 돼요. idempotent 와 같이 쓰면 중복 없이 동작.

여기서 시험 함정이 하나 있어요 — retry 가 메시지 순서를 깨뜨릴 수 있어요. max.in.flight.requests.per.connection=5 면 5개 in-flight 중 1개가 실패하고 retry 가 도는 사이에 나머지 4개가 먼저 도착해서 순서가 깨져요.

해법:

  • max.in.flight.requests.per.connection=1 (느림) 또는
  • enable.idempotence=true — sequence 로 순서 보장 + retry 안전

Batching + linger.ms — 효율과 지연 균형

85편에서 본 내용. Producer 설계 핵심:

batch.size=16384            # 16KB 또는
linger.ms=10                # 10ms 후 즉시 전송
buffer.memory=33554432       # 32MB 메모리 buffer
  • batch.size 까지 채우거나
  • linger.ms 시간 지나면 전송
  • buffer 가득 차면 send() 가 block

비동기 vs 동기 send

비동기 (권장)

producer.send(record, (metadata, exception) -> {
    if (exception != null) {
        // 실패 처리
    } else {
        System.out.println("Sent to partition " + metadata.partition());
    }
});

장점 — 높은 처리량 + batching 이 자연스럽게 일어남

동기

RecordMetadata metadata = producer.send(record).get();

장점 — 즉시 실패를 잡을 수 있고 단순함 단점 — 매우 느려요. 사실상 batch 효율을 못 살림

대부분의 환경은 비동기를 써요.

Compression 과 합쳐서

acks=all
enable.idempotence=true
compression.type=zstd
linger.ms=10
batch.size=32768

운영 환경 표준이에요. 안전성과 효율을 함께 잡는 조합.

한계·실무 함정

1. acks=0 잘못 사용

"빠르게" 만 보고 acks=0 을 쓰면 프로덕션에서 데이터 손실이 나요. 로그·메트릭 외에는 최소한 acks=1 부터 시작.

2. min.insync.replicas 미설정

acks=all 만 박고 broker 측 min.insync.replicas=1 이면 사실상 acks=1 이에요. 둘 다 같이 설정해야 안전.

3. Idempotent 비활성 + 무한 retries

중복 메시지가 폭증해요. Kafka 3.0+ 기본값 이라 안전하지만, 이전 버전은 명시적으로 설정.

4. linger.ms 너무 큼 = 지연 폭증

실시간 환경에서는 낮은 linger 를 쓰세요 (1~5ms).

5. 큰 메시지 + 작은 max.request.size

max.request.size=1048576    # 기본 1MB

1MB 초과 메시지는 RecordTooLargeException 이 떨어져요. 큰 메시지 환경은 따로 조정.

시험 직전 한 번 더 — Kafka Producer 함정 압축 노트

  • Partition 선택 3가지 = key hash · round-robin · Sticky (2.4+ 기본)
  • key 있음 = hash(key) % N같은 key 같은 partition 순서 보장
  • key 없음 (round-robin) = 균등 분산, 순서 보장 X
  • Sticky Partitioner = batch 다 찰 때까지 한 partition, 처리량 30%+
  • Custom Partitioner = 비즈니스 로직 기반
  • ACK 3가지 = 0 (fire-and-forget) · 1 (leader 확인, 기본) · all (모든 ISR)
  • acks=0 = 손실 OK 데이터만
  • acks=1 = 일반, leader 죽으면 손실 가능
  • acks=all = 가장 안전, replica 까지
  • acks=all + min.insync.replicas=2 + replication.factor=3 = 권장 안전 설정
  • min.insync.replicas 안 박으면 사실상 acks=1
  • Idempotent Producer (enable.idempotence=true) = 중복 메시지 방지
  • 메커니즘 = (PID, sequence) → broker 중복 거부
  • Kafka 3.0+ 기본값
  • 자동 활성 = acks=all, retries > 0, max.in.flight ≤ 5
  • Exactly-Once = Idempotent + Transactions (87편)
  • Retries = 일시적 실패 자동 복구
  • 함정 — retry 가 순서 깨뜨릴 수 있음 → max.in.flight=1 또는 idempotent
  • Batching = batch.size + linger.ms + buffer.memory
  • 비동기 send = 표준 (callback 패턴)
  • 동기 send = batch 효율 X, 특수한 경우만
  • 운영 표준 = acks=all + idempotent + zstd 압축 + linger 10ms
  • 함정 — acks=0 프로덕션 데이터 손실
  • 함정 — min.insync.replicas 미설정
  • 함정 — linger.ms 너무 큼 → 지연 폭증
  • 함정 — max.request.size 초과 → RecordTooLargeException

공식 문서: Kafka Design — The Producer 에서 자세한 사양을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!