[ADVANCED #1] Producer 심층 분석: 내부 동작 원리와 고급 설정

2025. 9. 7. 21:28·Kafka/Core

1. Java Producer API의 기본 동작 원리

Java의 Kafka Producer 클라이언트는 메시지를 전송하기 위해 명확한 단계를 따른다. 전체적인 처리 로직은 아래와 같다.

① 환경 설정: Properties 객체를 사용해 프로듀서에 필요한 설정을 정의한다. (예: 접속할 Kafka 서버 주소, 메시지 직렬화 방식 등)
② KafkaProducer 객체 생성: 1번에서 만든 설정값을 바탕으로 KafkaProducer 인스턴스를 생성한다. 이 객체가 메시지 전송의 핵심 역할을 담당한다.
③ ProducerRecord 객체 생성: 전송할 토픽(Topic) 이름과 메시지 값(Key, Value)을 담은 ProducerRecord 객체를 만든다. 이것이 보낼 메시지 한 건의 실체이다.
④ 메시지 전송: KafkaProducer 객체의 send() 메서드를 호출하여 3번에서 만든 ProducerRecord를 Kafka 브로커로 전송 요청한다.
⑤ 종료: 메시지 전송이 모두 끝나면 close() 메서드를 호출하여 프로듀서가 사용하던 리소스를 안전하게 종료해야 한다.

2. send() 메서드의 내부 동작: 숨겨진 비동기(Async) 구조 

send() 메서드를 호출하면 메시지가 즉시 발송될 것처럼 보이지만, 내부적으로는 더 효율적인 방식으로 동작한다. Kafka Producer는 기본적으로 비동기(Async) 전송 구조를 가진다.

Producer 내부 스레드 구조

  • Main Thread: send()를 호출하는 스레드이다. 이 스레드는 메시지를 즉시 네트워크로 보내지 않고, 내부 버퍼(Buffer)에 쌓아두기만 한 뒤 기다리지 않고 바로 다음 작업을 수행한다.
  • Background Thread (kafka-producer-network-thread): 프로듀서 내부에 존재하는 별도의 스레드이다. 이 스레드가 버퍼에 쌓인 메시지들을 모아(Batch) Kafka 브로커에 실제로 전송하는 역할을 전담한다.
Q. 왜 버퍼에 메시지를 모아서 보내는가?
A. send()를 호출할 때마다 매번 네트워크 통신을 하면 처리량(Throughput)이 크게 떨어지기 때문이다. 메시지를 일정량 모아서 한 번에 보내는 것이 훨씬 효율적이다.

 

이 구조는 디버깅을 통해 직접 확인할 수 있다. 아래처럼 send() 호출 부분에 중단점(Break Point)을 걸고 스레드 상태를 보면, kafka-producer-network-thread라는 이름의 스레드가 동작하는 것을 볼 수 있다.

2.1. `producer.send()` 호출 시점의 동작: 논블로킹과 비동기의 결합

`producer.send(record)`를 호출하면 메인 스레드에서 즉시 리턴된다. 이 순간에는 코드가 블로킹되지 않는다. 왜냐하면 카프카 프로듀서는 내부적으로 `RecordAccumulator`라는 버퍼에 메시지를 넣기만 하고, 실제 네트워크 송신은 Sender Thread가 담당하기 때문이다.

  • 논블로킹(Non-blocking): `send()` 함수는 물리적인 네트워크 전송(I/O)이 완료될 때까지 기다리지 않는다. 데이터를 내부 메모리 버퍼에 기록하는 작업은 매우 짧은 시간 내에 완료되므로, 함수는 호출 즉시 제어권을 메인 스레드에 돌려준다.
  • 비동기(Asynchronous): 실제 네트워크 전송 작업은 메인 스레드가 아닌 백그라운드의 Sender Thread가 담당한다. `send()` 호출이 리턴되는 시점은 '버퍼 저장 완료'를 의미할 뿐, '전송 완료'를 의미하지 않는다. 전송 결과는 한참 뒤에 Sender Thread가 네트워크 응답을 받은 후에야 결정된다.

이러한 설계 덕분에 메인 스레드는 네트워크 I/O로 인한 대기 없이 계속해서 다음 작업을 처리할 수 있으며, 결과는 나중에 콜백이나 `Future` 통해 통보받는 구조가 된다.

2.2. Sender Thread의 역할

Sender Thread는 `KafkaProducer` 내부에서 백그라운드로 계속 실행되면서 다음과 같은 작업을 수행한다.

  1. 버퍼(`RecordAccumulator`)에 쌓인 메시지를 확인한다.
  2. 메시지를 배치(batch) 단위로 묶어서 브로커에 전송한다.
  3. 브로커로부터 ACK(응답)를 수신한다.
  4. 그 결과를 콜백(Callback) 혹은 `Future` 객체에 기록한다.

2.3. 동기/비동기(sync/async)의 본질적 차이

여기서 말하는 sync/async는 메시지를 보낸 "결과"를 어떻게 기다리느냐의 차이이다.

  • [1] Async 방식:메인 스레드는 바로 다음 코드로 넘어간다. 결과는 callback이 Sender Thread에서 ACK 수신 후 실행된다.
    • `producer.send(record, callback);`
  • [2] Sync 방식:send() 자체는 비동기이지만, .get()을 호출하면서 메인 스레드가 ACK을 받을 때까지 블로킹된다. 결국 동기 방식으로 동작하게 된다. (ACK에는 파티션, 오프셋 정보 등 메타데이터가 포함된다.)
    • `producer.send(record).get();`

3. Sync vs Async: 결과 처리에 대한 개발자의 선택 

"어차피 Producer는 기본적으로 비동기인데, 왜 동기(Sync)와 비동기(Async) 전송 방식을 또 배우는가?" 라는 의문이 생길 수 있다. 핵심은 '비동기'라는 단어가 두 가지 다른 관점에서 사용되고 있다는 점이다.

  1. 구조적 관점 (Producer의 내부 동작): send() 메서드의 실제 전송은 kafka-producer-network-thread가 담당하며, Main Thread는 기다리지 않는다. 이것은 Kafka Producer의 설계 자체이며 바꿀 수 없는 기본 동작이다.
  2. 개발자 관점 (결과 처리 방식): Background Thread가 보낸 메시지가 "성공했는지, 실패했는지" 그 결과를 개발자가 어떻게 처리할 것인가에 대한 선택의 문제이다.

즉, Producer의 send() 메서드 자체는 항상 비동기적으로 움직이지만, 그 작업의 '결과'를 동기적으로 기다릴 수도(Sync), 비동기적으로 통보받을 수도(Async) 있는 것이다. 지금부터 다룰 Sync/Async는 바로 이 '결과 처리'에 대한 이야기이다.

💡Main Thread에서 send() 호출은 항상 비동기적이다. Sender Thread는 항상 백그라운드에서 메시지를 보내고 응답을 기다리는 자신의 역할을 수행한다.
💡개발자의 선택은 'Sender Thread의 작업 완료를 Main Thread가 어떤 방식으로 인지할 것인가'에 대한 문제이다. 즉시 결과를 기다리면 Sync(.get()), 나중에 통보받으면 Async(Callback)가 되는 것이다.
⚠️ Sender Thread를 Async/Sync 방식으로 나누는게 아니다. Sender Thread는 Broker로부터 ACK를 받아야 하기 때문에 항상 Sync이고, Main Thread가 send() 이후에 후처리를 어떻게 하느냐에 따라 Async/Sync 방식으로 나뉘는것이다.

4. 전송 결과 처리 방식: Sync와 Async 구현하기 

4.1. 동기(Sync) 방식

 

"Background Thread가 메시지를 보내고 응답(Ack)을 가져올 때까지, Main Thread는 여기서 멈춰서 기다린다!"는 방식이다. 전송 결과가 반드시 필요한 경우에 사용된다.

  • 구현: send() 메서드가 반환하는 Future 객체의 get() 메서드를 호출하면 된다. get()이 호출되면 브로커로부터 응답이 올 때까지 Main Thread는 대기(Wait) 상태가 된다.
  • 단점: 응답을 기다리는 시간 동안 프로그램이 멈추기 때문에, 비동기 방식에 비해 전체적인 성능은 떨어질 수 있다.
// send()가 반환한 Future 객체의 get()을 호출하여 결과를 기다린다.
try {
    RecordMetadata metadata = kafkaProducer.send(producerRecord).get();
    System.out.println("Offset: " + metadata.offset());
} catch (Exception e) {
    e.printStackTrace();
}

4.2. 비동기(Async) 방식과 콜백(Callback)

 

"Background Thread는 메시지를 보내고, Main Thread는 다른 일을 계속한다. 전송이 끝나면 그 결과를 나중에 나에게 알려달라(Callback)!" 는 방식이다. 높은 처리량이 필요할 때 주로 사용된다.

  • 구현: send() 메서드를 호출할 때, 두 번째 인자로 Callback 인터페이스를 구현한 객체를 넘겨주면 된다. 전송이 완료되면 onCompletion 메서드가 호출되어 성공 또는 실패 결과를 전달받는다.

콜백(Callback)이란?
다른 코드의 인자로서 넘겨주는 실행 가능한 코드를 의미한다. 콜백을 넘겨받은 코드는 특정 이벤트가 발생했을 때(여기서는 '메시지 전송 완료'), 약속된 메서드를 다시 호출(call back)해 준다. Java에서는 주로 인터페이스를 통해 구현한다.

// send() 메서드에 Callback 객체를 인자로 전달한다.
producer.send(producerRecord, new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
        if (exception == null) {
            // 성공 시 로직
            System.out.println("Topic: " + metadata.topic());
            System.out.println("Partition: " + metadata.partition());
            System.out.println("Offset: " + metadata.offset());
        } else {
            // 실패 시 로직
            exception.printStackTrace();
        }
    }
});

