GH GambleHub

Outbox-model

Outbox este un model arhitectural în care un serviciu de domeniu scrie o schimbare de afaceri și evenimentul corespunzător într-o tranzacție locală în depozitul său. Publicarea evenimentului la autobuzul/coada externă se efectuează asincron printr-un proces securizat separat (editor) care citește tabelul 'outbox' și releează înregistrările. Această abordare elimină cursa „în primul rând la baza de date, apoi la autobuz” și oferă livrare de încredere, chiar și în caz de eșecuri.

1) Când se aplică

Fit:
  • Microservicii și monoliți modulari cu evenimente între contexte.
  • Este necesar să se asigure că „statul este stabilit ↔ evenimentul nu poate fi pierdut”.
  • Avem nevoie de idempotenta si re-livrare controlata.
Nu este potrivit:
  • Tranzacțiile globale dure pe mai multe resurse sunt esențiale (mai bune decât TCC/sagas cu contracte explicite).
  • Nu există o sursă dedicată de adevăr (starea nu este stocată acolo unde este generat evenimentul).

2) Obiective și proprietăți

Scriere atomică: înregistrare domeniu + outbox - într-o singură tranzacție.
Cel puțin o dată publicare: permitem repetarea, excludem pierderea.
Idempotența consumatorilor: protecția împotriva ia pe partea de abonat.
Eficace exact o dată: realizat prin combinația de outbox + consumator idempotent + dedup.
Telemetrie clară - Corelați tranzacțiile și evenimentele de afaceri.

3) Schema de date (exemplu)

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) Strat de aplicare

pseudo begin tx domainChange () # INSERT/UPDATE in domain table insert into outbox (event) # event with aggregate/tenant commit tx keys

Dacă angajamentul are succes, evenimentul din outbox este garantat să existe. În cazul în care aplicația scade după un angajament, editorul va prinde din urmă.

5) Editor (cititor → editor)

Sarcini:
  • Citiți periodic evenimente nepublicate ('published _ at IS NULL' și' available _ at <= now () '), loturi.
  • Încercați să publicați la autobuz/coadă; dacă reușește, marcați 'published _ at'.
  • În caz de eroare - crește "încercări", pune "available _ at' pentru viitor (backoff exponențial), scrie" eroare ".
  • Respectați limitele chiriașilor/cheilor (corectitudine), nu blocați produsul.
Pseudocodul:
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
💡 'PENTRU ACTUALIZARE SKIP LOCKED' elimină concurența editorilor.

6) Idempotență și deduplicare

Pe partea de consum (Inbox/Idempotency store):
sql
CREATE TABLE inbox (
consumer_name  TEXT,
event_id    UUID,
processed_at  TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);

Algoritm: când primiți un eveniment, încercați mai întâi să 'INSERT' în 'inbox'; dacă există un conflict cheie, evenimentul a fost deja gestionat → no-op. Următoarea este logica de afaceri.

Pe partea editorului: "Idempotency-Key" în anteturi (de exemplu, "event _ id'), astfel încât autobuzul/brokerul/proxy să poată filtra duplicate.

7) Ordinea și cauzalitatea

Ordinul local by 'agregate _ id' este furnizat prin sortarea' occured _ at' și publicarea „by key”.
Pentru log-autobuze cu partiționare - partiție cu 'agregate _ id'/' tenant _ id' cheie, astfel încât evenimentele de un agregat sunt în aceeași partiționare.
Dacă ordinea este critică, evitați cursele de editori cu o singură cheie.

8) CDC (Schimbarea capturii de date)

În loc de un editor activ, puteți utiliza CDC: motorul citește jurnalul de tranzacții de baze de date și traduce liniile „outbox” la bus. pro - încărcare minimă în baza de date, secvență exactă, fără sondaje. Dezavantaje - complicația funcționării și o legătură cu specificul DBMS. Ambele abordări sunt valabile; alege prin competențe și SLO.

9) Erori, DLQ și Redrive

Retryable (rețea, limite) - crește 'attempts', amânați' available _ at' (backoff exponențial + jitter).
Neretryable (invalid scheme/contract) - transferat la DLQ/Dead-Letter Topic cu metadate bogate.
Safe Redrive: loturi, rată-limită, validarea schemei, prioritate sub traficul de producție.

10) Multi-chirie și limite

Etichete necesare: 'chiriaș _ id',' plan ',' regiune '- în' outbox. anteturi ".
Corectitudinea per-chiriaș: editorul distribuie „ferestrele” publicațiilor și limitele tentativelor de chiriași.
Rezidență: stoca outbox în aceeași regiune ca și datele de domeniu; publicație interregională - numai agregate/rezumate.

11) Siguranță și conformitate

