GH GambleHub

Outbox үлгісі

Outbox - бұл домендік сервис өзінің қоймасына бір жергілікті транзакцияда бизнес-өзгеріс пен тиісті оқиғаны жазатын архитектуралық паттерн. Оқиғаны сыртқы шинаға/кезекке жариялау «outbox» кестесін оқитын және жазбаларды ретрансляциялайтын жеке қауіпсіз процеспен (publisher) асинхронды орындалады. Мұндай тәсіл «алдымен ДБ-ға, содан кейін шинаға» жарысты жояды және істен шыққан кезде де сенімді жеткізуді қамтамасыз етеді.

1) Қашан қолдану

Жарайды:
  • Контекстер арасындағы оқиғалары бар микросервистер мен модульдік монолиттер.
  • «Оқиға жоғалуы мүмкін емес күйде» деп кепілдік беру талап етіледі.
  • Демпотенттілік пен бақыланатын қайта жеткізу қажет.
Жарамсыз:
  • Бірнеше ресурстардағы қатаң жаһандық транзакциялар өте қиын (нақты келісімшарттары бар ТСС/сагадан жақсы).
  • Таңдалған ақиқат көзі жоқ (state оқиға пайда болған жерде сақталмайды).

2) Мақсаттары мен қасиеттері

Atomic write: домендік жазба + outbox - бір транзакцияда.
At-least-once жарияланымы: қайталауға жол береміз, жоғалтуды болдырмаймыз.
Тұтынушылардың біртұтастығы: жазылушылар жағындағы дубльдерден қорғау.
Тиімді exactly-once: outbox + idempotent consumer + 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) Транзакциялық үлгі (application layer)

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

Егер коммит сәтті болса - outbox оқиғасы бар. Егер бағдарлама коммиттен кейін құласа, паблишер қуып жетеді.

5) Паблишер (reader → publisher)

Тапсырмалар:
  • Жарияланбаған оқиғаларды ('published _ at IS NULL' және 'available _ at <= now ()'), батчалармен оқыңыз.
  • Шинаға/кезекке жариялауға тырысу; табыста - 'published _ at' деп белгілеу керек.
  • Қате пайда болғанда 'attempts' -ті ұлғайту, 'available _ at' -ті болашаққа қою (exponential backoff), 'error' деп жазу.
  • Тенанттар/кілттер бойынша лимиттерді құрметтеу (fairness), өнімді бұғаттамау.
Жалған құжат:
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
💡 'FOR UPDATE SKIP LOCKED' паблишерлердің бәсекелестігін болдырмайды.

6) Ұқсастық және дедупликация

Тұтынушы жағында (Inbox/Idempotency store):
sql
CREATE TABLE inbox (
consumer_name  TEXT,
event_id    UUID,
processed_at  TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);

Алгоритм: оқиғаны алған кезде - алдымен 'inbox' дегенге 'INSERT' деген әрекет; егер кілт қайшылығы болса - оқиға өңделді → «no-op». Бұдан әрі - бизнес-логика.

Паблишер жағында: 'Idempotency-Key' в headers (мысалы, 'event _ id'), шина/брокер/прокси телнұсқаларды сүзе алуы үшін.

7) Тәртіп және себептері

'aggregate _ id' бойынша жергілікті тәртіп 'occurred _ at' сұрыптауымен және «кілт бойынша» жарияланымымен қамтамасыз етіледі.
Партиялануы бар лог-шиналар үшін бір агрегаттағы оқиғалар бір партишенде болуы үшін 'aggregate _ id '/' tenant _ id' кілтімен партияланыңыз.
Егер тәртіп сыни болса, паблишердің бір кілт бойынша ағынаралық жарыстарынан аулақ болыңыз.

8) CDC (Change Data Capture)

Белсенді паблишердің орнына CDC қолдануға болады: қозғалтқыш БД транзакциялар журналын оқиды және 'outbox' жолдарын шинаға аударады. Плюстер - ДБ-ға ең аз жүктеме, нақты дәйектілік, поллингтің болмауы. Кемшіліктері - операцияны күрделендіру және ДББЖ ерекшелігіне байлау. Екі тәсіл де валидті; құзыреттілік пен SLO бойынша таңдаңыз.

9) Қателер, DLQ және редрайв

Retryable (желі, лимиттер) - ұлғайтамыз 'attempts', кейінге қалдырамыз 'available _ at' (exponential backoff + джиттер).
Non-retryable (салидті емес схема/келісімшарт) - бай метадеректері бар DLQ/Dead-Letter Topic-ке көшіріледі.
Қауіпсіз редрайв: батчи, rate-limit, схема валидациясы, прод-трафиктен төмен басымдық.

10) Көп теңгелілік және лимиттер

Міндетті тегтер: 'tenant _ id', 'plan', 'region' - 'outbox. headers`.
Per-tenant fairness: паблишер жарияланымдардың «терезелерін» және жалға алушылар бойынша әрекеттердің лимиттерін бөледі.
Residency: outbox бағдарламасын домен деректері орналасқан аймақта сақтаңыз; өңіраралық жарияланым - тек агрегаттар/мәліметтер.

11) Қауіпсіздік және сәйкестік

PII-редакция в payload/headers по политике тенанта/региона.
Егер «бөтен» шина болса, пайдалы жүктемені қолтаңбалау/шифрлау.
Жағдайдың барлық ауысуларының аудиті: жасалды, жарияланды, қате, редрайв.