5. Producer의 acks 설정: 안정성과 속도의 줄다리기

Kafka Producer는 메시지를 보낼 때, 해당 토픽 파티션의 리더 브로커(Leader Broker)에게만 메시지를 전송한다. 리더 브로커는 메시지를 받은 후, 자신을 복제하고 있는 팔로워 브로커(Follower Broker)들에게 해당 메시지를 전파한다.

 

이때 Producer는 "메시지가 성공적으로 처리되었다"는 사실을 어떤 기준으로 판단할까? 바로 acks 옵션을 통해 데이터의 안정성(Durability)과 전송 속도(Throughput) 사이의 균형을 조절할 수 있다. acks는 "전송한 메시지에 대해 몇 개의 브로커로부터 응답(Acknowledgment)을 받을 것인가"를 정의하는 설정이다.

💡 ACKS 설정(0/1/all)은 메시지 전송의 Async/Sync을 결정짓는 요소가 아니다. 단지 전송된 메시지의 성공 여부의 판단 기준이 되는 값이라는 것을 알아야 한다. 즉, acks 설정은 async/sync와는 별개이다.
💡 쉽게 말해서, acks 설정은 "브로커에서 어느 정도의 확인을 받아야 성공으로 보느냐"를 결정하는 값
💡 설명에서 ack를 대기한다는것은 send()를 보내는 Main Thread의 입장이 아니라, 실제 메시지 전송을 담당하는 Sender Thread의 입장을 말한것이다. Sender Thread는 기본 설정이 acks=all(-1)인데, 이건 Broker로부터 ack를 받아야 다음 메시지를 전송하는 식으로 동작한다.

5.1. acks=0: 응답을 기다리지 않는 가장 빠른 전송

  • 동작 방식: Producer는 메시지를 보낸 후, 브로커로부터 성공 여부에 대한 응답(ack)을 전혀 기다리지 않고 즉시 다음 메시지를 전송한다. 전송 요청 후 확인 절차가 없으므로 'Fire-and-Forget' 방식이라고도 부른다.
  • 장점: 응답 대기 시간이 없으므로 세 가지 옵션 중 가장 빠르고 높은 처리량을 가진다.
  • 단점: 브로커가 메시지를 받았는지 보장할 수 없다. 메시지가 브로커에 기록되기 전에 장애가 발생하면 메시지는 그대로 유실된다. 재전송(retry)도 동작하지 않는다.
  • 사용 사례: 데이터가 일부 유실되어도 괜찮은 경우 (예: 주기적으로 수집되는 IoT 센서 데이터, 대용량 로그 수집)

