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에 파티션 번호가 명시된 경우

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

2. 키가 있는 경우

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

  • 동작 원리: 동일한 키를 가진 메시지들은 항상 같은 해시 값을 가지므로, 반드시 동일한 파티션으로 전송

  • 주요 목적: 특정 식별자(예: 사용자 ID, 주문 번호)를 기준으로 데이터의 처리 순서를 보장해야 할 때 사용

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 사용량이 증가할 수 있음

전송 보장을 위한 Ack 메커니즘

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

acks 모드
동작
장점
단점

acks=0

브로커 응답 없이 성공 처리

빠른 전송 속도

데이터 손실 가능성 높음

acks=1

리더가 메시지 수신 시 성공

적절한 성능과 신뢰성 균형

리더 장애 시 데이터 손실 가능

acks=all

모든 ISR 복제 완료 시 성공

강력한 내구성 및 데이터 무결성

처리 지연 및 성능 저하 가능

재시도와 멱등성

네트워크의 일시적인 문제나 브로커의 리더 선출 과정에서 메시지 전송은 실패할 수 있기 때문에, 프로듀서는 재시도 메커니즘을 제공한다.

  • 재시도(retries)

    • 전송 실패 시, 프로듀서가 자동으로 재시도하는 횟수 지정

    • 재시도 과정에서 메시지 중복이 발생 가능

      1. 프로듀서가 메시지를 보냈고 리더가 성공적으로 저장

      2. 프로듀서에게 Ack을 보내기 직전 네트워크 문제 발생

      3. 프로듀서는 실패로 간주하고 재시도하여 동일한 메시지를 다시 전송

  • 멱등성 보장(enable.idempotence=true)

    • 중복 문제를 해결하기 위한 기능

    • 프로듀서는 고유한 PID(Producer ID)를 할당받고, 보내는 각 메시지에 시퀀스 번호를 부여

    • 브로커는 (PID, 파티션) 조합별로 마지막 시퀀스 번호를 기록

      • 이보다 작거나 같은 번호의 메시지가 오면 중복으로 간주하여 저장하지 않고 Ack만 반환

    • enable.idempotence=true로 설정하면, acksall로, retriesInteger.MAX_VALUE로 자동 조정

      • 데이터 유실 없이 정확히 한 번만 전송되는 것을 보장

    • max.in.flight.requests.per.connection을 5 이하로 설정하여 재시도 시 메시지 순서가 뒤바뀌는 문제 방지 가능

Last updated

Was this helpful?