메시지 전송 보장 수준
Kafka Producer의 메시지 전송 보장 수준은 크게 "최대/최소/정확히 한 번" 세 가지로 나눠볼 수 있습니다.
최대 한 번 전송 (at most once)
acks=0
Broker 응답을 기다리지 않음
중복 전송 없음
유실 가능
최대 한 번 전송은 메시지가 0번 또는 1번만 전달되는 방식입니다. 즉, 중복 전송은 발생하지 않지만 메시지 유실은 발생할 수 있습니다. Producer는 Broker의 저장 성공 응답을 기다리지 않고 메시지를 전송된 것으로 간주합니다.
따라서 네트워크 장애, Broker 장애, Leader 변경 등의 이유로 메시지가 Kafka에 기록되지 않았더라도 Producer는 이를 알기 어렵습니다. 즉, 최대 한 번 전송은 중복을 허용하지 않는 대신 유실을 감수하는 방식입니다.

최소 한 번 전송 (at least once)
acks=1/all + retries>0
실패 시 재시도
최소 한 번 전송 특성
중복 가능
acks=all + min.insync.replicas
유실 가능성을 더 줄이는 안정성 설정
최소 한 번 전송은 메시지가 1번 이상 전달될 수 있도록 재시도하는 방식입니다. 이 방식에서는 메시지 유실 가능성을 줄이는 대신, 같은 메시지가 중복 전송될 수 있습니다.
Producer 관점에서는 Broker의 성공 응답을 기다리고, 실패나 타임아웃이 발생했을 때 재시도해야 합니다. 따라서 acks=1 또는 acks=all과 retries>0 조합에서 최소 한 번 전송의 특성이 나타납니다.
acks=1은 Leader Broker에만 메시지가 기록되면 Producer에게 성공 응답을 반환합니다. 따라서 Follower로 복제되기 전에 Leader 장애가 발생하면 해당 메시지가 유실될 수 있습니다. 이러한 유실 가능성을 줄이려면 acks=all을 사용하고, Topic 또는 Broker 설정에서 min.insync.replicas를 함께 설정하는 것이 좋습니다.
min.insync.replicas는 acks=all과 함께 사용할 때 의미가 있으며, ISR에 속한 복제본 중 최소 몇 개 이상에 기록되어야 성공으로 판단할지를 결정합니다. 반면 acks=1에서는 Leader 기록만으로 성공 응답을 받기 때문에, min.insync.replicas 값을 높여도 Producer의 성공 조건을 강화하지 못합니다.
- acks=1
- Leader Broker에만 기록되면 성공 응답을 받는 설정
- acks=all
- Leader Broker와 ISR 복제본들이 min.insync.replicas 조건을 만족할 때까지 기록된 뒤 성공 응답을 받는 설정
즉, 최소 한 번 전송은 "Producer가 실패한 메시지를 재시도한다"는 점이 핵심입니다. 이 과정에서 Kafka 로그에 같은 메시지가 중복 기록될 수 있으므로, Consumer나 애플리케이션에서는 멱등 처리를 별도로 고려해야 합니다.

정확히 한 번 전송 (exactly once)
정확히 한 번 처리는 메시지가 유실되지 않고, 중복 없이 한 번만 처리된 것처럼 보장하는 방식입니다. Kafka에서 이 개념을 설명할 때 enable.idempotence=true가 자주 등장합니다. 하지만 enable.idempotence=true만으로 Kafka의 전체 Exactly Once Semantics가 완성되는 것은 아닙니다.
enable.idempotence=true는 Producer retry로 인해 같은 메시지가 Kafka 로그에 중복 기록되는 것을 막는 설정입니다. Producer는 메시지를 보낼 때 Producer ID와 partition별 sequence number를 함께 보내고, Broker는 이 정보를 기준으로 중복 요청을 감지합니다. 멱등 Producer를 사용하려면 다음 설정 조건을 만족해야 합니다.
enable.idempotence=true (Producer retry로 인한 중복 기록 방지)
acks=all
retries > 0
max.in.flight.requests.per.connection <= 5
이 조합은 같은 Producer가 retry하는 과정에서 동일한 메시지가 Kafka 로그에 중복 기록되는 문제를 막습니다.