5.2. acks=1: 리더 브로커의 응답만 확인하는 균형 잡힌 전송

  • 동작 방식: Producer는 리더 브로커가 메시지를 성공적으로 받았는지에 대한 응답(ack)을 받은 후에 전송을 성공으로 간주한다. 만약 리더로부터 에러를 받으면 재전송을 시도한다.
  • 장점: 메시지가 최소 한 번은 리더 브로커에 기록되는 것을 보장하므로, acks=0에 비해 안정적이다.
  • 단점: 리더가 ack를 보낸 직후, 팔로워 브로커들에게 데이터를 복제하기 전에 장애가 발생하면 데이터가 유실될 수 있다.
    • 시나리오:
      1. Producer가 메시지 B를 리더에게 보낸다.
      2. 리더는 B를 저장하고, Producer에게 "잘 받았다"고 ack를 보낸다.
      3. Producer는 B가 성공적으로 처리되었다고 판단하고 다음 메시지를 준비한다.
      4. 리더가 팔로워들에게 B를 복제하기 직전, 장애로 다운된다.
      5. 팔로워 중 하나가 새로운 리더로 승격되지만, 이 팔로워는 아직 B를 복제받지 못한 상태이다.
      6. 결과적으로 메시지 B는 영구적으로 유실된다.

5.3. acks=all (또는 -1): 모든 복제까지 확인하는 가장 안전한 전송 (기본값)

  • 동작 방식: Producer는 리더 브로커뿐만 아니라, 모든 인-싱크 리플리카(In-Sync Replica, ISR) 그룹의 팔로워 브로커들까지 메시지를 성공적으로 복제했음을 확인한 후, 리더가 보내는 최종 응답(ack)을 받는다.
  • 장점: 리더에게 장애가 발생해도 이미 팔로워에 데이터가 복제되어 있으므로, 메시지 유실이 발생하지 않는 가장 안전한 방법이다. 이러한 안정성 때문에 최신 Kafka 클라이언트의 기본값으로 설정되어 있다.
  • 단점: 리더와 팔로워 간의 복제까지 모두 기다려야 하므로, 세 가지 옵션 중 가장 느리다.
  • min.insync.replicas: acks=all은 브로커 설정인 min.insync.replicas와 함께 동작한다. 이 값은 ack를 보내기 위해 최소한 살아있어야 하는 ISR 멤버의 수를 의미한다. 예를 들어 이 값이 2이고, ISR 멤버가 리더 포함 1개뿐이라면 Producer는 에러를 받게 된다.

5.4. acks와 전송 방식(Sync/Async)의 관계

acks와 재전송(retry) 설정은 Producer의 전송 방식이 동기(Sync)인지 비동기(Async)인지와 상관없이 동일하게 적용된다. 즉, 비동기 콜백 방식에서도 acks=all로 설정하면 내부적으로는 모든 복제가 완료될 때까지 기다린 후 콜백 함수가 호출된다.

  • 비동기 방식에서의 재전송과 메시지 순서 변경 문제 비동기 방식에서 재전송이 발생하면 메시지의 순서가 바뀔 수 있다는 점을 유의해야 한다.
    • 시나리오:
      1. Producer가 메시지 A, B를 순서대로 비동기 전송한다.
      2. A는 일시적인 네트워크 문제로 전송에 실패하고, 재전송 큐에 들어간다.
      3. B는 정상적으로 전송되어 브로커에 먼저 기록된다.
      4. 잠시 후, 재시도한 A가 성공적으로 전송되어 브로커에 기록된다.
      5. 결과적으로 Producer는 A, B 순으로 보냈지만, 브로커에는 B, A 순으로 저장된다.
    • 해결: 이 문제를 방지하려면 프로듀서 옵션에서 enable.idempotence=true(멱등성 프로듀서 활성화, 기본값)를 사용하면 된다. 이 옵션은 메시지 순서를 보장하고 중복을 방지하는 역할을 한다.

6. Producer의 메시지 배치 전송의 이해

Kafka Producer의 높은 처리량(Throughput)의 비밀은 바로 메시지 배치(Batch) 전송에 있다. `producer.send()` 메서드는 호출될 때마다 메시지를 즉시 네트워크로 보내는 것이 아니라, 내부적으로 더 정교하고 효율적인 과정을 거친다. 이 메커니즘의 핵심은 Main Thread와 Sender Thread의 역할 분리와, 이 둘을 잇는 버퍼인 Record Accumulator에 있다.

Main Thread가 여러 개의 ProducerRecord를 RecordAccumulator에 넣으면, 이들이 토픽-파티션별로 묶여 Record Batch를 형성하는 과정을 보여준다. 이후 Sender Thread가 완성된 배치들을 브로커로 전송하는 흐름을 표현하고 있다.

6.1. Producer의 이중 스레드 구조

KafkaProducer는 내부적으로 두 개의 스레드를 통해 동작한다.

  • Main Thread: 우리가 producer.send() 코드를 실행하는 애플리케이션의 메인 스레드이다. 이 스레드의 역할은 메시지(ProducerRecord)를 받아 Record Accumulator라는 메모리 버퍼에 쌓아두는 것까지이다. 즉, send() 호출은 실제 네트워크 전송을 의미하는 것이 아니라, "전송할 메시지를 버퍼에 추가한다"는 뜻이다.
  • Sender Thread: 프로듀서 내부에 별도로 존재하는 백그라운드 스레드이다. 이 스레드가 Record Accumulator를 주기적으로 확인하여, 잘 모아진 메시지 배치(Batch)들을 가져와 실제 Kafka 브로커로 전송하는 네트워크 통신을 전담한다.

이처럼 역할을 분리함으로써, Main Thread는 네트워크 I/O를 기다리지 않고 빠르게 다음 작업을 수행할 수 있어 애플리케이션의 전체적인 응답성이 향상된다.

6.2. Producer Record와 Record Batch

  • KafkaProducer 객체의 send( ) 메소드는 호출 시마다 하나의 ProducerRecord를 입력하지만 바로 전송 되지 않고 내부 메모리 에서 단일 메시지를 토픽 파티션에 따라 Record Batch 단위로 묶인 뒤 전송됨.
  • 메시지들은 Producer Client의 내부 메모리에 여러 개의 Batch들로 buffer.memory 설정 사이즈 만큼 보관될 수 있으며 여러 개의 Batch들로 한꺼번에 전송될수 있음.

6.3. Producer Record Accumulator: 메시지를 정리하는 보관소 (배치 저장소)

 

앞서 2.1절에서 설명했듯이, `RecordAccumulator`는 메인 스레드가 메시지를 적재하고 Sender Thread가 가져가는 버퍼 역할을 한다. 이번 절에서는 이 버퍼의 내부 구조를 좀 더 자세히 살펴보자.

  • 파티션별 큐 구조: `RecordAccumulator` 는 단순한 큐가 아니라, 토픽의 각 파티션별로 독립된 큐(Queue)를 가지고 있다. 동일한 파티션으로 전송될 메시지들은 같은 큐에 순차적으로 쌓이게 된다.
  • 배치(Batch) 단위 저장: 각 파티션 큐는 여러 개의 레코드 배치(Record Batch)로 구성된다. 메시지들은 개별 단위로 저장되는 것이 아니라, 이 배치 안에 차곡차곡 쌓인다.
  • 물류 창고 비유: `RecordAccumulator` 는 "피자 주문" 메시지들과 "피자 도착" 메시지들을 각각 다른 목적지(파티션)별로 구분하여 배치로 묶어두는 물류 창고와 같은 역할을 한다.
