Outbox nümunəsi
Outbox, domen xidmətinin iş dəyişikliyini və müvafiq hadisəni öz anbarında bir yerli əməliyyatda qeyd etdiyi bir memarlıq nümunəsidir. Hadisənin xarici şinə/növbəyə dərc edilməsi «outbox» cədvəlini oxuyan və qeydləri yayımlayan ayrı bir təhlükəsiz proses (publisher) vasitəsilə asenxron şəkildə həyata keçirilir. Bu yanaşma «əvvəlcə DB-də, sonra şində» yarışı aradan qaldırır və uğursuzluqlar baş verdikdə belə etibarlı çatdırılma təmin edir.
1) Nə zaman tətbiq olunur
Uyğun:- Kontekstlər arasında hadisələr olan mikroservislər və modul monolitlər.
- Zəmanət tələb olunur ki, «hal qeydə alınıb, hadisə itirilə bilməz».
- İdempotentlik və nəzarət olunan təkrar çatdırılma lazımdır.
- Bir neçə resursda sərt qlobal əməliyyatlar kritik əhəmiyyət kəsb edir (açıq müqavilələrlə daha yaxşı TSS/dastan).
- Xüsusi həqiqət mənbəyi yoxdur (state hadisənin yarandığı yerdə saxlanılmır).
2) Məqsədləri və xüsusiyyətləri
Atomic write: domen qeydiyyatı + outbox - bir əməliyyatda.
At-least-once nəşri: təkrar etməyə icazə veririk, itkini istisna edirik.
İstehlakçı idempotentliyi: abunəçilər tərəfində dubllardan qorunma.
Effektiv exactly-once: outbox + idempotent consumer + dedup kombinasiyası ilə əldə edilir.
Aydın telemetriya: biznes əməliyyatları və hadisələrin korrelyasiyası.
3) Verilənlər sxemi (nümunə)
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) Əməliyyat şablonu (application layer)
pseudo begin tx domainChange () # INSERT/UPDATE in domain table insert into outbox (event) # event with aggregate/tenant commit tx keys
Əgər kommit uğurlu olarsa, outbox hadisəsi mövcuddur. Əgər proqram kommitdən sonra düşsə, ictimaiyyət onu tutacaq.
5) Publisher (reader → publisher)
Tapşırıqlar:- Yayımlanmamış hadisələri vaxtaşırı oxumaq ('published _ at IS NULL' və 'available _ at <= now ()'), batches.
- Şin/növbə dərc etməyə çalışın; müvəffəqiyyətlə - 'published _ at' qeyd edin.
- Səhv olarsa - 'attempts' artırmaq, 'available _ at' gələcəyə qoymaq (exponential backoff), 'error' yazmaq.
- Tenant/açar limitlərinə hörmət edin (fairness), məhsuldarlığı bloklamayın.
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) İdempotentlik və duplikasiya
İstehlakçı tərəfində (Inbox/Idempotency store):sql
CREATE TABLE inbox (
consumer_name TEXT,
event_id UUID,
processed_at TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);
Alqoritm: hadisə alındıqda - əvvəlcə 'INSERT' in 'inbox' cəhdi; açar münaqişəsi olarsa - hadisə artıq işlənmişdir → «no-op». Sonrakı - biznes məntiqi.
İctimai tərəfdə: 'Idempotency-Key' başlıqlarında (məsələn, 'event _ id'), belə ki, şin/broker/proxy dublikatları süzə bilər.
7) Sifariş və səbəb
'aggregate _ id' yerli qaydası 'occurred _ at' çeşidlənməsi və «açar vasitəsilə» dərc edilməsi ilə təmin edilir.
Partiyalaşdırma ilə log-şinlər üçün - 'aggregate _ id '/' tenant _ id' açarı ilə partiyalaşdırın ki, bir aqreqatın hadisələri bir partişendə olsun.
Sifariş kritikdirsə, ictimaiyyətin bir açar üzrə axınlararası yarışlarından çəkinin.
8) CDC (Change Data Capture)
Aktiv reklam əvəzinə CDC istifadə edə bilərsiniz: mühərrik DB əməliyyat jurnalını oxuyur və 'outbox' sətirlərini şinə ötürür. Üstünlüklər - minimum DB yükü, dəqiq ardıcıllıq, pollinq olmaması. Mənfi cəhətləri - əməliyyatın mürəkkəbləşməsi və DBB xüsusiyyətlərinə bağlanması. Hər iki yanaşma validdir; səlahiyyət və SLO seçin.
9) Səhvlər, DLQ və redrave
Retryable (şəbəkə, limitlər) - 'attempts' artırmaq, 'available _ at' (exponential backoff + jitter) təxirə salmaq.
Qeyri-retryable - zəngin metadata malik DLQ/Dead-Letter Topic-ə köçürülür.
Təhlükəsiz redrave: batchi, rate-limit, sxem validasiyası, proyad trafikdən aşağı prioritet.
10) Çox tenantlıq və limitlər
Məcburi etiketlər: 'tenant _ id', 'plan', 'region' - in 'outbox. headers`.
Per-tenant fairness: publisist «pəncərə» nəşrləri və kirayəçilər üçün cəhd limitləri paylayır.
Residency: domen məlumatlarının olduğu eyni bölgədə outbox saxlayın; regionlararası nəşr - yalnız aqreqatlar/hesabatlar.
11) Təhlükəsizlik və uyğunluq
tenant/region siyasəti payload/headers PII-edition.
Şin «yad» olduqda faydalı yük imza/şifrələmə.
Bütün dövlət keçidlərinin auditi: yaradıldı, nəşr olundu, səhv, redrave.
12) Müşahidə
Metriklər:- Post Lag ('now - occurred_at' p50/p95/p99).
- Uğurların payı, səhvlərin payı, səbəblərin bölüşdürülməsi.
- outbox ölçüsü (yayımlanmamış sayı), cəhd/san.
- throughput və lag.
- Korrelyasiya 'event _ id '/' aggregate _ id '/' saga _ id'; «db-tx», «publish», «retry» yataqları.
- Şərhlər: 'attempt', 'backoff _ ms', 'dlq = true'.
- Uğur üçün qısa qeydlər; səhv/redrave üçün tam detallar.
13) Test və xaos
Atomicity Test: dərc əvvəl domen əməliyyatı kommit sonra süni «düşür» - hadisə sonra getmək lazımdır.
Duplicate test: Eyni hadisəni bir neçə dəfə dərc edirik - konsumer tam bir effekti (inbox) yerinə yetirir.
Order test: Bir aqreqat üzrə hadisələr paketi - ardıcıllıq/idempotentlik testi.
Chaos: broker rədd, DB gizli artım, split-brain publichers, clock-skew.
14) Konfiqurasiya şablonları (nümunə)
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) Dastan və retrajlarla inteqrasiya
Outbox - dastan addımları üçün «təhlükəsizlik nəqliyyatı»: lokal əməliyyat effekti və komanda/hadisə yazır; nəşr - etibarlı və dozalı.
Təkrarlama və backoff siyasətləri 'Retry-After' və Circuit Breaker ilə razılaşdırılmalıdır; «retraj fırtınasından» qaçın.
16) Tipik səhvlər
Domain State Committen sonra bir hadisə yazın - yıxıldıqda itki mümkündür.
Heç bir indekslər/arxiv 'outbox' → nəşr gecikmə artım.
Heç bir 'SKIP LOCKED' və ya charding olmadan reklam - rəqabət və bloklama.
İstehlakçılarda idempotentliyin olmaması - ikiqat və yan təsirləri.
DLQ/log maskasız PII qarışdırılması.
fairness olmadan vahid qlobal nəşr növbəsi - «səs-küylü» tenant hər kəsi yavaşlatır.
Lag monitorinqinin olmaması → gizli deqradasiyalar.
17) Sürətli strategiya seçimi
Başlanğıc səviyyəsi: DB-dən polling, 100-500 batches, full-jitter backoff, konsumerlərdə inbox.
Yüksək yük: Əməliyyat jurnalından CDC, 'tenant _ id/aggregate _ id', kirayəçilər üçün WFQ.
Aqreqat üzrə ciddi sifariş: per key (mutex) seriya nəşri, topik açarı ilə partizan.
Complayens/PII: payload şifrələmə, DLQ redaktə, regional outbox.
18) Satış öncəsi yoxlama siyahısı
- Domen dəyişikliyi və 'outbox' yazısı bir əməliyyatda baş verir.
- Publisher batches emal edir, 'SKIP LOCKED', jitter və limitləri ilə backoff istifadə edir.
- Konsumerlər idempotentdir (cədvəl 'inbox '/dedup jurnalı).
- DLQ və təhlükəsiz redrave konfiqurasiya.
- P95/p99 həddinə görə lag/səhv və alertlər.
- Açar qaydasına zəmanət verilir (partiyalar/seriyalar).
- Arxiv/retenshn 'outbox' və nəşr qeydlərin təmizlənməsi.
- PII siyasətləri və status keçidlərinin auditi.
- Kommit və nəşr, təkrarlanan və sifariş arasında düşmə testləri.
- Hadisə müqavilələrinin sənədləşdirilməsi (sxemlər/versiyalar/uyğunluq).
Nəticə
Outbox-pattern «kövrək» bağlayıcı «BD şina» etibarlı konveyer çevirir: atom vəziyyəti fiksasiya, zəmanətli (hətta «ən azı bir dəfə») nəşr, idempotent abunəçilər və nəzarət redrayv. Düzgün telemetriya, limitlər və sxemlərin nizam-intizamı ilə o, praktik exactly-once davranış verir, paylanmış əməliyyatların mürəkkəbliyini azaldır və sistemin uğursuzluqlara və pik yüklərə davamlılığını artırır.