미리 보기 패턴
편집기는 도메인 서비스가 하나의 로컬 트랜잭션에서 비즈니스 변경 및 해당 이벤트를 저장소에 작성하는 아키텍처 패턴입니다. 이벤트를 외부 버스/큐에 게시하는 것은 'outbox' 테이블을 읽고 레코드를 릴레이하는 별도의 보안 프로세스 (게시자) 에 의해 비동기식으로 수행됩니다. 이 접근 방식은 "먼저 데이터베이스에 이어 버스에 대한 경쟁" 을 제거하고 실패시 안정적인 전달을 제공합니다.
1) 신청시기
적합:- 컨텍스트 사이의 이벤트가있는 마이크로 서비스 및 모듈 식 모놀리스.
- "상태가 고정되어 있는지 확인해야합니다".
- 우리는 dempotence와 통제 된 재전달이 필요합니다.
- 여러 자원에 대한 엄격한 글로벌 거래가 중요합니다 (명시 적 계약이있는 TCC/sagas보다 낫습니다).
- 전용 진실의 원천은 없습니다 (이벤트가 생성되는 곳에 상태가 저장되지 않음).
2) 목표와 속성
원자 쓰기: 한 번의 트랜잭션에서 도메인 레코드 + 아웃 박스.
적어도 한 번 출판: 우리는 반복을 허용하고 손실을 배제합니다.
소비자 demotence: 가입자 측의 수용 방지.
효과적인 정확히 한 번: 아웃 박스 + dempotent 소비자 + dedup의 조합으로 달성되었습니다.
명확한 원격 측정-비즈니스 거래 및 이벤트와 관련이 있습니다.
3) 데이터 스키마 (예)
sql
-- Domain table (example: orders)
CREATE TABLE orders (
id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL,
status TEXT NOT NULL,
total_amount NUMERIC(12,2) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
-- Outbox
CREATE TABLE outbox (
id UUID PRIMARY KEY, -- event_id aggregate_type TEXT NOT NULL, -- 'order'
aggregate_id UUID NOT NULL, -- order_id tenant_id TEXT NOT NULL,
type TEXT NOT NULL, -- 'OrderCreated'
payload JSONB NOT NULL, -- serialized headers event JSONB NOT NULL DEFAULT '{}':: jsonb,
occurred_at TIMESTAMP NOT NULL, -- time in domain transaction available_at TIMESTAMP NOT NULL, -- earliest publish time (backoff)
published_at TIMESTAMP, - is filled by the attempts INT NOT NULL DEFAULT 0,
error TEXT
);
CREATE INDEX ON outbox (available_at) WHERE published_at IS NULL;
CREATE INDEX ON outbox (tenant_id, available_at) WHERE published_at IS NULL;
4) 응용 프로그램 계층
pseudo begin tx domainChange () # INSERT/UPDATE in domain table insert into outbox (event) # event with aggregate/tenant commit tx keys
커밋이 성공하면 아웃 박스의 이벤트가 존재하도록 보장됩니다. 커밋 후 응용 프로그램이 삭제되면 게시자가 따라 잡습니다.
5) 출판사 (독자 → 출판사)
작업:- 게시되지 않은 이벤트 ('published _ at IS now ()' 및 'sable _ at <= now ()'), 배치를 주기적으로 읽습니다.
- 버스/큐에 게시하십시오. 성공하면 'published _ at' 로 표시하십시오.
- 오류의 경우-' 시도 '를 증가시키고 미래를 위해' 사용 가능한 _ at '(지수 백오프) 를 넣고' 오류 '를 작성하십시오.
- 세입자/키 (공정성) 에 대한 제한을 존중하고 제품을 차단하지 마십시오.
pseudo loop:
events = select from outbox where published_at is null and available_at <= now()
order by occurred_at limit BATCH_SIZE for update skip locked
for e in events:
try:
broker. publish(topicFor(e), serialize(e. payload), headers(e))
markPublished(e. id, now())
except Retryable:
backoff = computeBackoff(e. attempts)
reschedule(e. id, now()+backoff, attempts+1, last_error)
except NonRetryable:
moveToDLQ (e) or markError (e) # by sleep (POLL_INTERVAL) policy
6) 이념과 중복 제거
소비자 측 (받은 편지함/이데올로기 상점):sql
CREATE TABLE inbox (
consumer_name TEXT,
event_id UUID,
processed_at TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);
알고리즘: 이벤트를받을 때 먼저 '받은 편지함' 에서 'INSERT' 를 시도하십시오. 중요한 충돌이있는 경우 이벤트는 이미 → no-op로 처리되었습니다. 다음은 비즈니스 논리입니다
게시자 측에서: 버스/브로커/프록시가 중복을 필터링 할 수 있도록 헤더의 'Idempotency-Key' (예: '이벤트 _ id').
7) 질서와 인과 관계
'agregate _ id' 의 로컬 순서는 'argeed _ at' 을 정렬하고 "key" 를 게시하여 제공됩니다.
파티셔닝이있는 로그 버스-' agregate _ id '/' tenant _ id '키가있는 파티션의 경우 하나의 골재 이벤트가 동일한 파티션에 있습니다.
주문이 중요한 경우 교차 흐름 단일 키 게시자 레이스를 피하십시오.
8) CDC (데이터 캡처 변경)
활성 게시자 대신 CDC를 사용할 수 있습니다. 엔진은 데이터베이스 트랜잭션 로그를 읽고 '아웃 박스' 라인을 버스로 변환합니다. 전문가-데이터베이스의 최소로드, 정확한 순서, 폴링 없음. 단점-작동의 합병증 및 DBMS의 세부 사항과의 연계. 두 가지 접근 방식 모두 유효합 역량 및 SLO로 선택하십시오.
9) 오류, DLQ 및 재구동
재시도 가능 (네트워크, 한계) -' 시도 '를 늘리고' 사용 가능한 _ at '(지수 백오프 + 지터) 를 연기하십시오.
재시도 할 수없는 (유효하지 않은 체계/계약) -풍부한 메타 데이터가있는 DLQ/Dead-Letter Topic으로 이전됩니다.
안전한 재구동: 배치, 속도 제한, 계획의 검증, 생산 트래픽 이하의 우선 순위.
10) 다중 임대 및 한계
필요한 태그: 'outbox' 에서 'tenit _ id', 'plan', 'region' - 헤더 '.
임차인의 공정성: 출판사는 출판물의 "창" 과 세입자에게 시도의 한계를 배포합니다.
거주지: 도메인 데이터와 동일한 영역에 아웃 박스를 저장합니다. 지역 간 출판-집계/요약 만.
11) 안전 및 준수
테넌트/지역 정책에 대한 페이로드/헤더의 PII 버전.
버스가 외부 인 경우 페이로드의 서명/암호화.
모든 상태 전환을 감사합니다: 생성, 게시, 오류, 리 드레이브.
12) 관찰 가능성
메트릭:- 출판 지연 ('현재-where _ at' p50/p95/p99).
- 성공률, 오류율, 분포를 유발합니다.
- 보내기 크기 (게시되지 않은 숫자), 다시 시도/초
- 테넌트 당 처리량 및 지연 그래프.
- 상관 관계 '이벤트 _ id '/' 집계 _ id '/' saga _ id'; "db-tx", "게시", "재 시도" 에 걸쳐 있습니다.
- 주석: '시도', '백오프 _ ms', 'dlq = 참'.
- 성공을위한 짧은 기록; 오류/재생 당 자세한 내용.
13) 테스트와 혼돈
원자 테스트: 게시 전에 도메인 트랜잭션을 커밋 한 후 인위적으로 "낙하" -이벤트는 나중에 릴리스해야합니다.
중복 테스트: 동일한 이벤트를 여러 번 게시합니다. 소비자는 정확히 하나의 효과 (받은 편지함) 를 수행합니다.
주문 테스트: 이벤트 배치를 하나의 집계-시퀀스/dedempotence 점검.
혼돈: 중개인 실패, 데이터베이스 대기 시간 증가, 분할 뇌 게시자, 시계 왜곡.
14) 설정 템플릿 (예)
yaml outbox:
poll_interval_ms: 200 batch_size: 200 order_by: occurred_at backoff:
strategy: exponential_full_jitter initial_ms: 250 max_ms: 10_000 max_attempts: 20 fairness:
per_tenant_parallelism: 4 per_key_serial: true
publisher:
rate_limit_per_sec: 500 headers:
idempotency_key: event_id schema_version: v3 dlq:
enabled: true topic: myapp. events. dlq include_metadata:
- error
- attempts
- source_table
- tenant_id
- aggregate_id
15) 사가 및 퇴각과의 통합
전송 - 사가 단계를위한 "보안 전송": 현지 거래 작성 효과 및 명령/이벤트; 출판-신뢰할 수 있고 복용합니다.
반복 및 백오프 정책은 'Recuted-After' 및 Circuit Breaker와 일치해야합니다. "retray storm" 을 피하십시오.
16) 전형적인 오류
도메인 상태 커밋 후 이벤트를 작성합니다. 낙하 중 손실이 가능합니다.
'outbox' → 게시 대기 시간이 증가하지 않습니다.
'SKIP LOCKED' 없이 또는 경쟁 및 차단없이 게시자.
소비자들 사이의 demotency 부족-중복 및 부작용.
DLQ/로그에 마스킹하지 않고 PII 혼합.
공정성이없는 단일 글로벌 출판 대기열-" 시끄러운 "임차인은 모든 사람을 느리게합니다
지연 모니터링 부족 → 잠복 저하.
17) 빠른 전략 선택
시작 수준: 데이터베이스에서 폴링, 100-500 배치, 전체 지터 백오프, 소비자를위한받은 편지함.
높은로드: 트랜잭션 로그의 CDC, '테넌트 _ id/집계 _ id', 세입자의 WFQ
집계별로 엄격한 순서: 키 당 직렬 게시 (뮤텍스), 키가있는 주제의 파티션.
규정 준수/PII: 페이로드 암호화, DLQ 에디션, 지역 아웃 박스.
18) 사전 판매 점검표
- 도메인 변경 및 'outbox' 로 쓰기는 동일한 거래에서 발생합니다.
- 게시자는 배치를 처리하고 'SKIP LOCKED' 를 사용하며 지터와 한계로 백오프합니다.
- 소비자는 유쾌합니다 (표 '받은 편지함 '/데드 업 로그).
- DLQ 및 보안 릴리스가 구성됩니다.
- p95/p99 임계 값의 지연/오류 및 경고 메트릭.
- 키 순서가 보장됩니다 (배치/직렬).
- 아카이브/보존 '아웃 박스' 및 명확한 게시 된 레코드.
- PII 정책 및 주 전환 감사.
- 커밋과 게시, 중복 및 순서 사이의 테스트를 중단하십시오.
- 이벤트 계약 문서 (스키마/버전/호환성).
결론
아웃 박스 패턴은 "DB 버스" 의 "깨지기 쉬운" 번들을 신뢰할 수있는 파이프 라인으로 바꿉니다. 원자 상태 고정, 보장 된 ("적어도 한 번") 게시물, dempotent 가입자 및 제어 된 재 드레이브. 적절한 원격 측정, 한계 및 스키마 규율을 통해 실용적으로 정확하게 한 번에 동작하여 분산 트랜잭션의 복잡성을 줄이고 충돌 및 피크 부하에 대한 시스템의 복원력을 높입니다.