send().get() 동기 호출의 내부 동작 동기 방식으로 send().get()을 호출하면, Main Thread는 메시지를 Record Accumulator에 넣은 후, Sender Thread가 해당 배치를 브로커로 전송하고 응답(ack)을 받아 Future 객체를 완료시킬 때까지 대기(wait)하게 된다.

6.4. 배치를 제어하는 핵심 옵션: batch.size와 linger.ms

 

Sender Thread가 언제, 얼마나 많은 메시지를 가져가서 보낼지는 두 가지 중요한 옵션으로 제어할 수 있다.

  • batch.size (기본값: 16384 byte)
    • Record Accumulator 내에서 생성되는 하나의 레코드 배치의 최대 크기를 바이트 단위로 지정한다.
    • 하나의 배치가 이 크기에 도달하면, Sender Thread는 linger.ms 대기 시간과 상관없이 즉시 이 배치를 가져가 브로커로 전송한다.
  • linger.ms (기본값: 0 ms)
    • Sender Thread가 배치를 가져가기 전 대기하는 최대 시간을 밀리초(ms) 단위로 지정한다.
    • batch.size가 아직 다 채워지지 않았더라도, linger.ms에 설정된 시간이 지나면 Sender Thread는 현재까지 모인 메시지만으로 배치를 구성하여 전송한다. 이는 메시지 전송량이 적을 때 불필요하게 오래 대기하는 것을 방지하여 지연 시간(Latency)을 줄이는 역할을 한다.
linger.ms 설정에 대한 고찰
- linger.ms=0 (기본값): 대기 시간이 없으므로 Sender Thread는 가능한 한 빨리 메시지를 전송하려 한다. 이는 지연 시간은 최소화되지만, 전송량이 적을 경우 작은 배치들이 자주 전송되어 처리량은 오히려 감소할 수 있다.
- linger.ms > 0: 약간의 지연 시간을 감수하는 대신, 더 많은 메시지를 하나의 배치로 묶어 보낼 확률을 높여 처리량을 향상시킨다. 네트워크 부하가 크거나 전반적인 전송이 느릴 때 이 값을 높여보는 것을 고려할 수 있다. 일반적으로 5~20ms 사이로 설정하는 것이 권장된다.

결론적으로, 이 두 옵션을 조절하여 처리량(Throughput)과 지연 시간(Latency) 사이의 균형을 맞추는 것이 Kafka Producer 튜닝의 핵심이다.


7. Producer의 동기(Sync)와 비동기(Async)에서 배치 전송 차이

Kafka Producer의 send() 메서드는 기본적으로 메시지를 배치(Batch) 단위로 모아 비동기(Async)로 전송한다. 하지만 개발자가 전송 결과를 어떻게 처리하느냐에 따라 배치 처리의 효율성이 크게 달라진다.

  • 비동기(Async) 방식: send() 메서드에 콜백(Callback)을 사용하는 비동기 방식은 Producer의 배치 처리 능력을 최대한 활용한다. Main Thread는 send() 호출 후 즉시 다음 작업을 수행하고, Sender Thread는 Record Accumulator에 메시지가 충분히 쌓일 때까지 기다렸다가 최적의 배치 단위로 묶어 전송한다. 이는 높은 처리량(Throughput)으로 이어진다.
  • 동기(Sync) 방식: 반면, send().get()을 사용하는 동기 방식은 배치 처리의 이점을 거의 살리지 못한다. Main Thread가 get() 호출 지점에서 멈춰서 응답을 기다리기 때문에, Sender Thread는 해당 메시지 단 한 건만을 포함한 배치를 즉시 전송해야만 한다. 결과적으로 전송은 배치 단위로 이루어지지만, 그 배치 안에는 메시지가 단 1개만 들어있어 사실상 개별 메시지를 보내는 것과 비슷해진다. 이러한 이유로 동기 방식은 성능이 중요시되는 환경에서는 거의 사용되지 않는다.

8. Producer의 타임아웃 및 재전송(Retry) 파라미터 이해

안정적인 데이터 전송을 위해 Producer는 다양한 상황에 대비한 타임아웃과 재전송 메커니즘을 가지고 있다. 이들은 여러 파라미터의 상호작용으로 동작한다.

[핵심 관계식]
delivery.timeout.ms >= linger.ms + request.timeout.ms
Producer는 이 관계식이 충족되는지 검증하므로, 설정 시 유의해야 한다.

8.1. 주요 파라미터의 역할

  1. delivery.timeout.ms (기본값: 120000ms, 2분): Producer가 메시지 전송을 시작해서 최종 성공/실패를 확정하기까지 허용된 총 시간이다. 최초 전송 시도부터 모든 재전송 시도를 포함하는 가장 상위의 타임아웃이다. 이 시간이 초과되면 Sender Thread는 더 이상 재전송을 시도하지 않고 예외를 발생시킨다.
  2. request.timeout.ms (기본값: 30000ms, 30초): Sender Thread가 브로커에게 단일 요청을 보낸 후 응답(ack)을 기다리는 최대 시간이다. 이 시간 내에 응답이 오지 않으면 요청은 실패한 것으로 간주하고 재전송을 시도한다.
  3. retry.backoff.ms (기본값: 100ms): 요청 실패 후 다음 재전송을 시도하기 전까지 대기하는 시간이다. 브로커에 일시적인 과부하가 걸렸을 경우, 즉시 재전송하여 부하를 가중시키는 것을 방지한다.
  4. max.block.ms (기본값: 60000ms, 1분): producer.send() 호출 시 Record Accumulator 버퍼가 가득 차 있을 때, Main Thread가 버퍼에 공간이 생길 때까지 대기하는 최대 시간이다. Sender Thread와 브로커 간 통신이 지연되어 버퍼가 비워지지 않는 상황에서 Main Thread가 무한정 대기하는 것을 방지한다. 이 시간이 초과되면 TimeoutException이 발생한다.

8.2. 재전송 시나리오

