Skip to content

Producer Internals

프로듀서가 send() 메소드를 호출하면 메시지는 즉시 네트워크를 통해 전송되는 것이 아닌, 내부 버퍼에 저장된 후 별도의 스레드에 의해 브로커로 전송되는 비동기 구조를 가진다.

  • 메인 스레드(애플리케이션 스레드)
    • ProducerRecord 객체를 생성하고 send() 메소드를 호출
    • 메시지는 직렬화(Serialize)되고, 파티셔너(Partitioner)에 의해 대상 파티션이 결정
    • 결정된 파티션에 해당하는 버퍼(RecordAccumulator 내부의 Deque)에 메시지가 저장
  • Sender 스레드
    • 프로듀서 내부에 존재하는 별도의 백그라운드 스레드
    • RecordAccumulator에서 전송할 준비가 된 메시지 배치(Batch)를 가져옴
    • 해당 배치를 대상 브로커로 전송하고, 응답(Acknowledgement)을 처리
  • 전송 결과 확인
    • send() 메소드는 Future 객체 반환
    • future.get()을 호출하면, Sender 스레드가 브로커로부터 응답을 받을 때까지 메인 스레드는 블로킹
    • send() 메소드에 콜백(Callback) 함수를 전달하여 응답을 받았을 때 비동기적으로 결과 처리 가능
      • 일반적으로 비동기 방식을 사용해 높은 처리량을 확보

메시지가 토픽의 여러 파티션 중 어느 곳으로 전송될지 결정하는 방법은 다음과 같다.

{ TopicA [ P0 | P1 | P2 ] }
Producer ---> Partitioner ---> { TopicB [ P0 | P1 | P2 ] }
{ TopicC [ P0 | P1 | P2 ] }

1. ProducerRecord에 파티션 번호가 명시된 경우

Section titled “1. ProducerRecord에 파티션 번호가 명시된 경우”

가장 높은 우선순위를 가지며, 무조건 해당 파티션으로 메시지가 전송된다.

메시지에 특정 키를 할당하면, 프로듀서는 키의 해시값(Hash Value)을 계산하여 데이터를 보낼 파티션을 일관되게 결정한다.

  • 동작 원리: 동일한 키를 가진 메시지들은 항상 같은 해시 값을 가지므로, 반드시 동일한 파티션으로 전송
  • 주요 목적: 특정 식별자(예: 사용자 ID, 주문 번호)를 기준으로 데이터의 처리 순서를 보장해야 할 때 사용

3. 키가 없는 경우: 처리량 극대화

Section titled “3. 키가 없는 경우: 처리량 극대화”

메시지에 키가 없으면, 프로듀서는 순서를 보장할 필요가 없다고 판단하고 처리량을 극대화하는 방향으로 동작한다.

  • 과거 방식 (Round-Robin): 메시지를 파티션별로 하나씩 순차 분배
    • 부하는 균등해지지만, 각 파티션으로 보내는 배치가 작게 형성되어 네트워크 오버헤드가 증가하고 전체 처리량이 저하될 수 있음
  • 최신 방식 (Sticky Partitioner): 하나의 파티션에 메시지를 집중적으로 보내 배치를 최대화
    • 이 방식은 메시지를 최대한 큰 배치(Batch)로 묶어 전송하므로 네트워크 오버헤드를 최소화하고 전체 처리량을 극대화합니다.
    • 동작 방식
      1. 하나의 파티션을 임의로 선택
      2. 배치 버퍼가 가득 차거나 linger.ms 시간이 초과될 때까지 해당 파티션에만 메시지를 계속 전송
      3. 배치가 전송되면, 다음 파티션을 선택하여 위 과정을 반복

