📚FOS Study
홈카테고리
홈카테고리
📚FOS Study

개발 학습 기록을 정리하는 블로그입니다.

바로가기

  • 홈
  • 카테고리

소셜

  • GitHub
  • Source Repository

© 2025 FOS Study. Built with Next.js & Tailwind CSS

목록으로 돌아가기
📨kafka

메시지 전송 신뢰성

약 4분
GitHub에서 보기

메시지 전송 신뢰성

  • 메시지 큐 시스템에서 가장 중요한 개념 중 하나인 전송 신뢰성에 대해 알아보자.
  • Kafka는 네트워크 오류나 서버 장애 등 다양한 변수 속에서 "메시지를 얼마나 확실하게 보낼 것인가"를 세 가지 단계로 정의한다.

At Most Once (최대 한 번)

메시지가 유실될 수는 있지만, 중복되지는 않는 방식

  • 동작 원리 : 프로듀서가 메시지를 보낸 후 브로커의 응답(ack)를 기다리지 않거나, 컨슈머가 메시지를 읽자마자 오프셋을 커밋해 버리는 경우이다.
  • 장단점 :
    • 장점 : 중복 처리를 고민할 필요가 없고 속도가 가장 빠르다.
    • 단점 : 장애 발생 시 메시지가 사라질 수 있다.
  • 사용 사례 : 대량의 로그 수집이나 센서 데이터처럼 한두 건의 데이터가 빠져도 시스템에 큰 타격이 없는 경우에 사용한다.

At Least Once (최소 한 번)

메시지가 절대 유실되지는 않지만, 중복될 수 있는 방식이다. Kafka의 기본 동작 방식이기도 하다

  • 동작 원리 :
    • 프로듀서는 브로커로부터 성공 응답을 받을 때까지 계속 재시도(Retry)한다.
    • 컨슈머는 메시지를 완전히 처리한 후에 오프셋을 커밋한다.
  • 중복 발생 시나리오 : 브로커가 메시지를 잘 저장했지만, 네트워크 문제로 프로듀서에게 응답을 못 보냈을 때, 프로듀서는 "전송 실패"로 간주하고 똑같은 메시지를 다시 보낸다.
  • 중요 : 이 방식을 채택할 때는 반드시 **컨슈머 측에서 멱등성(idempotency)**을 보장하도록 설계해야 한다.

Exactly Once (정확히 한 번)

메시지가 유실되지도 않고 중복되지도 않는 가장 이상적인 방식이다.

  • 동작 원리 : Kafka 0.11 버전부터 도입되었으며, Idempotent Producer와 Transaction API를 통해 구현된다.
    • Idempotent Producer : 프로듀서가 메시지마다 고유한 Sequence Number를 부여하여, 브로커가 중복된 번호의 메시지를 받으면 기록하지 않고 버린다.
    • Transactional Outbox : 여러 토픽에 메시지를 쓰거나 '읽기-처리-쓰기' 과정을 하나의 원자적 단위로 묶어 처리한다.
  • 사용 사례 : 결제 시스템, 금융 거래 등 데이터의 정합성이 무엇보다 중요한 핵심 비즈니스 로직에 사용한다.

세 가지 방식 한눈에 비교하기

방식유실 가능성중복 가능성난이도 / 비용주요 설정 예시
At-most-once있음없음낮음acks=0, enable.auto.commit=true
At-least-once없음있음보통acks=all, retries > 0
Exactly-once없음없음높음enable.idempotence=true, isolation.level=read_committed

개발자로서의 실무 팁

실무를 할 때에는 보통 At-least-once를 기본 전략으로 가져가되, 애플리케이션 계층에서 멱등성을 확보하는 방식이 가장 가성비가 좋다.


Exactly-once는 완벽해 보이지만, 트랜잭션 오버헤드로 인해 처리량이 다소 떨어질 수 있기 때문에 성능 요구사항을 잘 따져보고 적용하는 것을 추천한다.

추가 예시 : 슬롯 게임 개발 시, 하나의 스핀 결과를 여러 서비스에서 컨슘하여, 처리를 필요로 할 때 어떻게 해야할까?

  • 1. 개별 도메인 기반의 Idempotent Consumer

    • 각 스핀별 spinId를 각 도메인 (미션, 포인트) 테이블에 저장해 체크하는 방식이다.
    • 장점 : 데이터베이스 수준에서 정합성을 보장하므로 가장 확실하다. 비즈니스 로직과 중복 체크가 하나의 트랜잭션으로 묶여 안전하다.
    • 단점 : 모든 서비스 테이블마다 spin_id 컬럼과 인덱스가 필요하다.
  • 2. 전용 '중복 체크 테이블' 활용 (Dedup Table)

    • 비즈니스 테이블에 직접 컬럼을 추가하기 부담스러울 때 사용하는 방식
    • 구조 : 서비스마다 별도의 processed_event 테이블을 둔다.
    • 장점 : 비즈니스 테이블 구조를 깔끔하게 유지할 수 있고, 중복 체크 로직을 공통 라이브러리 (AOP 등)로 빼내기 쉽다.
  • 3. 더 효율적인 처리 : Redis 기반 필터링

    • 매번 DB를 조회하는 것이 성능상 부담스럽다면, DB 트랜잭션 진입 전에 Redis를 활용해 1차로 걸러낼 수 있다.
      • Check : Redis에 spinId가 있는지 확인 (SETNX 등 사용)
      • Process : 없다면 DB 트랜잭션 시작 및 로직 수행
      • Result : DB 처리가 성공하면 Redis 키의 만료시간을 충분히 길게 설정
    • 주의 : Redis는 '캐시'일 뿐이므로, 완벽한 정합성을 위해서는 최종적으로 DB의 Unique 제약 조건이 뒷받침 되어야 한다.
  • 4. 아키텍처적 대안 : 상태 기반 처리 (Stateful)

    • 만약 미션이나 포인트 서비스가 단순히 "누적"하는 구조라면, spinId를 일일히 기록하는 대신 시퀀스나 오프셋을 활용할 수도 있다
    • Last Processed ID : 각 유저별로 마지막으로 처리된 spinId를 저장한다.
    • 조건 : 새로 들어온 spinId가 기존 lastProcessedId 보다 작거나 같으면 무시한다. (이 방법은 spinId가 순차적으로 증가한다는 보장이 있을 때 매우 효과적이다)
kafka 카테고리의 다른 글 보기수정 제안하기
목차
  • 메시지 전송 신뢰성
  • At Most Once (최대 한 번)
  • At Least Once (최소 한 번)
  • Exactly Once (정확히 한 번)
  • 세 가지 방식 한눈에 비교하기
  • 개발자로서의 실무 팁
  • 추가 예시 : 슬롯 게임 개발 시, 하나의 스핀 결과를 여러 서비스에서 컨슘하여, 처리를 필요로 할 때 어떻게 해야할까?