이 파라미터들이 실제로 어떻게 동작하는지 시나리오를 통해 살펴보자.

  1. 전송 실패 발생: Sender 스레드가 브로커에 메시지 배치를 전송했으나, request.timeout.ms(예: 30초) 내에 응답을 받지 못해 1차 실패가 발생한다.
  2. 재시도 준비: Sender 스레드는 즉시 재전송하지 않고, retry.backoff.ms(예: 100ms) 동안 잠시 대기한다.
  3. 재전송 실행: 대기 시간이 끝나면, Sender 스레드는 동일한 메시지 배치를 다시 브로커로 전송하고, 또다시 request.timeout.ms 동안 응답을 기다린다.
  4. 최종 실패 결정: 이 재전송 과정은 계속해서 반복될 수 있다. 하지만 최초 전송을 시도했던 시점부터 현재까지의 총 소요 시간이 delivery.timeout.ms(예: 2분)를 초과하게 되면, Sender 스레드는 더 이상의 재전송을 모두 중단하고 해당 메시지를 최종 실패로 처리하는 것이다.

한편, Main 스레드에서는 Sender 스레드가 이처럼 재시도를 반복하며 버퍼를 비우지 못하는 동안, send() 호출 시 버퍼가 가득 차 max.block.ms(예: 1분) 이상 대기하게 되면, Sender 스레드의 최종 실패 여부와 상관없이 먼저 타임아웃 예외를 발생시킬 수 있다.

8.3. Producer의 메시지 재전송 - retries와 delivery.timeout.ms

• retries와 delivery.timeout.ms 를 이용하여 재 전송 횟수 조정

• retries는 재 전송 횟수를 설정.

• delivery.timeout.ms 는 메시지 재전송을 멈출때 까지의 시간

• 보통 retries는 무한대값으로 설정하고 delivery.timeout.ms(기 본 120000, 즉 2분) 를 조정하는 것을 권장.

 

8.4. Producer의 메시지 재전송 – retries와 request.timeout.ms, retry.backoff.ms

• retry.backoff.ms는 재 전송 주기 시간을 설정

• retries=10, request.timeout.ms=10000ms, retry.backoff.ms=30인 경우 request.timeout.ms 기다린후 재 전송하기전 30ms 이후 재전송 시도. 이와 같은 방식으로 재 전송을 10회 시도하고 더 이상 retry 시도 하지 않음.

• 만약 10회 이내에 delivery.timeout.ms에 도달하면 더 이상 retry 시도하지 않음.


9. max.in.flight.requests.per.connection의 역할과 처리량

max.in.flight.requests.per.connection은 Producer의 처리량(Throughput)을 높이기 위한 중요한 옵션이다.

  • 정의: 하나의 커넥션에서, Producer가 브로커로부터 응답(ack)을 받지 않고도 동시에 보낼 수 있는 최대 요청(Request)의 수를 의미한다. 여기서 요청은 개별 메시지가 아닌 메시지 배치(Batch) 단위이다.
  • 동작: 이 값을 1보다 크게 설정하면, Producer는 첫 번째 배치의 응답을 기다리지 않고 바로 두 번째, 세 번째 배치를 연달아 보낼 수 있다. 이는 파이프라이닝(Pipelining)과 유사하게 동작하여 네트워크 왕복 시간(Round Trip Time)으로 인한 대기 시간을 줄여준다.
  • 기본값: 5이다. 즉, 기본적으로 Producer는 5개의 배치를 응답 없이 연속으로 보낼 수 있다.

이 설정은 처리량을 극대화하는 데 도움을 주지만, 특정 조건에서는 메시지 순서가 뒤바뀌는 문제를 야기할 수 있다.


10. Producer 메시지 전송 순서와 Broker 메시지 저장 순서 고찰

max.in.flight.requests.per.connection이 1보다 크고, 재전송(retry)이 발생하는 상황에서는 Producer가 보낸 순서와 다르게 메시지가 브로커에 저장될 수 있다.

10.1. 메시지 순서가 뒤바뀌는 시나리오 (위의 사진)

  1. 순차적 전송: Producer가 메시지 배치 B0를 먼저 보내고, B0의 응답을 기다리지 않고 즉시 다음 배치인 B1을 보낸다.
  2. 부분적 실패 발생: B1은 브로커에 정상적으로 저장되고, 브로커는 B1에 대한 성공 응답(ack)을 Producer에게 보낸다. 하지만 B0는 일시적인 네트워크 문제나 리더 브로커의 장애와 같은 재시도 가능한 예외(Retryable Exception)로 인해 브로커에 쓰이지 못하고 실패한다.
  3. 재전송: Producer는 B1의 성공 응답은 받았지만 B0의 응답은 받지 못했으므로, 타임아웃 이후 B0를 재전송한다.
  4. 순서 역전: 재전송된 B0는 이번에는 성공적으로 브로커에 저장된다. 하지만 브로커의 파티션 로그에는 이미 B1이 먼저 기록되어 있으므로, 최종적으로는 B1, B0 순서로 데이터가 쌓이게 된다. 이는 Producer가 의도한 순서(B0, B1)와 다르다.

10.2. 해결책: 멱등성 프로듀서 (enable.idempotence=true)

이러한 메시지 중복 및 순서 변경 문제를 해결하기 위해 Kafka 0.11 버전부터 멱등성 프로듀서 기능이 도입되었다.

  • 설정: enable.idempotence=true (최신 클라이언트에서는 기본값)
  • 동작 원리:
    • 이 옵션을 활성화하면, Producer는 각 메시지에 PID(Producer ID)와 시퀀스 번호(Sequence Number)를 부여하여 전송한다.
    • 브로커는 이 PID와 시퀀스 번호를 확인하여, 재전송으로 인해 발생할 수 있는 중복 메시지를 제거하고, 메시지 순서를 올바르게 보장한다.

enable.idempotence=true로 설정하면, Kafka는 메시지 순서 보장을 위해 자동으로 다음 설정을 강제한다.

  • max.in.flight.requests.per.connection을 5 이하로 설정
  • retries를 매우 큰 값으로 설정
  • acks를 all로 설정

따라서 특별한 이유가 없는 한, enable.idempotence=true 설정을 활성화 상태로 유지하는 것이 데이터의 정확성과 순서를 보장하는 가장 확실한 방법이다.


11. Kafka의 메시지 전송 보장 레벨 (Delivery Guarantees)

분산 메시징 시스템에서 "메시지를 얼마나 안정적으로 전달할 것인가"는 매우 중요한 문제이다. Kafka는 Producer의 설정에 따라 다음과 같은 세 가지 수준의 전송 보장(Delivery Guarantee)을 제공한다. 크게 최대 한번 전송(at most once), 적어도 한번 전송(at least once), 정확히 한번 전송(exactly once) 세 가지가 있다.