12) Бақылау

Өлшемдері:
  • Жарияланым лаг ('now - occurred_at' p50/p95/p99).
  • Жетістіктер үлесі, қателер үлесі, себептерді бөлу.
  • outbox өлшемі (жарияланбаған саны), әрекеттер/сек.
  • throughput және lag.
Трейдинг:
  • 'event _ id '/' aggregate _ id '/' saga _ id' корреляциясы; «db-tx», «publish», «retry» спандары.
  • Аңдатпалар: 'attempt', 'backoff _ ms', 'dlq = true'.
Логи:
  • Жетістікке қысқаша жазбалар; толық бөлшектер қатеге/редрайв.

13) Тестілеу және хаос

Atomicity тест: Жарияланымға дейін домендік транзакция коммитінен кейін жасанды түрде «құлатамыз» - оқиға кейінірек шығуға міндетті.
Duplicate тест: Бір оқиғаны бірнеше рет жариялаймыз - консьюмер дәл бір эффектіні орындайды (inbox).
Order тест: бір агрегат бойынша оқиғалар пакеті - бірізділікті/теңсіздікті тексеру.
Chaos: брокердің бас тартуы, БД жасырындылығының өсуі, split-brain паблишерлері, clock-skew.

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) Сағалармен және ретралармен интеграциялау

Outbox - саганың қадамдары үшін «қауіпсіздік көлігі»: жергілікті транзакция әсер және команда/оқиға жазады; жарияланым - сенімді және дозаланатын.
Қайталау және backoff саясаты 'Retry-After' және Circuit Breaker бағдарламаларымен келісілуі тиіс; «ретрай дауылынан» аулақ болыңыз.

16) Типтік қателер

Домендік күй коммитінен кейін оқиға жазылады - құлау кезінде жоғалуы мүмкін.
'outbox' → ішінде индекс/архив жоқ.
'SKIP LOCKED' -сіз немесе шардарлаусыз паблишер - бәсекелестік және блоктау.
Тұтынушыларда іспеттіліктің болмауы - дубль және жанама әсерлер.
DLQ/логтарда PII бүркемелеусіз араластыру.
fairness жоқ жарияланымның бірыңғай жаһандық кезегі - «шулы» тенант баршаны тежейді.
Лаг мониторингінің жоқтығы → жасырын тозулар.

17) Стратегияны жылдам таңдау

Бастапқы деңгей: ДБ-дан поллинг, 100-500 батчиден, full-jitter backoff, inbox консюмерлерде.
Жоғары жүктеме: Транзакция журналынан CDC, 'tenant _ id/aggregate _ id' бойынша шардалау, жалға алушылар бойынша WFQ.
Агрегат бойынша қатаң тәртіп: per key (mutex) сериялық жариялау, топикті кілтпен партияландыру.
Комплаенс/PII: payload шифрлау, DLQ редакциясы, аймақтық outbox.

18) Азық-түлік алдындағы чек-парағы

  • Домендік өзгерістер мен 'outbox' жазбасы бір транзакцияда жасалады.
  • Паблишер батчиді өңдейді, 'SKIP LOCKED', джиттермен backoff және лимиттерді пайдаланады.
  • Консьюмерлер іспеттес ('inbox '/дедуп-журнал кестесі).
  • DLQ және қауіпсіз редрайв теңшелген.
  • p95/p99 табалдырықтары бойынша лаг/қателер метрикасы және алерталар.
  • Кілт бойынша тәртіпке кепілдік берілген (партия/серия).
  • 'outbox' мұрағаты/ретеншн және жарияланған жазбаларды тазалау.
  • PII-саясат және жай-күй ауысуларының аудиті.
  • Коммит пен жарияланым арасындағы құлау тестілері, телнұсқалар және тәртіп.
  • Оқиға келісімшарттарының құжаттамасы (схемалар/нұсқалар/үйлесімділік).

Қорытынды

Outbox-паттерн «морт» «ДБ шина» байланысын сенімді конвейерге айналдырады: жай-күйді атомарлық бекіту, кепілдік берілген (кем дегенде бір рет болса да) жарияланым, демпотенттік жазылушылар және бақыланатын редрайв. Дұрыс телеметрия, лимиттер және схема тәртібінде ол бөлінген транзакциялардың күрделілігін төмендете және жүйенің іркілістер мен ең жоғары жүктемелерге тұрақтылығын арттыра отырып, практикалық exactly-once мінез-құлқын береді.

Contact

Бізбен байланысыңыз

Кез келген сұрақ немесе қолдау қажет болса, бізге жазыңыз.Біз әрдайым көмектесуге дайынбыз!

Telegram
@Gamble_GC
Интеграцияны бастау

Email — міндетті. Telegram немесе WhatsApp — қосымша.

Сіздің атыңыз міндетті емес
Email міндетті емес
Тақырып міндетті емес
Хабарлама міндетті емес
Telegram міндетті емес
@
Егер Telegram-ды көрсетсеңіз — Email-ге қоса, сол жерге де жауап береміз.
WhatsApp міндетті емес
Пішім: +ел коды және номер (мысалы, +7XXXXXXXXXX).

Батырманы басу арқылы деректерді өңдеуге келісім бересіз.