Foundry
메시징
중급
핵심

메시지 전달 보장

유실과 중복 중 무엇을 택할까? at-least-once가 기본인 이유

메시지 전달 보장

세 가지 수준

  • at-most-once: 보내고 잊는다. ack가 없어도 재전송하지 않음 → 유실 가능, 중복 없음
  • at-least-once: ack를 못 받으면 재전송 → 유실 없음, 중복 가능. 실무 기본값
  • exactly-once: 정확히 한 번. 조건이 붙는 제한적 보장이다

커밋 순서가 등급을 결정한다

처리 순서결과
처리 → 오프셋 커밋at-least-once (처리 후 죽으면 재처리)
오프셋 커밋 → 처리at-most-once (커밋 후 죽으면 유실)

exactly-once의 흔한 오해

  • Kafka의 exactly-once는 트랜잭션으로 읽기-처리-쓰기가 Kafka 안에서 끝날 때 성립한다
  • 컨슈머가 외부 DB나 결제 API를 호출하는 순간 브로커의 보장은 거기서 끊긴다
  • 종단간 한 번은 결국 소비자 멱등성으로 만든다

실무 포인트

  • 기본 선택은 at-least-once + 멱등 소비자. exactly-once를 옵션으로 켜는 문제가 아니다
  • 유실이 허용되는 지표와 클릭 로그 수집은 at-most-once로 비용을 줄인다
  • ack 타임아웃이 실제 처리 시간보다 짧으면 정상 처리 중에도 중복이 폭증한다
면접에서 이렇게 나옵니다

Q.at-least-once와 exactly-once 중 무엇을 선택하시겠어요? 판단 근거를 설명해주세요

at-least-once 를 고르고 소비자를 멱등하게 만듭니다. 대부분의 실무 선택입니다.

보장의미대가
at-most-once중복은 없지만 유실 가능유실을 감수한다
at-least-once유실은 없지만 중복 가능소비자가 중복을 걸러야 한다
exactly-once정확히 한 번범위가 제한되고 비용이 크다

이유는 exactly-once 가 브로커와 소비자 사이의 특정 조건에서만 성립하기 때문입니다. 외부 API 호출이나 다른 DB 쓰기가 끼면 보장이 깨집니다.

그리고 중복 처리는 해결 방법이 명확합니다. 처리 키를 저장하고 이미 처리한 것을 건너뛰면 됩니다. 유실은 그런 사후 대응이 없습니다. 한 번 사라진 메시지는 복구할 방법이 없습니다.

at-most-once 를 고르는 경우도 있습니다. 실시간 위치 정보나 지표 수집처럼 다음 값이 곧 오는 데이터는 하나 잃어도 상관없고, 중복 처리 비용을 아끼는 편이 낫습니다.

흔한 실수: exactly-once 를 지원하는 브로커라 중복 처리를 안 해도 된다고 답하는 것. 그 보장이 어디까지 미치는지 확인해야 합니다.

Q.Kafka의 exactly-once가 외부 결제 API 호출까지 보장하지 못하는 이유는?

Kafka 의 보장은 읽기와 쓰기가 모두 Kafka 안에서 일어날 때 성립합니다. 소비한 오프셋 커밋과 결과 메시지 발행을 하나의 트랜잭션으로 묶는 방식이기 때문입니다.

외부 API 호출은 그 트랜잭션에 들어올 수 없습니다.

순서상황
1메시지를 소비한다
2결제 API 를 호출한다. 성공했다
3오프셋 커밋 전에 프로세스가 죽는다
4재시작하면 같은 메시지를 다시 받는다
5결제를 다시 호출한다

2번은 이미 되돌릴 수 없습니다. 외부 시스템은 우리 트랜잭션을 모릅니다.

해결내용
외부 API 에 멱등 키같은 키의 요청을 상대가 걸러낸다
우리 DB 에 처리 이력호출 전에 기록하고, 이미 있으면 건너뛴다
상태 조회호출 전에 상대에게 그 건의 처리 여부를 묻는다

첫 번째가 가장 확실합니다. 그래서 결제 API 들은 대개 멱등 키를 받습니다.

흔한 실수: 브로커의 exactly-once 를 종단 간 보장으로 읽는 것. 경계를 넘는 순간 보장은 끝납니다. 외부와 닿는 지점마다 멱등성을 따로 만들어야 합니다.

Q.오프셋 커밋을 처리 전에 하도록 바꾸면 어떤 문제가 생기나요?

처리 중에 죽으면 그 메시지는 사라집니다. 커밋된 오프셋 뒤부터 다시 읽기 때문입니다.

커밋 시점죽었을 때보장
처리 전그 메시지를 잃는다at-most-once
처리 후그 메시지를 다시 받는다at-least-once

처리 전 커밋이 나쁜 이유는 유실이 조용하다는 것입니다. 오류 로그도 없고 지표도 정상입니다. 나중에 데이터가 비어 있는 것을 발견하지만 어느 메시지였는지 알 수 없습니다.

자동 커밋을 쓰면 의도하지 않게 이 상태가 됩니다. 일정 간격으로 백그라운드에서 커밋하므로, 아직 처리하지 않은 메시지의 오프셋이 커밋될 수 있습니다.

안전한 구성내용
자동 커밋을 끈다처리 완료 후 명시적으로 커밋
묶음 처리라면묶음 전체 처리 후 마지막 오프셋을 커밋
실패 시커밋하지 않고 재시도하거나 실패 큐로 보낸다

흔한 실수: 처리 전 커밋을 성능 개선으로 여기는 것. 커밋 비용이 병목인 경우는 드물고, 필요하면 묶음 단위 커밋으로 횟수를 줄이는 편이 안전합니다.

Q.메시지 유실을 감수해도 되는 데이터는 어떤 기준으로 판단하나요?

다음 값이 곧 오는지, 없어진 것을 알아챌 수 있는지로 봅니다.

감수 가능이유
실시간 위치, 센서 값1초 뒤 다음 값이 온다
지표와 통계 표본표본이 하나 빠져도 결론이 바뀌지 않는다
캐시 무효화 알림만료 시간이 대신 처리한다
화면 갱신 알림사용자가 새로고침하면 된다
감수 불가이유
결제, 정산돈이 움직인다
주문 상태 변경다음 값으로 덮이지 않는다
재고 차감누적 계산이라 하나 빠지면 계속 어긋난다
감사 로그없어진 것을 증명할 수 없다

기준이 하나 더 있습니다. 누적 계산인지 최신값 덮어쓰기인지입니다. 재고 차감처럼 이전 값에 더하는 방식은 하나를 잃으면 영구히 어긋나지만, 위치 정보처럼 최신값으로 덮는 방식은 다음 값이 복구해 줍니다.

흔한 실수: 중요도로 판단하는 것. 중요한 지표도 표본 하나쯤은 잃어도 되고, 사소해 보이는 재고 이벤트는 하나도 잃으면 안 됩니다. 데이터의 성격이 기준입니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

메시징 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.