11.1. 최대 한번 전송 (at most once)

  • 설정: acks=0
  • 동작 방식:Producer는 브로커로부터 ACK(응답)이나 에러 메시지를 기다리지 않고, 메시지를 연속적으로 보낸다.
  • 특징:메시지를 빠르게 전송할 수 있지만, 네트워크 장애나 브로커 장애가 발생하면 메시지가 유실될 수 있다. 다만, 중복 전송은 일어나지 않는다.
  • 요약: "메시지가 아예 없을 수는 있어도, 중복되지는 않는다."

11.2.적어도 한번 전송(at least once)

 

  • 설정: acks=1 또는 acks=all
  • 동작 방식:Producer는 브로커로부터 ACK를 반드시 받은 뒤에 다음 메시지를 전송한다. ACK를 받지 못하면 같은 메시지를 재전송한다.
  • 특징:메시지가 유실될 가능성은 없지만, 네트워크 문제나 브로커 응답 지연 때문에 동일한 메시지가 여러 번 전송될 수 있다. 즉, 중복은 허용된다.
  • 요약: "메시지가 적어도 한 번은 전달되지만, 중복될 수 있다."

11.3. 중복 없이 전송(idempotence)

 

정확히 한 번만 메시지를 전달하는 것을 보장하는 방식이다. Kafka에서는 크게 두 가지 기능을 통해 이를 구현한다.

11.3.1. 멱등성(Idempotence)

  • 설정: enable.idempotence=true
  • 동작 방식:Producer는 메시지를 보낼 때 Producer ID와 메시지 Sequence 번호를 헤더에 함께 담아 보낸다.
    • Producer ID: Producer가 기동될 때마다 새로 생성된다.
    • Sequence 번호: Producer가 보낸 메시지마다 0부터 시작해 순차적으로 증가한다.
  • 브로커는 메시지를 저장할 때, 이미 저장된 Sequence 번호와 중복된다면 저장하지 않고 ACK만 반환한다.
  • 특징:이 방식으로 동일 메시지 중복 저장을 방지할 수 있다. 즉, 재전송이 발생해도 중복 기록이 없다.

11.3.2. 트랜잭션(Transaction)

  • 주로 Consumer → Process → Producer 패턴에서 사용된다. (Kafka Streams 같은 프레임워크에서 많이 활용된다)
  • 여러 개의 Producer 메시지를 하나의 트랜잭션 단위로 묶어, 모두 성공하거나 모두 실패하는 원자성(Atomicity)을 보장한다.
  • 예를 들어, Consumer가 메시지를 읽고 이를 처리한 뒤 Producer로 다시 전송하는 경우, 중간에 오류가 발생하면 전체 트랜잭션을 롤백하여 중복이나 데이터 불일치를 방지할 수 있다.
✅ 정리
at most once: 빠르지만 메시지 유실 가능 → 중복은 없음
at least once: 유실은 없지만 중복 가능 → 기본적으로 가장 널리 쓰임
exactly once: 중복도 유실도 없음 → 멱등성과 트랜잭션 기능으로 구현

12. Idempotence (멱등성)

Idempotence는 메시지를 중복 없이 정확히 한 번만 전송하기 위한 Kafka Producer의 핵심 기능이다.
Producer가 네트워크 오류나 브로커 장애로 인해 메시지를 재전송할 때도 브로커가 중복 메시지를 걸러내어 로그에 한 번만 기록되도록 한다.

12.1. Idempotence를 위한 Producer 설정

Idempotence를 활성화하려면 Producer 설정을 다음과 같이 해야 한다.

  • enable.idempotence = true
  • acks = all
  • retries > 0 (0보다 큰 값)
  • max.in.flight.requests.per.connection = 1 ~ 5

Idempotence를 적용하면 약간의 성능 저하(최대 20% 정도)가 있을 수 있지만, 메시지의 중복 문제를 예방할 수 있으므로 일반적으로 적용하는 것이 권장된다.

12.2. Idempotence 설정 시 유의 사항

  • Kafka 3.0 버전부터는 Producer의 기본 설정이 Idempotence이다.
  • 다만, 기본값 중 일부를 잘못 설정하면 Idempotence가 의도대로 동작하지 않을 수 있다.
    예를 들어 acks=1로 설정하면 Producer는 메시지를 정상적으로 보내지만, 이 경우 실제로는 Idempotence가 적용되지 않는다.
  • 반면 enable.idempotence=true를 명시적으로 설정했는데 다른 파라미터(acks=1 등)를 잘못 설정하면 Config 오류가 발생하고 Producer가 아예 기동되지 않는다.

즉, "자동으로 켜지니까 괜찮다"라고 생각하지 말고, 반드시 관련 파라미터들을 올바르게 맞추는 것이 중요하다.

12.3. Idempotence 기반에서 메시지 전송 순서 유지

  • Producer는 메시지를 배치 단위로 생성한다. 예를 들어, 메시지 B0, B1, B2가 순차적으로 배치된다.
  • Idempotence가 적용된 상태에서는 max.in.flight.requests.per.connection 값에 따라 여러 개의 배치가 동시에 브로커로 전송될 수 있다.
  • 브로커는 메시지를 저장할 때, 이미 기록된 마지막 메시지 Sequence + 1인 경우에만 정상적으로 저장한다.
  • 만약 순서가 맞지 않는 배치(예: B2가 먼저 도착)가 도착하면, 브로커는 OutOfOrderSequenceException을 발생시켜 Producer에 오류로 알린다.

이를 통해 메시지 순서를 어긋나지 않게 유지할 수 있다.

12.4. Idempotence 고찰

Idempotence는 "Producer ↔ Broker 간 메시지 전송 과정에서 중복 제거"만 보장한다. 하지만 몇 가지 한계도 있다.

  • 동일 메시지를 send()로 두 번 호출하면 이는 새로운 전송으로 인식되므로 Idempotence의 중복 제거 대상이 아니다.
  • Producer가 재기동되면 새로운 Producer ID가 부여된다. 따라서 이전 Producer가 보낸 메시지와 새 Producer가 보낸 메시지는 구분되지 않으므로 중복이 발생할 수 있다.
  • Consumer → Process → Producer 구조에서는 Idempotence만으로는 완벽한 보장이 어렵다.
    예를 들어, Consumer가 메시지를 읽었지만 offset을 커밋하기 전에 Producer가 메시지를 전송해버리면, Consumer가 다시 이전 offset부터 읽어 중복 메시지를 Producer를 통해 보낼 수 있다.

