Transactional Outbox — database’dagi business o‘zgarish va tashqi brokerga yuborilishi kerak bo‘lgan event haqidagi yozuvni bitta local transactionda saqlaydigan integration pattern. Keyin alohida publisher outbox table’dan eventlarni olib message brokerga yuboradi.
Pattern database commit va message publish orasidagi dual-write nomuvofiqligini kamaytiradi.
Dual-write muammosi
Application quyidagilarni bajaradi:
- orderni database’ga saqlaydi;
OrderCreatedeventini brokerga yuboradi.
Database commit muvaffaqiyatli, publish xato bo‘lsa order bor, ammo event yo‘q.
Publish oldin, database commit xato bo‘lsa event mavjud bo‘lmagan order haqida xabar beradi.
Outbox table
Business table bilan bir database’da outbox table yaratiladi.
Fieldlar:
- event ID;
- aggregate ID;
- event type;
- payload;
- created time;
- status;
- attempt;
- schema version;
- correlation ID.
Order va outbox row bitta transactionda yoziladi.
Local transaction
BEGIN
→ orders INSERT
→ outbox INSERT
→ COMMIT
Commit bo‘lsa ikkalasi ham mavjud.
Rollback bo‘lsa ikkalasi ham yo‘q.
Distributed transaction yoki brokerning database transactionida qatnashishi talab qilinmaydi.
Publisher
Background worker outbox rowlarni o‘qiydi va brokerga yuboradi.
Muvaffaqiyatli publishdan keyin row:
- published deb belgilanadi;
- o‘chiriladi;
- archive qilinadi.
Publisher bir nechta instance bilan parallel ishlashi mumkin.
Polling publisher
Worker davriy query qiladi:
status = pending
Rowlarni batch bilan lock qilib oladi.
SKIP LOCKEDga o‘xshash mexanizm parallel workerlar bir rowni olishini kamaytiradi.
Polling interval latency va database load o‘rtasida muvozanat qiladi.
Log-based publisher
Change Data Capture transaction logdagi outbox insertlarni kuzatib brokerga uzatadi.
Bu polling querylarini kamaytiradi va past latency beradi.
Evaziga CDC infrastructure, offset va schema boshqaruvi kerak.
At-least-once delivery
Publisher eventni brokerga yubordi, ammo outbox statusini update qilishdan oldin crash qilishi mumkin.
Restartda event yana yuboriladi.
Shu sababli pattern odatda at-least-once delivery beradi.
Consumer duplicate eventga tayyor bo‘ladi.
Event ID
Har outbox event unique IDga ega.
Consumer processed event IDlarni saqlab deduplication qilishi mumkin.
Event ID retryda o‘zgarmaydi.
Yangi ID yaratish duplicate’ni aniqlashni imkonsiz qiladi.
Idempotent consumer
Handler bir eventni bir necha marta olganda yakuniy holat buzilmaydi.
Masalan:
invoice statusini CREATED qilish
idempotent bo‘lishi mumkin.
balance += amount
esa event ID yoki ledger unique constraint talab qiladi.
Ordering
Bir aggregate eventlari tartibi muhim bo‘lishi mumkin.
Outbox:
saqlaydi.
Broker partition key sifatida aggregate ID ishlatiladi.
Parallel publisherlar bir aggregate tartibini buzmasligi kerak.
Payload
Outbox payload event consumerlariga kerakli minimal data’ni saqlaydi.
Variantlar:
Faqat entity ID yuborilsa consumer keyin source service’ga query qilib, boshqa versionni olishi mumkin.
Schema version
Event format vaqt o‘tishi bilan o‘zgaradi.
Payload schema version saqlaydi.
Consumer eski va yangi versiyani bir muddat tushunishi kerak.
Breaking change yangi event type yoki migration talab qilishi mumkin.
Failure va retry
Publish xatosida:
- attempt oshadi;
- next retry time;
- exponential backoff;
- error summary
saqlanadi.
Doimiy invalid event dead-letter yoki manual review holatiga o‘tkaziladi.
Cheksiz tez retry database va brokerni band qiladi.
Cleanup
Published outbox rowlar table’ni cheksiz o‘stirmasligi kerak.
- ma’lum kundan keyin delete;
- monthly partition drop;
- archive;
- audit retention.
Pending rowlar tasodifan cleanup qilinmaydi.
Backlog
Outbox publisher ishlamasa pending rowlar yig‘iladi.
Kuzatiladi:
Database normal ishlayotgani eventlar yetkazilayotganini kafolatlamaydi.
Transaction boundary
Outbox row aynan business o‘zgarishi bilan bir transactionda yozilishi kerak.
Eventni transaction commitdan keyin application memory’da yaratish pattern kafolatini buzadi.
ORM hook va callback transaction semantikasiga mos ishlatiladi.
Inbox bilan birga
Consumer kelgan message’ni processed registry yoki inbox table’da saqlashi mumkin.
Business update va event ID recording bitta transactionda bajariladi.
Bu duplicate processingni boshqaradi.
Outbox va Inbox birgalikda ishonchli event workflow beradi.
Publisher locking
Bir nechta publisher pending rowlarni parallel olganda bir event ikki workerga bir vaqtda tushmasligi uchun row lock yoki claim field ishlatiladi.
Claim lease bilan cheklanadi.
Worker crash qilsa event keyin qayta olinadi.
Partitioning
Katta outbox table vaqt bo‘yicha partition qilinadi.
Published eski partitionlar tez drop yoki archive qilinadi.
Pending event boshqa partitionda qolib ketmasligi uchun cleanup statusni tekshiradi.
Event visibility
Business transaction commit bo‘lmagunga qadar outbox row publisherga ko‘rinmasligi kerak.
Isolation va polling query aynan committed eventlarni oladi.
Replica’dan polling qilinsa replication lag event deliveryni kechiktirishi mumkin.
Payload maxfiyligi
Outbox table database backup va administratorlarga ko‘rinadi.
Shu sababli token, parol va keraksiz personal data event payloadga yozilmaydi.
Zarur maxfiy field encryption va qisqa retention bilan boshqariladi.
Bog‘liq tushunchalar
Dual write, Domain event, Message broker, Change Data Capture, At-least-once delivery, Idempotent consumer, Outbox table, Inbox pattern, Event-driven architecture