다만 이것이 모든 중복 문제를 해결한다는 뜻은 아닙니다. 서로 다른 Producer가 같은 비즈니스 이벤트를 중복 발행하거나, 애플리케이션 재기동 후 같은 이벤트를 다시 생성해서 발행하는 문제까지 막아주지는 않습니다.
Kafka에서 동일한 Producer란?
Kafka가 내부적으로 부여한 Producer ID를 기준으로 같은 Producer 세션을 의미합니다. 멱등성 Producer는 PID와 topic-partition별 sequence number를 함께 사용해, 같은 Producer가 retry로 동일 메시지를 다시 보내는 경우 broker가 중복 쓰기를 방지할 수 있게 합니다.
다만 Producer가 재시작되어 새로운 PID를 받거나, 애플리케이션이 같은 이벤트를 새 메시지로 다시 발행하는 경우까지 자동으로 중복 제거해 주는 것은 아닙니다.
Kafka의 EOS(Exactly Once Semantics)는 idempotent producer와 transaction을 함께 사용해 Kafka 내부의 consume-process-produce 흐름을 정확히 한 번 처리된 것처럼 보장하는 개념입니다. 예를 들어 order-created topic에서 메시지를 읽고, 처리 결과로 delivery-requested topic에 메시지를 발행하는 구조를 생각해볼 수 있습니다.
order-created consume
→ 비즈니스 처리
→ delivery-requested produce
→ order-created offset commit
이 흐름에서 결과 메시지 발행과 offset commit은 함께 성공하거나 함께 실패해야 합니다. 결과 메시지는 발행됐는데 offset commit 전에 장애가 발생하면, Consumer가 같은 메시지를 다시 읽고 결과 메시지를 중복 발행할 수 있기 때문입니다.
Kafka transaction을 사용하면 결과 메시지 발행과 offset commit을 하나의 transaction으로 묶을 수 있습니다.
delivery-requested 발행
+ order-created offset commit
= 하나의 Kafka transaction
Spring Kafka에서는 listener container에 KafkaTransactionManager를 설정해 이 흐름을 구성할 수 있습니다. listener가 정상 종료되면 KafkaTemplate으로 발행한 결과 메시지와 Consumer offset commit이 하나의 Kafka transaction으로 commit됩니다. listener에서 예외가 발생하면 transaction은 rollback되고, offset도 commit되지 않기 때문에 같은 메시지를 다시 읽게 됩니다. 정리하면 다음과 같습니다.
*enable.idempotence=true
Producer retry로 인한 중복 기록 방지
*Kafka Exactly Once Semantics
idempotent producer + transaction을 사용해,
Kafka 내부의 consume-process-produce 흐름을 정확히 한 번 처리된 것처럼 보장
Kafka transaction은 Kafka 내부의 메시지 발행과 offset commit을 묶는 기능입니다. 외부 DB 저장, 외부 API 호출, Redis write 같은 작업까지 자동으로 하나의 transaction으로 묶어주지는 않습니다. 따라서 외부 시스템이 함께 포함되는 구조에서는 unique key, 처리 이력 테이블, outbox pattern 같은 애플리케이션 레벨의 멱등성 설계가 필요합니다.
즉, 멱등 Producer는 정확히 한 번 처리를 구성하는 중요한 기반입니다. 하지만 그 자체만으로 모든 중복 문제를 해결하지는 않습니다.
| 보장 수준 | 의미 | 중복 | 유실 | 설정 |
| 최대 한 번 | 0번 또는 1번 전달 | 없음 | 발생할 수 있음 | acks=0 |
| 최소 한 번 | 1번 이상 전달 | 발생할 수 있음 | 줄일 수 있음 | acks=1/all, retries>0 |
| 정확히 한 번 처리 | 중복 없이 한 번 처리된 것처럼 보장 | 없음 | 없음 | idempotent producer + transaction |
Kafka Producer 설정만 놓고 보면 다음 조합이 안정적인 발행을 위한 기본 설정입니다.
- enable.idempotence=true
- acks=all
- retries>0
- max.in.flight.requests.per.connection<=5
하지만 실제 서비스에서는 Producer 설정만으로 충분하지 않습니다. 같은 비즈니스 이벤트가 여러 번 생성될 수 있고, Consumer가 같은 메시지를 다시 처리할 수도 있습니다. 따라서 Kafka 설정과 함께 애플리케이션 레벨의 멱등성 설계가 필요합니다.
enable.idempotence는 Producer retry 중복 기록을 막는다
enable.idempotence=true는 Kafka Producer의 멱등성을 활성화하는 설정입니다. 이 값을 true로 설정하면 Producer가 retry를 수행하더라도 동일한 메시지가 Kafka에 중복 기록되지 않도록 Broker가 방어합니다.
Producer는 메시지를 Broker에 전송한 뒤 ack 응답을 기다립니다. 그런데 Broker가 메시지를 정상적으로 기록했더라도 네트워크 장애나 타임아웃으로 인해 ack 응답이 Producer에게 도달하지 못할 수 있습니다.
Producer 입장에서는 메시지가 성공적으로 기록되었는지 알 수 없기 때문에 같은 메시지를 재전송할 수 있습니다. 멱등성이 꺼져 있다면 이 재전송으로 인해 같은 메시지가 Kafka 로그에 중복 append될 수 있습니다.
Producer 전송
Batch A → Broker 기록 성공
장애 상황
Broker ack 응답 유실
Producer는 실패로 판단
Batch A 재전송
멱등성이 꺼져 있는 경우
Batch A가 Kafka에 중복 기록될 수 있음
enable.idempotence=true를 사용하면 Producer는 메시지를 전송할 때 Producer ID와 partition별 sequence number를 함께 보냅니다. Broker는 이 정보를 기준으로 해당 Producer가 보낸 메시지의 순서를 추적합니다.
이미 처리한 sequence number가 다시 도착하면 중복 요청으로 판단해 다시 기록하지 않습니다. 반대로 예상보다 앞선 sequence number가 도착하면 순서가 깨진 것으로 판단해 오류로 처리할 수 있습니다.
이를 통해 Producer retry로 인한 중복 기록을 방지하고, 같은 Producer 인스턴스가 같은 partition으로 보내는 메시지의 순서도 안전하게 유지할 수 있습니다.
다만 여기서 주의할 점이 있습니다. enable.idempotence=true는 Producer retry로 인한 중복 기록을 막는 설정이지, 모든 비즈니스 이벤트 중복 발행을 막는 설정은 아닙니다. 예를 들어 서로 다른 Producer가 같은 비즈니스 이벤트를 각각 발행하거나, 애플리케이션 재기동 후 같은 이벤트를 다시 생성해서 발행하는 경우까지 Kafka Producer 멱등성이 막아주지는 않습니다.
막아주는 것
- 같은 Producer가 retry하면서 같은 batch를 중복 기록하는 문제
막아주지 못하는 것
- 서로 다른 Producer가 같은 비즈니스 이벤트를 발행하는 문제
- 애플리케이션 재기동 후 같은 이벤트를 다시 생성해서 발행하는 문제
- Consumer가 처리 후 offset commit 전에 장애로 같은 메시지를 다시 읽는 문제
따라서 비즈니스 이벤트 중복은 애플리케이션 레벨에서 별도로 방어해야 합니다. 예를 들어 eventId, outbox table, unique constraint, 처리 이력 테이블 등을 사용할 수 있습니다.
또한 enable.idempotence=true는 Consumer의 중복 처리까지 막아주지 않습니다. Consumer가 메시지를 읽어 DB 처리를 완료했지만 offset commit 전에 장애로 종료되면, 같은 메시지를 다시 읽을 수 있습니다. 따라서 Consumer 쪽 멱등성은 별도로 설계해야 합니다.
max.in.flight.requests.per.connection과 순서 보장
max.in.flight.requests.per.connection은 Producer가 하나의 Broker connection에 대해 ack를 받기 전까지 동시에 보낼 수 있는 요청 수를 의미합니다. 기본값은 5입니다.
예를 들어 이 값이 5라면 Producer는 Broker의 응답을 기다리는 중에도 최대 5개의 요청을 연속으로 보낼 수 있습니다. 값이 클수록 처리량에는 유리하지만, 특정 조건에서는 메시지 순서가 뒤바뀔 수 있습니다.
예를 들어 Producer가 같은 partition으로 Batch A와 Batch B를 순서대로 전송했다고 가정해보겠습니다. 이때 enable.idempotence=false이고 retry가 활성화되어 있다면, Batch A가 일시적인 네트워크 문제로 실패한 뒤 Batch B가 먼저 성공할 수 있습니다. 이후 Producer가 Batch A를 재시도해서 성공하면 Broker에는 원래 전송 순서와 다르게 Batch B가 먼저 기록되고, 그 뒤에 Batch A가 기록될 수 있습니다.
Producer 전송 순서
Batch A → Batch B
장애 상황
Batch A 실패
Batch B 성공
Batch A 재시도 성공
Broker 기록 순서
Batch B → Batch A
이 문제를 가장 단순하게 막는 방법은 max.in.flight.requests.per.connection=1로 설정하는 것입니다. 이 값이 1이면 Producer는 이전 요청의 ack를 받은 뒤에만 다음 요청을 보낼 수 있습니다. 따라서 retry가 발생하더라도 뒤 요청이 앞 요청보다 먼저 기록될 가능성을 줄일 수 있습니다.
다만 동시에 보낼 수 있는 요청이 하나로 제한되기 때문에 처리량은 떨어질 수 있습니다. 현재는 일반적으로 멱등 Producer를 함께 사용하는 방식이 더 많이 권장됩니다. 멱등 Producer를 사용하는 경우 max.in.flight.requests.per.connection은 5 이하로 설정되어야 합니다. Broker가 Producer 별로 최근 batch 상태를 추적하는 범위가 제한되어 있기 때문입니다.
즉, 예전에는 순서 보장이 중요하면 max.in.flight.requests.per.connection=1로 낮추는 방식이 자주 언급되었습니다. 하지만 멱등 Producer를 사용하는 경우에는 max.in.flight.requests.per.connection<=5 범위에서도 중복 방지와 파티션 내 순서 보장이 가능합니다. 따라서 중요한 메시지를 안정적으로 발행해야 한다면 보통 다음 조합을 함께 고려합니다.
enable.idempotence=true
acks=all
retries>0
max.in.flight.requests.per.connection<=5
Kafka Producer에서 enable.idempotence의 기본값은 true입니다. 다만 멱등 Producer가 정상적으로 동작하려면 acks=all, retries>0, max.in.flight.requests.per.connection<=5 조건을 만족해야 합니다. 이 조건과 충돌하는 설정을 사용하면 멱등성이 비활성화되거나, 명시적으로 enable.idempotence=true를 설정한 경우에는 설정 충돌로 Producer 생성 시 예외가 발생할 수 있습니다.
정확히 한 번 전송과 멱등성은 같은 말이 아니다
Kafka를 설명할 때 enable.idempotence=true를 정확히 한 번 전송이라고 표현하는 경우가 있습니다. 하지만 엄밀하게는 구분하는 것이 좋습니다. enable.idempotence=true는 Producer retry로 인한 중복 기록을 막는 설정입니다. 즉, 같은 Producer가 같은 partition으로 보낸 batch가 retry 때문에 Kafka 로그에 중복 append되는 것을 방지합니다.
반면 Kafka의 Exactly Once Semantics는 더 넓은 개념입니다. 여러 partition에 대한 원자적 쓰기, consume-process-produce 흐름에서의 offset commit과 produce 결과를 함께 다루는 트랜잭션까지 포함해서 이해해야 합니다.
즉, 멱등 Producer는 정확히 한 번 처리를 구성하는 중요한 기반이 될 수 있지만, 그 자체만으로 Consumer 처리 중복이나 비즈니스 이벤트 중복까지 모두 해결해주지는 않습니다.
결론
정리하면 Kafka의 메시지 전송 보장 수준은 acks, retries, enable.idempotence, transaction 조합에 따라 달라집니다. acks=0은 Broker 응답을 기다리지 않기 때문에 가장 빠르지만 메시지 유실을 감지하기 어렵고, acks=1 또는 acks=all과 retries>0 조합은 유실 가능성을 줄이는 대신 중복 전송 가능성을 가집니다.
안정적인 발행을 위해서는 일반적으로 enable.idempotence=true, acks=all, retries>0, max.in.flight.requests.per.connection<=5 조합을 사용합니다. 이 설정은 같은 Producer가 retry하는 과정에서 동일한 메시지가 Kafka 로그에 중복 기록되는 문제를 막고, 파티션 내 순서도 안전하게 유지하는 데 도움이 됩니다.
하지만 enable.idempotence=true가 곧 Kafka의 Exactly Once Semantics를 의미하는 것은 아닙니다. 멱등 Producer는 Producer retry로 인한 중복 기록을 막는 기능이고, Kafka의 Exactly Once Semantics는 idempotent producer와 transaction을 함께 사용해 Kafka 내부의 consume-process-produce 흐름에서 결과 메시지 발행과 offset commit을 하나의 트랜잭션으로 묶는 개념입니다.
또한 Kafka transaction은 Kafka 내부의 메시지 발행과 offset commit을 묶어줄 뿐, 외부 DB 저장, 외부 API 호출, Redis write 같은 작업까지 함께 보장하지는 않습니다. 따라서 실제 서비스에서는 Kafka Producer 설정만으로 충분하지 않고, eventId, unique key, 처리 이력 테이블, outbox pattern 같은 애플리케이션 레벨의 멱등성 설계가 함께 필요합니다.
결국 Kafka 설정은 메시지 유실과 중복 가능성을 줄이는 기반이고, 서비스의 최종 정합성은 Consumer와 애플리케이션의 멱등성 설계까지 함께 고려해야 완성됩니다.
'Server > Kafka' 카테고리의 다른 글
| [Kafka] Consumer Lag이 쌓였을 때 먼저 봐야 할 것들 (0) | 2026.06.19 |
|---|---|
| [Kafka] Producer acks, Consumer AckMode 그리고 Listener (0) | 2026.05.24 |
| [Kafka] Producer 메시지 전송 방식 (0) | 2026.05.03 |
| [Kafka] Consumer 동작 방식과 리밸런싱 (0) | 2026.03.31 |
| [Kafka] Producer 동작 방식과 파티셔닝 전략 (0) | 2026.02.02 |