
리포트가 “가끔 사라진다”는 문제는 늘 애매하게 취급되기 쉽습니다. 하지만 PDF 글은 이를 단순 버그가 아니라 경쟁 조건, 이중 쓰기, 트랜잭션 경계가 얽힌 일관성 모델 문제로 끌어올립니다. 저는 여기에 실무 관점의 KPI, 운영 체크리스트, 관찰가능성 설계를 덧붙여 “도입 이후가 더 쉬운” 해법으로 정리하겠습니다.
Outbox로 ‘이중 쓰기’의 뿌리를 끊는 방법
PDF의 출발점은 “리포트 메시지 유실”이라는 아주 현실적인 품질 이슈입니다. 페이지 1~3의 다이어그램은 흐름을 직관적으로 보여줍니다. 대략적인 구조는 API Server가 메시지를 생성/전송하고, Report Server가 상태를 갱신하거나 리포트 처리를 이어받는 형태이며, 중간에 비동기 처리(이벤트/메시지 브로커)가 끼어 있습니다. 문제는 “DB에 상태를 쓰는 일”과 “이벤트를 발행하는 일”이 서로 다른 자원/경로를 타면서, 커밋 타이밍이 어긋날 수 있다는 점입니다. 이게 바로 글에서 강조하는 Dual Write Problem의 전형적인 출발점입니다(페이지 3~5의 문제 전개와 안티패턴 논지).
많은 팀이 첫 시도로 “트랜잭셔널 이벤트 리스너” 같은 형태를 떠올립니다. PDF도 유사한 접근을 다루면서, 왜 이것만으로는 충분하지 않은지(경쟁 조건, 커밋 시점, 이벤트 발행 시점의 불일치 가능성)를 보여줍니다(페이지 4~6). 특히 트랜잭션 격리 수준(예: READ COMMITTED)과 DB 커밋 타이밍이 얽히면, “이벤트는 나갔는데 상태는 아직 보장되지 않는다” 혹은 그 반대가 됩니다. 결과적으로, 소비자는 잘못된 순간에 상태를 읽거나, 상태가 영원히 그 단계로 가지 못한 채 드롭되는 시나리오가 생깁니다.
여기서 Transactional Outbox Pattern의 가치가 선명해집니다(페이지 7
9)은 Report Server가 상태를 갱신하면서 Outbox에 기록하고, 이후 Outbox를 통해 발행이 이루어지는 방향성을 잘 보여줍니다.
다만 저는 여기서 한 걸음 더 나아가 “의사결정이 쉬워지는 표”를 본문에 반드시 넣는 편을 권합니다. 사용자의 비평처럼, 정량 지표가 붙는 순간 글의 설득력이 급상승하기 때문입니다. 아래 표는 실무에서 바로 가져다 쓸 수 있는 ‘전/후 KPI 프레임’입니다. 숫자는 팀 상황에 맞춰 채우되, 항목 자체는 바꾸지 않는 편이 좋습니다.
| KPI 항목 | 개선 전(안티패턴 구간) | 개선 후(Outbox 적용) |
|---|---|---|
| 리포트 유실률 | 예: 간헐적(재현 어려움) | 예: 0에 수렴(예외는 운영 이슈로 전환) |
| 재시도 횟수/비율 | 예: 비정상 급증(원인 불명) | 예: 멱등 전제 하에 통제 가능 |
| 지연 P95/P99 | 예: 피크 타임에 튐 | 예: Outbox 적체 여부로 원인 분리 |
| DB 부하(쓰기/인덱스) | 예: 애플리케이션 산발적 쓰기 | 예: Outbox 중심으로 패턴화(튜닝 포인트 명확) |
이 표가 중요한 이유는, Outbox를 “도입했다/안 했다”가 아니라 “어떤 실패가 어떤 수치로 바뀌었는지”로 설명하게 해주기 때문입니다. 특히 Outbox 도입 이후에는 유실이 완전히 사라지기보다, 유실이 ‘적체, 재처리, 멱등 실패’ 같은 운영 가능한 범주로 바뀌는 경우가 많습니다. 즉, 문제를 제거했다기보다 “관찰·통제 가능한 형태로 변환했다”는 표현이 더 정확합니다.
멱등이 없으면 Outbox도 운영 지옥이 되는 이유
사용자 비평의 핵심은 “Outbox는 패턴 도입보다 운영이 진짜 난이도”라는 지점입니다. 저는 여기에 100% 동의합니다. Outbox 자체는 트랜잭션 경계를 정리해 주지만, 발행/소비가 at-least-once가 되는 순간 “중복”은 필연이 됩니다. 그리고 중복을 견디는 시스템은 멱등이 설계로 박혀 있어야 합니다. PDF가 SENT, REPORTED 같은 상태를 언급하며(페이지 8~9) 상태 갱신 흐름을 설명하는 이유도, 결국 멱등 가능한 전이 구조를 만들기 위해서입니다. 상태모델이 흐릿하면 “중복 이벤트가 들어왔을 때 같은 결과로 수렴”하기가 어려워집니다.
실무에서 멱등을 설계할 때는 “기술”보다 “키”가 중요합니다. 제가 추천하는 기준은 다음과 같습니다.
멱등키는 비즈니스 의미가 있어야 합니다: (message_id + report_type)처럼 재처리해도 동일 개체를 가리키는 조합이어야 합니다.
소비자에서의 멱등은 ‘검사→처리’가 아니라 ‘원자적 업데이트’로 끝나야 합니다: 예를 들어 “이미 REPORTED면 무시” 같은 조건은 동시성에서 흔들릴 수 있으니, 가능하면 조건부 UPDATE로 한 번에 끝내는 방식이 안전합니다.
멱등은 “중복을 허용”하는 게 아니라 “중복 비용을 상한”으로 만드는 일입니다: 중복이 생겨도 DB가 폭발하지 않게 인덱스/유니크 제약/적절한 TTL을 붙여야 합니다.
그리고 운영 체크리스트가 반드시 필요합니다. Outbox 테이블은 시간이 지나면 커집니다. 커지면 느려지고, 느려지면 적체가 생기고, 적체는 다시 지연 P99를 악화시킵니다. 따라서 아래 항목은 “선택”이 아니라 “초기 설계에 포함”되어야 합니다.
청소(Deletion/Archiving) 정책입니다: 성공 발행 후 N일 보관, 혹은 월 단위 파티셔닝 후 드롭 같은 전략을 명시해야 합니다.
재처리(replay) 전략입니다: 특정 기간/키 범위를 다시 발행하는 도구가 있어야 하며, 그 도구는 반드시 멱등과 세트여야 합니다.
중복 발행/중복 소비의 기준을 합의해야 합니다: at-least-once 전제를 문서로 못 박고, “중복은 버그가 아니라 정상”이라는 운영 마인드를 팀에 심어야 합니다.
컨슈머 지연이 사용자 경험에 미치는 영향 기준을 정해야 합니다: 예를 들어 “리포트 상태 반영 지연 5초까지 허용” 같은 SLO가 필요합니다.
여기서 제가 특히 강조하고 싶은 포인트는 “운영 지표가 곧 설계”라는 점입니다. Outbox를 도입했는데도 장애가 나는 팀은, 대체로 Outbox가 아니라 관찰/알람/청소/재처리의 부재로 무너집니다. 즉, 멱등과 운영 설계가 Outbox의 절반 이상입니다.
SW로 끝내는 경쟁 조건 정리와 관찰가능성 설계
PDF는 마지막 단계로 Single Writer Principle을 제시합니다(페이지 9). 저는 이 결론이 실무에서 특히 강력하다고 봅니다. 시스템이 커질수록 “누가 최종 상태를 쓸 권한이 있는가”가 모호해지고, 그 순간 경쟁 조건은 다시 고개를 듭니다. Report Server, API Server 등 여러 컴포넌트가 동일 리소스(리포트 상태)에 쓰기를 시도하면, 아무리 Outbox를 붙여도 “상태 결정권” 자체가 분산되어 있기에 경합이 완전히 사라지지 않습니다. 그래서 SW, 즉 단일 작성자 원칙은 단순한 코드 스타일이 아니라 ‘권한 모델’입니다.
SW의 요지는 “상태를 최종 확정하는 쓰기는 한 곳에서만 한다”입니다. 예를 들어 리포트 상태 전이는 Report Server만 담당하고, API Server는 요청을 수집하거나 커맨드를 기록하는 역할로 제한하는 식입니다. PDF의 다이어그램(페이지 9)은 이러한 역할 분리를 통해 Report Replayer 같은 구성요소가 Outbox 기반으로 안정적으로 상태를 수렴시키는 방향을 보여줍니다. 이때 설계가 좋아지는 이유는 다음과 같습니다.
경쟁 조건이 구조적으로 줄어듭니다: “두 곳이 동시에 상태를 바꾸는” 상황 자체가 사라집니다.
장애 분석이 쉬워집니다: 상태가 틀리면 ‘쓴 주체’가 단 하나이므로 추적 범위가 급격히 줄어듭니다.
트레이드오프가 명확해집니다: PDF도 마지막에 Trade-Offs를 언급하는데(페이지 10), SW는 대체로 쓰기 병목을 만들 수 있으나, 대신 일관성과 디버깅 비용을 크게 낮춥니다.
여기에 사용자가 지적한 Observability를 붙이면 글이 “아키텍처 설명”에서 “운영 가능한 설계 가이드”로 완성됩니다. 저는 리포트 같은 상태 전이 시스템에서 다음 3가지를 최소 요건으로 봅니다.
상태 전이 로그의 구조화입니다: message_id, 이전 상태, 새 상태, 전이 원인(이벤트 타입), 처리 주체(서비스명), 처리 소요시간을 필드로 남겨야 합니다.
메트릭의 분리입니다: “상태 전이 성공/실패”, “Outbox 적체량”, “Outbox 발행 지연”, “컨슈머 처리 지연”, “재처리 횟수”를 각각 분리해야 원인-결과가 섞이지 않습니다.
알람 기준의 SLO화입니다: 예를 들어 “Outbox 적체가 X분 이상 지속” 또는 “REPORTED 전이 지연 P99가 Y초 초과”처럼, 운영자가 행동할 수 있는 임계값으로 정의해야 합니다.
마지막으로, 사용자 비평에서 제안한 “상태모델 표”는 독자 이해 속도를 끌어올리는 최고의 장치입니다. 본문에서는 표를 KPI로 이미 사용했지만, 글 구성에서는 상태모델을 텍스트로라도 명확히 적는 편이 좋습니다. 예를 들어 SENT → REPORTED 전이는 어떤 이벤트/조건에서만 가능하며, “REPORTED는 되돌아가지 않는다” 같은 불변식을 선언해야 합니다. 이 불변식이 있어야 멱등도 단단해지고, 운영 중 재처리 도구를 써도 시스템이 안정적으로 수렴합니다.
리포트 유실 문제를 해결하는 진짜 핵심은 “패턴 이름”이 아니라, ①이중 쓰기를 없애는 데이터 경계(Outbox), ②중복을 견디는 멱등 설계, ③최종 쓰기 권한을 한 곳으로 모으는 SW, 그리고 ④그 모든 것을 측정 가능한 KPI와 알람으로 고정하는 운영 설계의 결합입니다.
결국 리포트 유실은 버그가 아니라 경계의 문제입니다. Outbox로 트랜잭션 경계를 정리하고, 멱등과 운영(청소·재처리·모니터링)로 at-least-once를 통제하며, SW로 쓰기 권한을 단일화해야 재발이 줄어듭니다. 여기에 전/후 KPI와 상태모델 정리가 붙으면 설득력과 실무 적용성이 함께 올라갑니다.
자주 묻는 질문 (FAQ)
Q. Outbox를 도입했는데도 지연이 늘어날 수 있습니까? A. 가능합니다. Outbox 적체(발행 지연), 인덱스 설계 미흡, 청소 부재로 테이블이 비대해지면 지연 P99가 악화될 수 있습니다. Outbox 적체량/발행 지연 메트릭과 보관·파티셔닝·삭제 정책을 함께 설계하는 것이 핵심입니다.
Q. at-least-once 전제에서 멱등은 어디에 구현하는 것이 좋습니까?
A. 소비자에 구현하는 것이 일반적으로 안전합니다. 중복 발행은 피하기 어렵기 때문에, “중복 이벤트가 와도 상태가 같은 곳으로 수렴”하도록 조건부 UPDATE, 유니크 제약, 멱등키를 조합하는 방식이 효과적입니다.
Q. Single Writer를 적용하면 쓰기 병목이 생기지 않습니까?
A. 가능성이 있습니다. 다만 병목의 위치가 명확해진다는 장점이 큽니다. 병목이 문제라면 샤딩(키 기반), 비동기 처리, 배치 전이, 상태 전이 최소화 같은 방식으로 확장 전략을 세울 수 있으며, 무엇보다 경쟁 조건으로 인한 품질 비용을 크게 줄일 수 있습니다.
[출처]
https://tech.kakao.com/posts/810
'IT' 카테고리의 다른 글
| 멀티모달 추론 가이드 (학습단계,비용,검증) (1) | 2026.02.13 |
|---|---|
| 모델 학습 설정 정리 (실험설계,튜닝,최적화) (0) | 2026.02.12 |
| 아이클라우드(iCloud) 사진 동기화 오류 시 단계별 조치 가이드 (0) | 2026.01.25 |
| 공공장소 무료 와이파이 사용 시 VPN을 반드시 써야 하는 이유 (1) | 2026.01.24 |
| 구글 드라이브 용량 부족할 때 중복 파일 한 번에 찾아 지우기 (0) | 2026.01.23 |