카프카 멱등성만으로 중복저장을 완전히 막을 수 있을까?

이 글의 목적
이 글은 Kafka 프로듀서 트랜잭션의 내부 동작 방식과 멱등성과의 관계를 중점적으로 다룹니다.
다루는 범위는 Producer → Broker 사이의 메시지 전송 레벨이며,
Kafka Consumer 및 외부 시스템까지 포함한 엔드 투 엔드 정합성은 별도 글에서 다룹니다.
🥥 Exactly-once semantics (Producer → Broker)
Kafka는 중복 메시지 저장을 방지하고, 여러 메시지를 하나의 단위로 처리하기 위해 다음 두 가지 기능을 제공합니다:
멱등성 (Idempotence)
트랜잭션 (Transactions)
이 둘은 목적과 범위가 다르며, 일부 시나리오에서는 함께 사용됩니다.
멱등성 (enable.idempotence=true
)
Kafka 브로커에 같은 메시지가 여러 번 저장되는 것을 방지하는 기능입니다.
주요 메타데이터
Kafka는 아래 정보를 바탕으로 중복 메시지를 식별합니다:
Producer ID (PID)
파티션별 시퀀스 번호 (Sequence Number)
Epoch: 멱등성만 사용하는 경우 항상
0
동작 방식
프로듀서가 메시지를 전송하면 Kafka는
(PID, Partition, Seq, Epoch)
조합으로 메시지를 식별합니다.동일한 세션(PID가 유지되는 동안)에는 중복 메시지를 감지하고 무시합니다.
하지만 프로듀서가 재시작되면 PID가 변경되어 중복 메시지가 저장될 수 있습니다.
[Producer]
│ (PID: 123, Seq: 0~N, Epoch: 0)
▼
[Kafka Broker]
└─ 중복 여부 체크: (PID + Partition + Seq)
└─ 동일 메시지 → 무시
└─ PID 바뀌면 → 중복 발생 가능
트랜잭션 (transactional.id
)
Kafka 트랜잭션은 여러 메시지를 논리적으로 하나의 단위로 묶어, 모두 저장되거나 아무것도 저장되지 않도록 보장합니다.
보장 사항
다중 메시지의 원자성 (All-or-Nothing)
프로듀서 재시작 후에도 중복 없이 메시지를 1회만 저장
멱등성과의 관계
트랜잭션은 내부적으로 멱등성을 전제로 설계되어 있습니다.
transactional.id
를 설정하면 Kafka는 자동으로enable.idempotence=true
를 활성화합니다.중복 메시지 판단 방식도 동일하게 PID + Sequence 기반입니다.
Epoch의 역할
트랜잭션을 사용하는 경우, Kafka는 Producer 세션을 식별하기 위해 Epoch 값을 관리합니다.
프로듀서가 재시작되면 이전보다 높은 Epoch 값으로 갱신됩니다.
브로커는 낮은 Epoch에서 온 메시지를 무효화(discard) 처리하여 중복 저장을 방지합니다.
트랜잭션 흐름 요약
[Producer]
└─ beginTransaction()
└─ send() * N
└─ commitTransaction()
▼
[Kafka Broker]
└─ TransactionCoordinator: 트랜잭션 상태 관리
└─ __transaction_state 토픽에 상태(COMMIT/ABORT) 기록
└─ 커밋된 트랜잭션만 Consumer가 읽을 수 있음
트랜잭션 코드 예시 (기본)
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
producer.send(record3);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
실전에서 주의할 트랜잭션 예외 및 복구 전략
트랜잭션은 강력한 기능이지만, 예외 처리 및 세션 관리를 제대로 하지 않으면 정합성이 깨질 수 있습니다.
commitTransaction()
호출 시 예외
이 시점에서 예외가 발생하면 해당 트랜잭션은 실패한 것으로 간주됩니다.
같은 트랜잭션을 재시도할 수 없으며, 반드시 새 트랜잭션을 시작해야 합니다.
abortTransaction()
호출 중 실패
abortTransaction()
도 실패할 수 있으며, 이 경우 Producer 세션 자체가 불안정해집니다.해결 방법:
producer.close()
후initTransactions()
로 세션 재초기화
자주 마주치는 예외
예외 이름 | 설명 |
ProducerFencedException | 같은 transactional.id 를 가진 중복 Producer가 존재할 때 발생 |
OutOfOrderSequenceException | 시퀀스 번호가 순서대로 전송되지 않았을 때 발생 |
TimeoutException | 트랜잭션 처리 시간이 너무 길어 세션이 만료되었을 때 발생 |
💡 위 예외 발생 시, 대부분 Producer를 재생성하고 트랜잭션을 재초기화해야 복구가 가능합니다.
멱등성과 트랜잭션 비교
항목 | 멱등성만 사용 | 멱등성 + 트랜잭션 사용 |
Epoch 사용 여부 | ❌ 항상 0 | ✅ 세션 재시작 시 증가 |
재시작 후 중복 방지 | ❌ 보장 안 됨 | ✅ 이전 세션 메시지 무효화 |
다중 메시지 원자성 | ❌ 없음 | ✅ All-or-Nothing 보장 |
중복 저장 방지 범위 | ✅ 세션 유지 중만 가능 | ✅ 세션 재시작 후까지 포함 |
브로커에 중복 메시지 저장 방지 | ❌ 제한적 | ✅ 완전 보장 |
이전 세션 중복 메시지 제거 | ❌ 불가 | ✅ Epoch 기반 무효화 |
🥥 결론
enable.idempotence=true
만으로도 기본적인 중복 방지는 가능하지만, 이는 세션 유지 중에만 유효합니다.프로듀서가 죽었다 살아나도, 여러 메시지를 하나의 논리 단위로 처리해도, 정확히 한 번만 저장되길 원한다면 트랜잭션을 사용해야 합니다.
실전에서는 트랜잭션 예외와 세션 관리 전략까지 함께 설계해야 완전한 정합성을 확보할 수 있습니다.
💡 다음 글에서는 Kafka Consumer와 외부 시스템까지 포함한 End-to-End Exactly-once 처리를 다룹니다.
필요하면 글 톤에 맞춰 요약 이미지나 시각 자료도 만들어줄 수 있어. 혹시 그럴까?
Subscribe to my newsletter
Read articles from coco directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
