백엔드 면접 용어 사전
메시지 큐at-least-once

At-Least-Once

메시지가 최소 한 번 전달되는 보장. 중복이 생길 수 있어 소비자 멱등성이 필요하다.

메시지가 최소 한 번은 전달된다는 보장. 유실은 없지만 중복이 생길 수 있다. 대부분의 브로커가 기본으로 제공하는 수준이다.

왜 중복이 생기는가 — 확인 시점의 틈

  • ① 브로커 → 소비자 : 메시지 전달
  • ② 소비자 : 처리 완료 (DB에 저장됨)
  • ③ 소비자 → 브로커 : ack 전송

②와 ③ 사이에 소비자가 죽으면 브로커는 ack를 못 받았으니 "아직 처리 안 됐다"고 판단해 다시 보낸다. 그런데 실제로는 이미 처리됐다 → 중복.

순서를 바꾸면 어떻게 될까?

  • ① 전달 → ② ack 먼저 → ③ 처리 이러면 ②와 ③ 사이에 죽었을 때 메시지가 사라진다(At-Most-Once). 유실과 중복 중 하나는 반드시 감수해야 한다 — 이것이 분산 시스템의 근본 제약이다.

세 가지 시맨틱

유실중복언제
At-Most-Once있음없음유실해도 되는 로그·메트릭
At-Least-Once없음있음대부분의 업무 처리
Exactly-Once없음없음조건부로만 성립

그래서 소비자를 멱등하게 만든다

방법 ① 처리 이력 테이블

INSERT INTO processed_message (message_id) VALUES (?);
-- 유니크 제약 위반 → 이미 처리한 것 → 건너뛴다

업무 처리와 같은 트랜잭션에 넣어야 한다. 따로 하면 그 사이에 죽었을 때 또 어긋난다.

방법 ② 연산 자체를 멱등하게

  • 잔액 = 1000 — ← 멱등 (몇 번 해도 같음)
  • 잔액 = 잔액 + 100 — ← 비멱등 (할 때마다 늘어남) 가능하면 절대값 설정으로 설계한다.

방법 ③ 조건부 갱신

UPDATE orders SET status = 'PAID'
WHERE id = ? AND status = 'PENDING';   -- 이미 PAID면 0행 → 무시

상태 전이를 조건으로 걸면 두 번째 실행이 자동으로 무효가 된다.

면접 답변 골격

"브로커는 at-least-once를 주므로 중복은 전제하고 설계합니다. 메시지에 고유 ID를 두고 처리 이력을 업무 트랜잭션과 함께 기록하거나, 상태 전이를 조건부 UPDATE로 만들어 두 번째 처리가 무해하게 합니다."

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 메시지 큐·비동기 — Kafka/RabbitMQ·EOS·순서·outbox