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

cocococo
3 min read

이 글의 목적

이 글은 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

동작 방식

  1. 프로듀서가 메시지를 전송하면 Kafka는 (PID, Partition, Seq, Epoch) 조합으로 메시지를 식별합니다.

  2. 동일한 세션(PID가 유지되는 동안)에는 중복 메시지를 감지하고 무시합니다.

  3. 하지만 프로듀서가 재시작되면 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 처리를 다룹니다.


필요하면 글 톤에 맞춰 요약 이미지나 시각 자료도 만들어줄 수 있어. 혹시 그럴까?

0
Subscribe to my newsletter

Read articles from coco directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

coco
coco