Files
llm-wiki/raw/official-docs/dual-write-antipattern-microservices-io.md

11 KiB

title, source_type, url, archive_url, status, confidence, tags, related_projects, related_branches, created, last_reviewed
title source_type url archive_url status confidence tags related_projects related_branches created last_reviewed
Dual-Write Anti-pattern (microservices.io / 일반 정설) official-doc https://microservices.io/patterns/data/application-events.html raw medium
ca-outbox-pattern
dual-write
anti-pattern
failure-case
microservices-io
official-doc
ca-skeleton-operational-contract
feature-domain-event-outbox-contract
feature-background-job-async-contract
2026-05-22 2026-05-27

Dual-Write Anti-pattern (microservices.io)

Layer: raw/official-docs/ — microservices.io "Pattern: Application events" 및 "Transactional outbox" 페이지의 problem 섹션 원문 발췌·출처 기록. ca-tmpl 의 SKIP LOCKED outbox 결정에 대한 대안 3: dual-write (DB 와 broker 에 직접 동시 쓰기). 실패 케이스로 조사 — 왜 outbox 가 필요한가의 negative case.

Parent / 활용 branch (필수)

Branch 이 자료가 정당화하는 결정
raw/branch-notes/feature-domain-event-outbox-contract Topic 3 — Outbox Pattern 대안 매트릭스에서 dual-write 를 negative reference 로 채택한 결정의 1차 근거 — 분산 트랜잭션 불가 + 부분 실패 silent divergence
raw/branch-notes/feature-background-job-async-contract Background job 발행에서 save(); publish(); 직접 호출 패턴이 금지되는 근거
raw/project-notes/ca-skeleton-operational-contract §18 / §19 의 outbox 채택 정당화 — dual-write 의 명시적 금지 reference

컨텍스트

ca-tmpl 의 SKIP LOCKED outbox 결정에 대한 대안 3: dual-write (DB 와 broker 에 직접 동시 쓰기). 실패 케이스로 조사 — outbox 패턴 도입의 직접적 동기. dual-write 는 단일 DB + 단일 broker 환경에서도 atomic 보장이 불가능하며, 부분 실패 시 lost event / phantom event 가 발생한다.

출처 / Source

  • 원본 URL: https://microservices.io/patterns/data/application-events.html
  • 보조 URL: https://microservices.io/patterns/data/transactional-outbox.html (problem 섹션)
  • 보조: Confluent / Debezium 다수 글에서 동일한 "dual-write 금지" 메시지를 반복
  • 아카이브 URL: (미수집)
  • 저자 / 조직: Chris Richardson — microservices.io
  • 발행일: rolling docs (페이지 자체에 명시 없음)
  • 마지막 확인일 (capture): 2026-05-22
  • 마지막 재검증 시도: 2026-05-27
  • [2026-05-25 capture]: user 가 2026-05-22 수집한 인용 원형 유지 (재검증 보류 마커는 2026-05-25 부여된 상태).
  • 재검증 결과 [2026-05-27 verified attempt]:
    • 1차 URL https://microservices.io/patterns/data/application-events.html WebFetch 결과 REDIRECT 상태 — 페이지는 transactional-outbox.html 로의 redirect notice 만 남음. 즉 user 가 인용한 "Application events" 본문 자체가 이제 1차 URL 에서 직접 노출되지 않음 (microservices.io 가 페이지를 통합한 것으로 추정).
    • 보조로 redirect 대상 transactional-outbox.html 도 WebFetch 했으나 본 raw 의 3개 quote (Problem / Failure mode / Crash scenario) 는 모두 발췌 결과에서 NOT FOUND.
    • WebFetch 가 페이지 전체를 노출하지 않을 수 있어 NOT FOUND 가 absence 의 결정적 증거는 아니나, 출처 페이지 자체가 redirect 로 바뀌어 verbatim 위치를 더는 1차 URL 로 가리킬 수 없는 상황.
  • 재검증 한계 + Strength 정책: 출처 URL 의 redirect 발생 + 3개 quote 모두 redirect 대상 페이지에서 verbatim NOT FOUND → 본 문서 인용은 모두 needs-confirmation Strength 유지 (Strength 상향 없음). wiki 승급 전 다음 중 하나 필요: (a) archive.org 스냅샷으로 원본 "Application events" 페이지 wording 복원 + 인용 위치 확정, (b) 동등한 내용을 명시한 다른 1차 source (Chris Richardson 책 / Confluent / Debezium) 로 cross-reference.

핵심 인용 / Key quotes (verbatim claimed, user 수집본 [2026-05-25 capture] — [2026-05-27 verified attempt] 출처 URL redirect + verbatim NOT FOUND)

[§Application events — Problem] "A service often needs to atomically update the database and publish a message/event. It is not viable to use a distributed transaction that spans the database and the message broker." — [2026-05-27 verified attempt]: 출처 페이지 redirect → transactional-outbox.html 에서 NOT FOUND.

[§Application events — Failure mode] "Without 2PC, writing to the database and then publishing to a broker — or vice versa — can result in inconsistency if either step fails." — [2026-05-27 verified attempt]: redirect 대상 페이지에서 NOT FOUND.

[§Application events — Crash scenario] "Even if both calls succeed individually, a process crash between them leaves the system in an inconsistent state." — [2026-05-27 verified attempt]: redirect 대상 페이지에서 NOT FOUND.

Claims Extracted / 추출된 주장

본 raw 의 모든 quote 는 2026-05-22 user 수집본이며 2026-05-27 WebFetch 차단으로 verbatim 재확인 보류 → 전체 needs-confirmation.

