본문 바로가기
1. 개발/1.4. 데이터 분석

Kafka - '최소 한 번(At-least-once)' vs '딱 한 번(Exactly-once)'

by 엉짱 2026. 2. 6.
반응형

드디어 분산 시스템의 성배(Holy Grail)라고 불리는 메시지 전송 보장(Delivery Guarantees)의 영역에 발을 들이셨군요! 🫡

카프카를 운영하다 보면 반드시 마주하게 되는 질문입니다. "데이터가 중복되어도 괜찮은가, 아니면 절대로 단 하나라도 틀리면 안 되는가?" 이 결정에 따라 시스템의 복잡도와 성능이 천차만별로 달라집니다. 특히 주식 종목이나 금융 데이터라면 '중복'은 곧 '재앙'이 될 수 있죠.

카프카가 이 까다로운 전송 보장을 어떻게 구현하는지 그 내부 메커니즘을 낱낱이 파헤쳐 보겠습니다!


🚚 메시지 전송 보장의 세 가지 단계

카프카와 컨슈머 사이에는 크게 세 가지의 약속 방식이 있습니다.

1. 최대 한 번 (At-most-once): "잃어버려도 난 몰라"

  • 원리: 컨슈머가 메시지를 읽자마자 "나 이거 읽었어!"라고 오프셋을 먼저 저장(Commit)해버립니다. 그 후 실제 데이터를 처리합니다.
  • 문제점: 만약 오프셋을 저장한 직후, 실제 데이터를 DB에 넣기 전에 컨슈머가 죽어버리면? 다시 살아난 컨슈머는 다음 번호부터 읽기 시작하므로, 그 사이의 데이터는 유실됩니다.
  • 사용 사례: 데이터가 누락되어도 시스템 전체에 큰 지장이 없는 로그 수집 등.

2. 최소 한 번 (At-least-once): "중복은 돼도 유실은 안 돼"

  • 원리: 컨슈머가 데이터를 가져와서 처리를 완전히 끝낸 후에(DB 저장 완료 등) 오프셋을 저장합니다. 카프카의 기본 설정이자 가장 많이 쓰이는 방식입니다.
  • 문제점: 데이터를 처리하고 DB에 저장까지 성공했는데, 오프셋을 저장하기 직전에 컨슈머가 죽으면 어떻게 될까요? 다시 살아난 컨슈머는 "어? 아까 그 데이터 아직 안 읽었네?"라고 판단하여 똑같은 데이터를 다시 처리하게 됩니다. 즉, 데이터 중복이 발생합니다.
  • 사용 사례: 대부분의 일반적인 서비스. (중복 처리를 로직으로 방어할 수 있는 경우)

3. 딱 한 번 (Exactly-once): "완벽한 결벽증"

  • 원리: 데이터의 유실도, 중복도 허용하지 않습니다. 시스템이 어떤 장애를 겪더라도 결과적으로 데이터는 단 한 번만 처리된 것과 동일한 효과를 냅니다.
  • 방법: 카프카의 트랜잭션(Transaction) API를 사용하거나, 멱등성(Idempotence) 설정을 통해 구현합니다.

🛠️ '딱 한 번(Exactly-once)'은 어떻게 가능한가? (Deep Dive)

사실 분산 시스템에서 '물리적으로 딱 한 번' 메시지를 보내는 것은 거의 불가능에 가깝습니다. 네트워크 오류는 언제든 발생하니까요. 그래서 카프카는 "결과적으로 딱 한 번만 반영되게 한다"는 전략을 씁니다.

① 프로듀서의 멱등성 (Idempotent Producer)

프로듀서 설정에서 enable.idempotence=true를 주면 마법이 시작됩니다.

  • 카프카는 각 프로듀서에게 고유한 PID(Producer ID)를 부여하고, 메시지마다 시퀀스 번호(Sequence Number)를 붙입니다.
  • 만약 프로듀서가 메시지를 보냈는데 응답을 못 받아서 재전송했을 때, 브로커가 "어? 이 번호 아까 받은 건데?"라고 판단하면 중복된 메시지를 저장하지 않고 버립니다. 쓰기 단계에서의 중복을 원천 차단하는 것이죠.

② 카프카 트랜잭션 (Transactions)

여러 토픽에 데이터를 쓰거나, 데이터를 읽고 쓰는 과정(Read-Process-Write)을 하나의 원자적 단위로 묶는 기능입니다.

  • Transaction Coordinator: 브로커 내부에서 트랜잭션을 관리하는 특수 프로세스입니다.
  • 동작: 모든 작업이 성공하면 Commit을 찍고, 하나라도 실패하면 Abort를 찍어 전체 작업을 무효화합니다. 컨슈머는 isolation.level=read_committed 설정을 통해 커밋된 데이터만 읽게 됩니다.

⚖️ 실전 선택 가이드

"그럼 무조건 '딱 한 번'이 좋은 거 아니야?"라고 물으실 수 있습니다. 하지만 세상에 공짜는 없죠.

구분 최소 한 번 (At-least-once) 딱 한 번 (Exactly-once)
성능 (Throughput) 매우 높음 (오버헤드 거의 없음) 낮음 (트랜잭션 관리 오버헤드 발생)
복잡도 낮음 (기본 설정으로 가능) 높음 (코딩 및 설정이 까다로움)
데이터 정합성 중복 발생 가능 (로직 대응 필요) 완벽 보장
추천 상황 대용량 로그, 트래픽이 몰리는 서비스 결제, 뱅킹, 주식 체결, 정산 시스템

💡 팁: "멱등적 컨슈머(Idempotent Consumer)"

실제로 '딱 한 번'을 구현하기 위해 카프카의 무거운 트랜잭션 API를 쓰는 대신, 엔지니어들은 컨슈머 쪽에서 중복을 걸러내는 전략을 많이 씁니다.

  1. Unique Key 활용: DB의 Primary KeyUnique Index를 메시지의 고유 ID로 설정합니다. 중복 데이터가 들어와도 DB 수준에서 거절되므로 결과적으로 '딱 한 번' 처리된 효과를 봅니다.
  2. Upsert 처리: 데이터를 새로 넣는 대신, 있으면 업데이트하고 없으면 넣는 Upsert 로직을 사용합니다. 여러 번 실행해도 최종 결과는 동일하므로 안전합니다. (이것을 멱등성이라고 합니다.)

📊 요약: "어떤 그릇을 선택하시겠습니까?"

  • 배달 사고가 나도 괜찮으니 빨리 배달해라: At-most-once
  • 배달 사고는 절대 안 되지만, 같은 물건이 두 번 와도 네가 알아서 처리해라: At-least-once (가장 추천!)
  • 배달 사고도, 중복 배달도 절대 안 된다. 비용이 많이 들어도 완벽해야 한다: Exactly-once

반응형