프로듀서는 네트워크 효율성과 브로커 부하를 줄이기 위해 메시지를 압축하고 배치 단위로 묶어 전송하는 방식을 사용한다.

  • batch.size
    • 하나의 배치에 담을 수 있는 메시지의 최대 크기(byte)
    • 이 크기에 도달하면 Sender 스레드는 즉시 메시지 배치를 전송
    • 너무 작으면 배치 효율이 떨어지고, 너무 크면 메모리 사용량이 증가
  • linger.ms
    • 배치가 batch.size에 도달하지 않더라도, Sender 스레드가 메시지를 보내기 전까지 대기하는 최대 시간(ms)
    • 이 값을 늘리면 지연 시간은 증가하지만, 더 많은 메시지를 하나의 배치로 묶을 수 있어 처리량 향상
  • compression.type
    • 메시지 배치를 브로커로 보내기 전에 압축하여 네트워크 대역폭 사용량 감소
    • snappy, lz4, gzip, zstd 등의 옵션
    • 압축은 개별 메시지가 아닌 배치 단위로 수행
    • 네트워크 오버헤드를 줄여 처리량을 높이는 데 효과적이지만, 압축/해제 과정에서 Producer / Consumer CPU 사용량이 증가할 수 있음

전송 보장을 위해 프로듀서는 브로커로부터 메시지 전송 성공 여부를 확인하는 메커니즘으로, acks 설정에 따라 다음 세 가지 모드가 제공된다.

acks 모드동작장점단점
acks=0브로커 응답 없이 성공 처리빠른 전송 속도데이터 손실 가능성 높음
acks=1리더가 메시지 수신 시 성공적절한 성능과 신뢰성 균형리더 장애 시 데이터 손실 가능
acks=all모든 ISR 복제 완료 시 성공강력한 내구성 및 데이터 무결성처리 지연 및 성능 저하 가능

네트워크 일시 오류 등으로 전송 실패 시 프로듀서는 재시도를 수행할 수 있는 옵션을 제공하며, 재시도 중 중복 전송이나 순서 역전을 방지하기 위한 멱등성 기능도 제공된다.

  • retries: 전송 실패 시 프로듀서가 자동으로 재시도 수행 횟수 지정
    • 재시도 과정에서 메시지 중복 발생 가능
      1. 프로듀서가 메시지를 보냈고 리더가 성공적으로 저장
      2. 프로듀서에게 Ack을 보내기 직전 네트워크 문제 발생
      3. 프로듀서는 실패로 간주하고 재시도하여 동일한 메시지를 다시 전송
  • enable.idempotence: 멱등성 활성화 여부
    • true 설정 시 프로듀서는 동일 메시지를 중복으로 저장하지 않도록 보장
    • 필요 조건
      • acks=all
      • retries > 0
      • max.in.flight.requests.per.connection <= 5
  • max.in.flight.requests.per.connection: 브로커로부터 응답을 받지 않은 상태에서 한 번에 보낼 수 있는 최대 요청 수

멱등적 프로듀서는 각 메시지에 PID(Producer ID)와 시퀀스 번호를 부여하고, 브로커가 이를 대조하여 중복과 순서를 판단한다.

  • 중복 방지: 브로커는 파티션별로 마지막으로 저장된 시퀀스 번호를 기록
    • 새로 들어온 번호가 기존보다 작거나 같으면 이미 저장된 것으로 판단하여 기록을 건너뛰고 Ack만 반환
  • 순서 검증: 브로커는 시퀀스 번호를 통해 메시지가 탐지된 순서대로 기록되도록 검사
    • 시퀀스가 증가하지 않고 예상된 순서가 아닐 경우 해당 요청은 정상 처리하지 않음

In-flight 요청과 재전송 순서 보장

Section titled “In-flight 요청과 재전송 순서 보장”
  • max.in.flight.requests.per.connection 설정은 멱등성 활성화 시 브로커와 클라이언트가 안정적으로 ordering을 유지하기 위해 필요(최대 5 권장)
  • 멱등성이 활성화된 프로듀서는 동일한 PID/시퀀스 번호를 유지하며 재전송 시에도 순서가 보존되도록 브로커가 검사
  • 재전송 과정에서 브로커는 이전 시퀀스 상태를 참고하고 중복 저장 방지

Last updated:

Kafka