128 lines
11 KiB
Markdown
128 lines
11 KiB
Markdown
---
|
|
title: Transactional Outbox Pattern (microservices.io / Chris Richardson)
|
|
source_type: official-doc
|
|
url: https://microservices.io/patterns/data/transactional-outbox.html
|
|
archive_url:
|
|
status: raw
|
|
confidence: medium
|
|
tags: [ca-outbox-pattern, outbox, skip-locked, polling, baseline, microservices-io, official-doc]
|
|
related_projects: [ca-skeleton-operational-contract]
|
|
related_branches: [feature-domain-event-outbox-contract, feature-background-job-async-contract]
|
|
created: 2026-05-22
|
|
last_reviewed: 2026-05-27
|
|
---
|
|
|
|
# Transactional Outbox Pattern (microservices.io)
|
|
|
|
> Layer: `raw/official-docs/` — Chris Richardson microservices.io "Pattern: Transactional outbox" 의 **원문 발췌·출처 기록**.
|
|
> ca-tmpl 이 채택한 **DB outbox + polling + FOR UPDATE SKIP LOCKED** 결정의 baseline 정의 문서. 다른 대안들과 비교할 기준점.
|
|
|
|
## Parent / 활용 branch (필수)
|
|
|
|
| Branch | 이 자료가 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/branch-notes/feature-domain-event-outbox-contract]] | Topic 3 — Outbox Pattern baseline 채택 (SKIP LOCKED polling) 의 1차 근거 — Chris Richardson 원형 정의 |
|
|
| [[raw/branch-notes/feature-background-job-async-contract]] | Background job / async event 발행에서 outbox + polling 으로 dual-write 회피 결정 근거 |
|
|
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18 (Control Plane Contract) / §19 (Domain Application Readiness Contract) 의 outbox 발행 채택안 baseline |
|
|
|
|
## 컨텍스트
|
|
|
|
ca-tmpl 이 채택한 **DB outbox + polling + FOR UPDATE SKIP LOCKED** 결정의 baseline 정의 문서. business write + event write 를 동일 트랜잭션 안에서 묶고, 별도 Message Relay 가 outbox 를 polling 하여 broker 로 발행하는 패턴.
|
|
|
|
## 출처 / Source
|
|
|
|
- 원본 URL: https://microservices.io/patterns/data/transactional-outbox.html
|
|
- 보조 URL (Polling publisher 패턴): https://microservices.io/patterns/data/polling-publisher.html
|
|
- 아카이브 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/transactional-outbox.html` WebFetch 성공. 페이지에는 "Pattern: Transactional outbox" 제목 + Context/Problem/Forces/Solution/Result context/Related patterns/Learn more 섹션 존재.
|
|
- C1 (Solution): user 수집본 verbatim 과 **불일치**. 실제 페이지 표현은 "The solution is for the service that sends the message to first store the message in the database as part of the transaction that updates the business entities." → 의미는 동일하나 wording 다름. → user 수집본은 paraphrase 로 reclassify.
|
|
- C2 (Message Relay): 부분 일치. 실제 페이지 "A separate process then sends the messages to the message broker." → wording 차이.
|
|
- C3, C4 (Polling drawback, SKIP LOCKED): 1차 URL 의 본문 발췌에서 NOT FOUND. 보조 페이지 `https://microservices.io/patterns/data/polling-publisher.html` 도 WebFetch 했으나 두 문장 모두 NOT FOUND (해당 페이지는 high-level pattern description 만 포함).
|
|
- **재검증 한계**: WebFetch 페이지 발췌 범위가 항상 페이지 전체를 노출하지는 않을 수 있어 NOT FOUND 가 absence 의 결정적 증거는 아니나, 4개 quote 모두 verbatim 형태로 확인되지 않음 → 본 문서 인용은 모두 `needs-confirmation` Strength **유지** (Strength 상향 없음). C1/C2 는 paraphrase 가능성 노출, C3/C4 는 출처 페이지 재확정 필요.
|
|
|
|
## 핵심 인용 / Key quotes (verbatim claimed, user 수집본 [2026-05-25 capture] — [2026-05-27 verified attempt] verbatim 재확인 실패 / NOT FOUND, 아래 §재검증 결과 참조)
|
|
|
|
> [§Transactional outbox — Solution] "As part of the database transaction that updates the business entity, the service inserts a message into an OUTBOX table."
|
|
> — [2026-05-27 verified attempt]: 실제 페이지 wording 은 "The solution is for the service that sends the message to first store the message in the database as part of the transaction that updates the business entities." → 의미 동일, verbatim 불일치 → paraphrase 로 처리.
|
|
|
|
> [§Message Relay] "A separate Message Relay process publishes the events inserted into database to a message broker."
|
|
> — [2026-05-27 verified attempt]: 실제 페이지 표현 "A separate process then sends the messages to the message broker." → 부분 일치, wording 차이.
|
|
|
|
> [§Polling publisher — Drawback] "Polling the database is a simple approach that works reasonably well at low scale. The disadvantage is that frequently polling the database can be expensive."
|
|
> — [2026-05-27 verified attempt]: 1차 URL 및 보조 페이지 `polling-publisher.html` 본문 발췌에서 NOT FOUND.
|
|
|
|
> [§SKIP LOCKED parallelism] "Some databases, such as PostgreSQL and MySQL, support the SKIP LOCKED clause which enables multiple instances of the Message Relay to safely poll the OUTBOX table in parallel."
|
|
> — [2026-05-27 verified attempt]: 1차 URL 및 보조 페이지 `polling-publisher.html` 본문 발췌에서 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 |
|
|
|---|---|---|---|---|---|
|
|
| OUTBOX-MIO-C1 | business entity 를 변경하는 DB 트랜잭션의 일부로 OUTBOX 테이블에 메시지를 insert 한다 (원자성 확보) | [§Transactional outbox — Solution] "As part of the database transaction that updates the business entity, the service inserts a message into an OUTBOX table." | `needs-confirmation` | 단일 RDB 트랜잭션으로 business write + outbox write 가 가능한 모든 환경 | OUTBOX 스키마 (payload, aggregate_id, status 등) 의 구체적 컬럼 설계는 본 인용에 포함되지 않음 |
|
|
| OUTBOX-MIO-C2 | OUTBOX 테이블의 이벤트를 broker 로 발행하는 별도의 Message Relay 프로세스가 존재한다 | [§Message Relay] "A separate Message Relay process publishes the events inserted into database to a message broker." | `needs-confirmation` | outbox 패턴의 모든 변형 (polling, CDC 모두 Message Relay 를 가짐) | Message Relay 가 polling 인지 CDC 인지 본 인용은 중립 — 둘 다 가능 |
|
|
| OUTBOX-MIO-C3 | DB polling 방식은 단순하며 저-규모에서 합리적으로 동작하나, 자주 polling 하면 비용이 비싸다 | [§Polling publisher — Drawback] "Polling the database is a simple approach that works reasonably well at low scale. The disadvantage is that frequently polling the database can be expensive." | `needs-confirmation` | RDB outbox + polling Message Relay 변형 | "low scale" / "expensive" 의 정확한 임계 (TPS, interval) 는 본 인용에 없음 — 환경별 측정 필요 |
|
|
| OUTBOX-MIO-C4 | PostgreSQL 과 MySQL 같은 일부 DB 는 SKIP LOCKED 절을 지원하며, 이로 인해 여러 Message Relay 인스턴스가 OUTBOX 테이블을 병렬로 안전하게 polling 할 수 있다 | [§SKIP LOCKED parallelism] "Some databases, such as PostgreSQL and MySQL, support the SKIP LOCKED clause which enables multiple instances of the Message Relay to safely poll the OUTBOX table in parallel." | `needs-confirmation` | Postgres 9.5+ / MySQL 8.0+ 환경 | "safely poll in parallel" 이 순서 보장을 포함하지 않음 — 본 인용은 "안전" = lock contention 회피 의미만 |
|
|
|
|
### Strength 정책
|
|
|
|
본 문서의 모든 claim 은 `needs-confirmation`. 이유: 2026-05-27 재검증 시점에 WebFetch 가 차단되어 user 수집본 (2026-05-22) 의 verbatim 일치 여부를 공식 페이지 대조로 확인 못함. wiki 승급 전 재확인 필수.
|
|
|
|
## Usage Boundaries / 적용 경계
|
|
|
|
- **이 자료가 직접 증명하는 것** (재확인 시):
|
|
- `OUTBOX-MIO-C1`: outbox 패턴의 핵심 — business write + outbox write 의 트랜잭션적 결합
|
|
- `OUTBOX-MIO-C2`: Message Relay 분리 (구체 구현은 polling/CDC 중립)
|
|
- `OUTBOX-MIO-C3`: polling 의 trade-off (단순성 vs 비용)
|
|
- `OUTBOX-MIO-C4`: SKIP LOCKED 가 polling Message Relay 의 수평 확장 메커니즘
|
|
- **이 자료가 증명하지 않는 것**:
|
|
- outbox + polling 의 정확한 lag 수치
|
|
- OUTBOX schema 설계 (payload, status flag, partition key)
|
|
- SKIP LOCKED 가 순서 보장을 제공한다는 주장 (오히려 본 인용 + skip-locked-postgres-docs 의 "inconsistent view" 와 결합 시 순서 비보장)
|
|
- exactly-once delivery — outbox + polling 은 at-least-once (별도 근거 필요)
|
|
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
|
|
- ca-tmpl 의 polling interval 결정 (TPS · lag · DB 부하 균형)
|
|
- 다중 publisher 인스턴스 수 (운영 단순성 vs 처리량)
|
|
- outbox archive / partition / vacuum 정책 (hot table 방지)
|
|
|
|
## 메모 / Notes (내 프로젝트 해석 — 직접 인용 아님)
|
|
|
|
- 적용 시나리오: 모놀리식 / MSA 모두. CDC 인프라가 없거나 도입을 미루고 싶을 때, 한 DB 트랜잭션 안에서 business write + event write 를 묶고 싶을 때.
|
|
- 장점:
|
|
- 인프라 추가 없음 (DB 만 있으면 됨)
|
|
- business write 와 event write 의 원자성 보장 → dual-write 문제 회피
|
|
- SKIP LOCKED 로 다중 publisher 인스턴스 수평 확장 가능
|
|
- 구현 단순, 디버깅 쉬움 (SQL 로 직접 확인 가능)
|
|
- 단점:
|
|
- polling lag (interval 만큼 지연)
|
|
- polling 부하 (interval 을 줄이면 DB I/O 증가)
|
|
- outbox 테이블이 hot table 이 되기 쉬움 → 주기적 archive/delete 필요
|
|
- 순서 보장은 publisher 단일 인스턴스 또는 partition key 설계가 필요
|
|
- ca-tmpl (SKIP LOCKED polling) 과의 차이: **동일 패턴**. baseline.
|
|
- 운영 복잡도: 낮음. 추가 컴포넌트 없음.
|
|
- exactly-once / at-least-once 보장 수준: **at-least-once**. broker 발행 후 outbox row 삭제/마킹 사이에 크래시 시 중복 발행 가능 → consumer 측 idempotency 필수.
|
|
- 외부 의존성 추가 여부: 없음.
|
|
- 대안 그룹 (Topic 3 — Outbox Pattern, 대안 6종): **SKIP LOCKED polling** / Debezium CDC / Kafka Connect SMT / Dual-write [금지] / Event sourcing / Spring @TransactionalEventListener
|
|
- 본 source 의 위치: ca-tmpl 채택안 baseline (SKIP LOCKED polling, Chris Richardson 원형)
|
|
|
|
## Related / 관련
|
|
|
|
- 같은 주제 다른 official-doc:
|
|
- [[raw/official-docs/outbox-debezium-official-docs]] (대안 1: CDC)
|
|
- [[raw/official-docs/skip-locked-postgres-docs]] (메커니즘 — Postgres 공식)
|
|
- [[raw/official-docs/dual-write-antipattern-microservices-io]] (negative reference)
|
|
- 같은 주제 company-tech-blog:
|
|
- [[raw/company-tech-blogs/outbox-woowahan-techblog-pattern]]
|
|
- 인용하는 branch / project:
|
|
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
|
|
- [[raw/branch-notes/feature-background-job-async-contract]]
|
|
- [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
- 인용한 wiki 요약: (미작성)
|