Ediție PII în sarcină utilă/anteturi pe politica chiriaș/regiune.
Semnătura/criptarea sarcinii utile dacă autobuzul este străin.
Audit toate tranzițiile de stat: creat, publicat, eroare, Redrave.

12) Observabilitate

Măsurători:
  • Publicarea decalajului ("acum - occurred_at' p50/p95/p99).
  • Rata de succes, rata de eroare, distribuția cauzei.
  • Dimensiune outbox (număr de nepublicat), retries/sec
  • Per-chiriaș grafice de debit și lag.
Vectorizare:
  • Corelație 'event _ id'/' aggregate _ id'/' saga _ id'; se întinde pe „db-tx”, „publish”, „retry”.
  • Adnotări: 'încercare', 'backoff _ ms',' dlq = adevărat '.
Busteni:
  • Înregistrări scurte pentru succes; detalii complete per eroare/redrave.

13) Testarea și haosul

Testul de atomicitate: artificial „cade” după ce a comis o tranzacție de domeniu înainte de publicare - evenimentul trebuie să fie lansat mai târziu.
Test duplicat: publicăm același eveniment de mai multe ori - consumatorul efectuează exact un efect (inbox).
Test de ordine: lot de evenimente cu un singur agregat - verificarea secvenței/idempotenței.
Haos: eșec broker, creșterea latenței bazei de date, editori split-creier, ceas-skew.

14) Șabloane de configurare (exemplu)

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) Integrarea cu sagas și retrageri

Outbox - „transport de securitate” pentru pașii saga: tranzacția locală scrie efect și comandă/eveniment; publicarea - fiabilă și dozată.
Repetarea și politicile de backoff trebuie să fie în concordanță cu „Retry-After” și Circuit Breaker; evita „furtuna din spate”.

16) Erori tipice

Ei scriu un eveniment după ce statul de domeniu comite - pierderea în timpul unei căderi este posibilă.
Nu există indici/arhivă în 'outbox' → creșterea latenței publicării.
Editor fără „SKIP LOCKED” sau fără sharing - concurență și blocare.
Lipsa idempotenței în rândul consumatorilor - duplicate și efecte secundare.
PII amestecare fără mascare în DLQ/busteni.
O singură coadă globală de publicare fără echitate - un chiriaș „zgomotos” încetinește pe toată lumea.
Lipsa monitorizării decalajului → degradarea latentă.

17) Alegerea rapidă a strategiei

Nivel de pornire: sondare din baza de date, 100-500 loturi, full-jitter backoff, inbox pentru consumatori.
Încărcare mare: CDC din jurnalul de tranzacții, sharding by 'chiriaș _ id/agregate _ id', WFQ by chiriaș.
Ordine strictă prin agregat: publicarea serială pe cheie (mutex), partiția subiectului cu o cheie.
Conformitate/PII: criptare sarcină utilă, ediție DLQ, outbox regional.

18) Lista de verificare pre-vânzare

  • Modificările de domeniu și scrie la 'outbox' apar în aceeași tranzacție.
  • Editorul se ocupă de loturi, utilizează „SKIP LOCKED”, backoff cu jitter și limite.
  • Consumatorii sunt idempotent (tabelul „inbox ”/deadup log).
  • DLQ și Secure Release sunt configurate.
  • Lag/eroare și indicatori de alertă pe pragurile p95/p99.
  • Comanda cheie este garantată (loturi/seriale).
  • Arhivă/retenție 'outbox' și înregistrări clare publicate.
  • politicile PII și auditul tranziției de stat.
  • Drop teste între comite și publică, duplicate și ordine.
  • Documentația contractului de eveniment (scheme/versiuni/compatibilitate).

Concluzie

Modelul outbox transformă pachetul „fragil” al „DB ↔ bus” într-o conductă fiabilă: fixarea stării atomice, publicarea garantată (deși „cel puțin o dată”), abonații idempotenți și redrave controlate. Cu o disciplină adecvată de telemetrie, limite și scheme, oferă un comportament practic exact o dată, reducând complexitatea tranzacțiilor distribuite și crescând rezistența sistemului la accidente și sarcini de vârf.

Contact

Contactați-ne

Scrieți-ne pentru orice întrebare sau solicitare de suport.Suntem mereu gata să ajutăm!

Telegram
@Gamble_GC
Pornește integrarea

Email-ul este obligatoriu. Telegram sau WhatsApp sunt opționale.

Numele dumneavoastră opțional
Email opțional
Subiect opțional
Mesaj opțional
Telegram opțional
@
Dacă indicați Telegram — vă vom răspunde și acolo, pe lângă Email.
WhatsApp opțional
Format: cod de țară și număr (de exemplu, +40XXXXXXXXX).

Apăsând butonul, sunteți de acord cu prelucrarea datelor dumneavoastră.