Claim ID Claim (이 자료가 직접 말하는 것) Evidence quote Strength Applies to Does not prove
DUAL-WRITE-C1 서비스는 종종 DB 갱신과 메시지 발행을 atomic 하게 해야 하지만, DB 와 message broker 에 걸친 distributed transaction 사용은 viable 하지 않다 [§Application events — Problem] "A service often needs to atomically update the database and publish a message/event. It is not viable to use a distributed transaction that spans the database and the message broker." needs-confirmation DB + broker 를 동시에 다루는 모든 서비스 "어떤 환경에서도 절대 불가" 는 아님 — 본 인용은 일반적 viability 부정, 일부 broker 의 XA 지원은 별도 검증
DUAL-WRITE-C2 2PC 없이 DB 에 쓰고 broker 에 발행 (또는 그 반대) 하는 경우, 어느 한쪽이 실패하면 inconsistency 가 발생할 수 있다 [§Application events — Failure mode] "Without 2PC, writing to the database and then publishing to a broker — or vice versa — can result in inconsistency if either step fails." needs-confirmation 2PC 없이 DB + broker 를 순차 호출하는 모든 패턴 inconsistency 의 정확한 형태 (lost event vs phantom event) 분류는 본 인용에 포함되지 않음
DUAL-WRITE-C3 두 호출이 개별적으로 성공하더라도, 그 사이에 프로세스 크래시가 발생하면 시스템은 inconsistent state 가 된다 [§Application events — Crash scenario] "Even if both calls succeed individually, a process crash between them leaves the system in an inconsistent state." needs-confirmation DB commit 과 broker publish 사이의 임의 지점에서 프로세스 종료 가능한 모든 환경 crash recovery 메커니즘 (retry, compensation) 으로 이를 해결 가능한지 본 인용은 침묵 — outbox 가 그 해결책임은 별도 인용 (transactional outbox 페이지)

Strength 정책

본 문서의 모든 claim 은 needs-confirmation. 이유: 2026-05-27 재검증 시점에 WebFetch 가 차단되어 user 수집본 (2026-05-22) 의 verbatim 일치 여부를 공식 페이지 대조로 확인 못함. wiki 승급 전 재확인 필수.

Usage Boundaries / 적용 경계

  • 이 자료가 직접 증명하는 것 (재확인 시):
    • DUAL-WRITE-C1: 분산 트랜잭션 (DB + broker 2PC) 의 viability 부정 — outbox 도입의 1차 동기
    • DUAL-WRITE-C2: 순차 호출 시 부분 실패 = inconsistency
    • DUAL-WRITE-C3: 두 호출 사이의 crash 도 inconsistency 원인 (성공 호출만으로는 안전 불가)
  • 이 자료가 증명하지 않는 것:
    • 모든 broker (Kafka, RabbitMQ, SQS, ...) 가 2PC 를 지원하지 않는다는 절대 명제 (Kafka 는 transaction API 가 있지만 외부 DB 와의 2PC 는 별도 논의)
    • lost event 와 phantom event 의 명시적 분류 (메모 영역에서 해석 필요)
    • dual-write 가 모든 시나리오에서 항상 잘못된 선택이라는 일반화 (low-criticality 도메인에서 monitoring 으로 운영 가능한 케이스도 존재 — 본 인용은 silent divergence 위험만 지적)
  • 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
    • ca-tmpl 의 도메인 이벤트가 silent loss 를 허용할 수 없는 critical 도메인인지 확인 (그렇다면 outbox 채택이 정당화)
    • dual-write 가 잘못이라는 결론 자체는 microservices.io 1차 source 외에 Confluent / Debezium / Stripe 의 동일 메시지로 corroborate 가능 (다중 source 권장)

메모 / Notes (내 프로젝트 해석 — 직접 인용 아님)

  • 적용 시나리오: 권장하지 않음. 단일 DB + 단일 broker 라도 atomic 보장 불가.
  • 장점:
    • 코드가 가장 단순 (save(); publish();)
    • 인프라 추가 없음
  • 단점 (failure modes — 본 인용의 일반 inconsistency 명제로부터 도출):
    • DB commit 성공 + broker publish 실패 → 외부에는 이벤트 안 감, 상태만 변함 (lost event)
    • broker publish 성공 + DB commit rollback → 외부에는 발생하지 않은 이벤트 발행 (phantom event)
    • DB commit 성공 + 프로세스 크래시 → publish 안 됨 (lost event, DUAL-WRITE-C3 의 직접 결과)
    • 분산 트랜잭션 (XA/2PC) 은 broker 측 지원 미흡/성능 문제로 사실상 불가 (DUAL-WRITE-C1)
  • ca-tmpl (SKIP LOCKED polling) 과의 차이:
    • outbox 는 "이벤트도 DB 에 같이 쓴다" 로 atomic 문제를 회피
    • dual-write 는 이 atomic 문제를 그대로 노출 → outbox 도입의 직접적 동기
  • 운영 복잡도: 코드는 낮음, 장애 디버깅 비용은 매우 높음 (silent data divergence).
  • exactly-once / at-least-once 보장 수준: 보장 없음. lost / phantom 둘 다 가능.
  • 외부 의존성 추가 여부: 없음 (그러나 그 대가가 신뢰성 손실).
  • 결론: ca-tmpl 이 dual-write 를 피하고 outbox 를 택한 것은 정설. 이 문서는 "대안"이 아니라 "왜 outbox 를 골랐는가의 negative reference".
  • 대안 그룹 (Topic 3 — Outbox Pattern, 대안 6종): SKIP LOCKED polling / Debezium CDC / Kafka Connect SMT / Dual-write [금지] / Event sourcing / Spring @TransactionalEventListener
  • 본 source 의 위치: negative reference — Dual-write 금지