이런 경우에는 트랜잭션 기반 처리를 적용해야만 진정한 의미의 exactly-once processing을 보장할 수 있다.

✅ 정리
[1] Idempotence는 Producer의 메시지 재전송으로 인한 중복 문제만 해결한다.
[2] 단순한 중복 호출, Producer 재기동, Consumer-Producer 조합에서는 중복이 여전히 발생할 수 있다.
[3] 따라서 "전송 자체의 멱등성 보장"은 Idempotence로 충분하지만, "엔드 투 엔드(end-to-end) 정확히 한 번 처리"를 보장하려면 트랜잭션이 필요하다.

13. 커스텀 파티셔너(Custom Partitioner)

 

Kafka는 메시지를 토픽(topic) 단위로 저장하지만, 실제로는 토픽이 여러 개의 파티션(partition)으로 나누어져 있다. Producer가 메시지를 보낼 때, 메시지가 어느 파티션에 저장될지는 파티셔너(Partitioner)가 결정한다. 기본적으로 Kafka는 DefaultPartitioner를 사용하지만, 특정한 조건에 맞게 메시지를 특정 파티션으로 보내야 하는 경우 Custom Partitioner를 직접 구현할 수 있다.

13.1. Custom Partitioner로 메시지의 특정 Partition 설정하기

  • Kafka에서 파티셔너를 커스터마이징하면 메시지가 특정 파티션으로 들어가도록 로직을 직접 제어할 수 있다.
  • 예를 들어, 사용자 ID나 지역 코드에 따라 파티션을 나누고 싶을 때 Custom Partitioner를 활용한다.

13.2. 기본 메시지 파티셔닝 (Default Partitioner)

  • KafkaProducer는 기본적으로 DefaultPartitioner 클래스를 사용한다.
  • DefaultPartitioner의 동작 방식:
    • 메시지에 Key가 있는 경우 → Key를 해싱(Hashing)하여, 동일한 Key는 항상 같은 파티션에 매핑된다.
    • 메시지에 Key가 없는 경우 → 라운드 로빈 방식으로 파티션을 선택한다.
즉, 기본 파티셔너는 "키 기반 해싱 + 라운드 로빈" 방식을 통해 메시지를 균일하게 분산시킨다.

13.3. Producer의 메시지 Key값에 기반한 Custom 파티셔닝

기본 파티셔너만으로는 모든 요구사항을 충족할 수 없다. 예를 들어:

  • VIP 고객 메시지를 항상 특정 파티션에 기록하고 싶다.
  • 주문 ID의 마지막 숫자에 따라 파티션을 정하고 싶다.
  • 지역 코드(서울, 부산 등)에 따라 파티션을 나누고 싶다.

이런 경우, 메시지의 Key값을 기반으로 직접 파티셔닝 로직을 구현해야 한다.

13.4. Producer의 Custom Partitioning 클래스 구현하기

public class DefaultPartitioner implements Partitioner
public interface Partitioner extends Configurable, Closeable {

	int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);

	void close();

	default void onNewBatch(String topic, Cluster cluster, int prevPartition) { }
}

 

Kafka에서 커스텀 파티셔너를 만들려면 Partitioner 인터페이스를 구현해야 한다.

  • partition() 메소드
    → 메시지가 어느 파티션으로 들어갈지를 결정하는 핵심 메소드이다.
  • close() 메소드
    → 파티셔너 종료 시 리소스를 정리할 때 사용한다.
  • onNewBatch() 메소드
    → 새로운 배치가 시작될 때 호출되며, 기본적으로는 구현하지 않아도 된다.

13.5.  Default Partitioner vs Custom Partitioner 비교

구분 Default Partitioner Custom Partitioner
기본 동작 메시지에 Key가 있으면 해싱(Hashing), 없으면 라운드 로빈 방식으로 파티션 선택 partition() 메소드에 원하는 로직을 직접 구현하여 파티션 선택
파티션 결정 기준 Key 값(해싱) 또는 Key 없음 → 라운드 로빈 Key 값, 메시지 내용, 사용자 정의 조건(예: VIP, 지역 코드 등)
장점 균일한 분산, 별도 설정 필요 없음 특정 비즈니스 로직에 맞게 파티셔닝 가능
단점 세밀한 제어 불가능 (특정 파티션 강제 지정 불가) 직접 구현해야 하고, 잘못 구현 시 파티션 불균형 발생 가능
활용 사례 일반적인 메시지 분산 처리 특정 고객 그룹, 주문 ID, 지역별 처리 등 맞춤형 파티션 필요 시
설정 방법 기본값 (DefaultPartitioner) ProducerConfig.PARTITIONER_CLASS_CONFIG에 구현 클래스 등록

 


14. Producer → Broker 동작 흐름 최종 정리 (⭐)

14.1. 공통 동작 과정: send() 호출부터 버퍼 저장까지

결과를 처리하는 방식이 동기이든 비동기이든, 프로듀서의 send() 메소드가 호출되면 가장 먼저 아래의 과정이 순서대로 일어난다.

  1. producer.send() 호출 (by Main Thread)
    • 개발자가 작성한 메인 스레드의 코드에서 ProducerRecord 객체를 인자로 하여 kafkaProducer.send()를 호출한다.
  2. 직렬화 (Serialization)
    • send()가 호출되는 즉시, 프로듀서는 설정된 직렬화기(Serializer)를 사용하여 메시지의 key와 value를 바이트 배열(byte array)로 변환한다. 이는 브로커가 바이트 배열 형태의 데이터만 취급하기 때문이다.
  3. 파티션 결정 (Partitioning)
    • 파티셔너(Partitioner)는 이 메시지가 토픽 내의 여러 파티션 중 어느 곳으로 전송되어야 할지를 결정한다.
      • Key가 존재할 경우: Key의 해시값을 계산하여 특정 파티션에 고정적으로 할당한다. (hash(key) % num_partitions)
      • Key가 없을 경우: 스티키 파티셔닝(Sticky Partitioning) 전략에 따라, 하나의 배치가 가득 찰 때까지는 특정 파티션에 메시지를 집중시킨다.
  4. 레코드 누산기(RecordAccumulator)에 저장
    • 직렬화되고 목적지 파티션이 정해진 메시지는 네트워크로 즉시 전송되는 것이 아니다. 프로듀서 내부에 존재하는 메모리 버퍼, 즉 레코드 누산기(RecordAccumulator)에 우선적으로 쌓이게 된다. 이 버퍼는 각 파티션별로 독립된 큐(Queue)를 가지고 있어, 동일한 파티션으로 전송될 메시지끼리 그룹화된다.
이 단계까지의 모든 과정은 메인 스레드에 의해 매우 빠르게 처리되며, 아직 메인 스레드는 멈추지(Blocking) 않는다.

14.2. 백그라운드 작업자: Sender 스레드의 역할

메인 스레드가 메시지를 버퍼에 쌓아두는 동안, 프로듀서 내부에서 별도로 동작하는 Sender 스레드는 다음과 같은 핵심적인 임무를 수행한다.

  1. 배치(Batch) 구성
    • Sender 스레드는 레코드 누산기를 지속적으로 감시한다. 특정 파티션의 버퍼가 가득 차거나(batch.size), 지정된 대기 시간(linger.ms)이 지나면, 해당 파티션의 메시지들을 하나의 배치(batch)로 묶는다.
  2. 브로커로 전송
    • 구성된 배치를 해당 파티션의 리더(Leader) 브로커에게 네트워크를 통해 전송을 시도한다.
  3. 응답(Ack) 대기 및 실패 시 재시도
    • Sender 스레드는 브로커로부터 "메시지를 성공적으로 받았다"는 응답(Acknowledgment, Ack)을 기다린다.
    • 만약 전송이 실패하면, 프로듀서는 무조건 포기하는 것이 아니다. 브로커로부터 받은 에러가 일시적인 문제(Retriable Error), 예를 들어 네트워크 불안정(NETWORK_EXCEPTION)이나 일시적인 리더 부재(NOT_LEADER_OR_FOLLOWER)와 같은 경우, 자동으로 재전송을 시도한다.
    • 이 재시도 과정은 retries 설정값에 지정된 횟수만큼 반복되며, 재시도 사이에는 retry.backoff.ms 만큼의 대기 시간을 가진다.
    • 이 모든 재시도 과정이 실패로 돌아가야 비로소 최종적인 전송 실패로 간주하는 것이다.
  4. 최종 결과 처리
    • 전송에 성공했거나, 모든 재시도 끝에 최종적으로 실패했다면, 그 결과를 send() 메소드가 반환했던 Future 객체에 기록한다. 만약 콜백(Callback) 함수가 등록되어 있었다면, 해당 콜백 함수를 실행하여 결과를 전달한다.

14.3. 동기(Sync) 방식: send().get()

이는 "최종 결과를 받을 때까지 모든 것을 멈추고 기다리는 방식"이다.

  1. send() 호출 후 .get() 실행 (by Main Thread)
    • 메인 스레드는 kafkaProducer.send()를 호출하고, 그 즉시 반환된 Future 객체에 대해 .get() 메소드를 호출한다.
  2. 메인 스레드 멈춤 (Blocking)
    • .get()이 호출되는 순간, 메인 스레드는 그 자리에서 모든 작업을 멈추고 대기 상태에 들어간다.
  3. Sender 스레드의 결과 전달
    • 백그라운드에서 Sender 스레드가 브로커로부터 최종 응답(재시도가 있었다면 그 최종 결과까지 포함)을 받아 Future 객체에 결과를 채워 넣는다.
  4. 메인 스레드 작업 재개
    • Future 객체에 결과가 채워지는 순간, 멈춰있던 메인 스레드는 그 결과를 받고 나서야 다음 코드를 실행한다. 만약 재시도가 발생했다면, 그만큼 대기 시간은 길어지게 된다.
  • 특징: 메시지 전송의 최종 성공 여부를 확실하게 보장받을 수 있으나, 메인 스레드가 계속 멈춰있기 때문에 전체적인 처리량은 매우 낮아지는 것이다.

14.4. 비동기(Async) 방식: send(record, callback)

이는 "작업을 일단 맡겨두고, 나중에 결과만 알려달라고 하는 방식"이다.

  1. send() 호출 (by Main Thread)
    • 메인 스레드는 kafkaProducer.send()를 호출하며 콜백 함수를 함께 전달한다.
  2. 메인 스레드 즉시 다음 작업 수행 (Non-Blocking)
    • send() 메소드는 결과를 기다리지 않고 Future 객체를 반환한 뒤, 메인 스레드는 즉시 다음 코드를 실행하러 간다.
  3. Sender 스레드의 독립적인 작업 및 콜백 호출
    • 백그라운드에서 Sender 스레드가 브로커로부터 최종 응답을 받으면(재시도 과정이 모두 끝난 후), 약속된 콜백(Callback) 함수를 실행하여 결과를 처리한다. 이 모든 과정은 메인 스레드의 작업 흐름에 전혀 영향을 주지 않는다.
  • 특징: 메인 스레드가 멈추지 않아 매우 높은 처리량을 확보할 수 있다. 다만, 재시도 과정에서 첫 번째 메시지(A)가 실패하여 재시도 큐에 들어가고, 그 사이에 두 번째 메시지(B)가 먼저 성공하는 경우, 메시지의 순서가 B -> A로 뒤바뀔 수 있다는 점을 유의해야 한다. (이는 enable.idempotence=true 설정으로 해결 가능하다.)

'Kafka > Core' 카테고리의 다른 글

[ADVANCED #3] Consumer 심층 분석: 그룹 코디네이터부터 리밸런싱 전략까지  (0) 2025.09.09
[ADVANCED #2][실습] Producer 완전 정복: 기초부터 고급 설정  (0) 2025.09.08
[BASIC #8] Kafka: Java 클라이언트 구현 환경 구축  (0) 2025.09.07
[BASIC #7] Config 구분 및 이해: 카프카 설정의 계층 구조 파악하기  (0) 2025.09.07
[BASIC #6] Consumer의 핵심: Consumer Group과 리밸런싱 전략  (0) 2025.09.07
'Kafka/Core' 카테고리의 다른 글
  • [ADVANCED #3] Consumer 심층 분석: 그룹 코디네이터부터 리밸런싱 전략까지
  • [ADVANCED #2][실습] Producer 완전 정복: 기초부터 고급 설정
  • [BASIC #8] Kafka: Java 클라이언트 구현 환경 구축
  • [BASIC #7] Config 구분 및 이해: 카프카 설정의 계층 구조 파악하기
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250)
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25)
        • MSA 기본 (11)
        • MSA 아키텍처 (14)
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[ADVANCED #1] Producer 심층 분석: 내부 동작 원리와 고급 설정
상단으로

티스토리툴바