Oyun Kitabı
Ödeme Sistemlerinde Outbox/Inbox Pattern (Odeme Sistemlerinde Outbox İnbox Pattern)
Veritabanına yazmak ile event yayınlamak aynı transaction'da değilse, biri kaybolur ya da tekrarlanır. Outbox yayınlar, inbox tüketicide dedup eder.
Dağıtık Ödeme Motoru (Distributed Payment Engine)
Bolum 7 / 22
Capture ile complete arasındaki boşluğu kapatan dağıtık ödeme mimarisi serisi.
İki yazma, tek transaction değil
Bir servis “sipariş oluştu” kararını verdiğinde genelde iki şey yapmak ister: veritabanına bir satır yazmak ve bir mesaj kuyruğuna/broker'a event yayınlamak. Bu iki yazma, farklı sistemlere gittiği için tek bir transaction'da atomik olarak yapılamaz. Aralarında kalan boşluğa “dual-write hole” denir.
DbContext.SaveChanges() → veritabanına yazıldı
Broker.Publish(event) → burası başarısız olursa event kaybolur
(veya sıra tersine çevrilirse, event gönderilir ama DB commit rollback olur)
Bu bölüm, bu boşluğu kapatan outbox ve inbox pattern'lerini anlatıyor.
Kavramlar ilk geçtiği yerde
📦 Dual-Write Problemi
Aynı iş kararının iki farklı sisteme (DB ve broker) atomik olmayan iki yazma ile yansıtılmaya çalışılması.
📦 Outbox
İş kararıyla aynı veritabanı transaction'ında yazılan, sonradan ayrı bir süreç tarafından yayınlanan olay tablosu.
📦 Lease
Bir publisher sürecinin, outbox'taki bir satırı işlerken diğer publisher süreçlerinin aynı satırı almasını önleyen geçici kilit.
📦 Inbox
Tüketici tarafında, gelen bir olayın işlenmeden önce kaydedildiği ve tekrarları filtreleyen tablo.
📦 Effectively-once
At-least-once teslimat ile idempotent tüketicinin birleşiminden elde edilen, pratikte “tam olarak bir kez” gibi davranan sonuç.
Outbox, gönderen tarafın sorununu (yazma + yayın atomikliği) çözer. Inbox, alan tarafın sorununu (tekrar teslimat) çözer. İkisi birlikte, uçtan uca güvenilir bir olay akışı kurar.
Dual-write deliği nasıl açılır
Bir handler, veritabanına yazdıktan hemen sonra broker'a event yayınlarsa, iki ayrı adım arasında sistem çökebilir. Eğer DB yazması başarılı olur ama broker çağrısı başarısız olursa, iş kararı kalıcı olur ama olay hiç yayınlanmaz — downstream servisler bu karardan asla haberdar olmaz.
1. DB: Order.Status = Captured ✓ (commit edildi)
2. Broker.Publish(OrderCaptured) ✗ (network hatası, process crash)
Sonuç: Order tablosunda Captured var, ama hiçbir servise event gitmedi
Tersi de mümkündür: event yayınlanır ama DB transaction'ı rollback olur; bu durumda downstream servisler hiç gerçekleşmemiş bir olaya tepki verir.
Outbox: yazma ve yayını aynı transaction'a taşımak
Outbox deseni, event'i broker'a doğrudan yayınlamak yerine, iş kararıyla aynı veritabanı transaction'ı içinde bir outbox tablosuna yazar. Bu satır artık iş kararıyla atomik olarak kalıcıdır — ya ikisi de commit olur, ya hiçbiri.
Tek transaction:
UPDATE orders SET status = 'Captured' WHERE id = 42;
INSERT INTO outbox (event_type, payload, dispatched_at) VALUES ('OrderCaptured', ..., NULL);
COMMIT
Ayrı bir publisher süreci, outbox'taki dispatched_at IS NULL satırlarını periyodik olarak tarar, broker'a yayınlar, başarılı olursa dispatched_at alanını doldurur. Bu publisher birden fazla instance ile çalışıyorsa, aynı satırı iki instance'ın aynı anda almaması için bir lease (kısa süreli kilit) mekanizması kullanılır.
Publisher A: satır #7'yi lease'ler (30 sn) → broker'a yayınlar → dispatched_at = now()
Publisher B: satır #7 lease'li, atlar; başka satır arar
Bu adım, event'in en az bir kez yayınlanmasını garanti eder — ama publisher crash olursa yayın iki kez de olabilir. Bu yüzden tüketici tarafında da bir savunmaya ihtiyaç vardır.
Inbox: tüketici tarafında dedup
Outbox at-least-once yayın garantisi verdiğine göre, tüketici aynı event'i birden fazla kez alabilir. Inbox deseni, event'i doğrudan işlemek yerine önce benzersiz bir event id ile inbox tablosuna yazar; bu yazma zaten varsa (unique constraint ihlali), event tekrar işlenmez.
Tüketici event alır: event_id=evt_001
INSERT INTO inbox (event_id, status) VALUES ('evt_001', 'received')
→ başarılı: iş bir job'a kuyruklanır
→ unique constraint hatası: zaten görülmüş, sessizce atlanır
Outbox + Inbox = effectively-once
Outbox, yayının kaybolmamasını garanti eder (at-least-once). Inbox, aynı yayının tüketici tarafında iki kez işlenmemesini garanti eder (idempotent consumer). Bu ikisi birleştiğinde, sistem pratikte “tam olarak bir kez” gibi davranır — buna effectively-once denir; gerçek exactly-once garantisi dağıtık sistemlerde neredeyse imkansızdır, ama bu kombinasyon onun pratikte yeterli bir yaklaşımıdır.
Outbox (gönderen) → at-least-once yayın
Inbox (alan) → idempotent tüketim
─────────────────────────────────────────
Toplam davranış → effectively-once
Bu bölümde en çok karışan eşleştirmeler
❌ DB'ye yazıp hemen ardından broker'a publish etmek güvenlidir
✓ Bu iki adım atomik değildir; aralarında dual-write hole vardır
❌ Outbox tek başına tekrar teslimatı önler
✓ Outbox at-least-once garanti eder; tekrar teslimata karşı tüketici tarafında inbox gerekir
❌ Lease, kalıcı bir kilittir
✓ Lease geçici ve süreli bir kilittir; publisher crash olursa süre dolunca serbest kalır
❌ Effectively-once, exactly-once ile aynı şeydir
✓ Exactly-once dağıtık sistemlerde pratik değildir; effectively-once, at-least-once + idempotency'nin sonucudur
Outbox/Inbox kurulumunuzu denetleme listesi
- İş kararınızı DB'ye yazma ile event yayınlama aynı transaction içinde mi, yoksa iki ayrı adımda mı yapılıyor?
- Outbox publisher'ınız birden fazla instance ile çalışıyorsa, aynı satırı iki instance'ın almasını önleyen bir lease mekanizmanız var mı?
- Publisher crash olursa, lease süresi dolduktan sonra satır tekrar denenebiliyor mu, yoksa sonsuza dek “lease'li” kalıyor mu?
- Tüketici tarafında inbox tablonuzda event id için unique constraint var mı?
- Outbox'ta
dispatched_at IS NULLolan satırların sayısını izleyen bir metrik/alarm var mı (yayın gecikmesi göstergesi)?
Bu beş sorudan ikisine emin cevap veremiyorsanız, sisteminiz muhtemelen dual-write deliğine hâlâ açıktır.
Bu bölümden aklında kalması gerekenler
- Veritabanına yazma ile event yayınlama farklı sistemlere gider ve atomik değildir; bu boşluk dual-write hole'dur.
- Outbox, event'i iş kararıyla aynı transaction'a yazarak gönderen tarafın atomikliğini sağlar; ayrı bir publisher süreci onu yayınlar.
- Lease, birden fazla publisher instance'ının aynı outbox satırını aynı anda işlemesini önler; kalıcı değil, süreli bir kilittir.
- Inbox, tüketici tarafında aynı event'in iki kez işlenmesini önler; outbox'ın at-least-once garantisini idempotent hale getirir.
Outbox olmadan yayınlanan bir event, atıldığı anda kaybolabilir. Inbox olmadan alınan bir event, iki kez işlenip kaybolmayı iki kat pahalı hale getirebilir.
SSS
Sık sorulan sorular
Dual-Write Problemi nedir?
Aynı iş kararının iki farklı sisteme (DB ve broker) atomik olmayan iki yazma ile yansıtılmaya çalışılması.
Outbox nedir?
İş kararıyla aynı veritabanı transaction'ında yazılan, sonradan ayrı bir süreç tarafından yayınlanan olay tablosu.
"DB'ye yazıp hemen ardından broker'a publish etmek güvenlidir" doğru mu?
Bu iki adım atomik değildir; aralarında dual-write hole vardır
Bu bölüm neyi sabitler?
Bu bölüm, bu boşluğu kapatan outbox ve inbox pattern'lerini anlatıyor. Veritabanına yazma ile event yayınlama farklı sistemlere gider ve atomik değildir; bu boşluk dual-write hole'dur. Bir servis “sipariş oluştu” kararını verdiğinde genelde iki şey yapmak ister: veritabanına bir satır yazmak ve bir mesaj kuyruğuna/broker'a event yayınlamak. Bu iki yazma, farklı sistemlere gittiği için tek bir transaction'da atomik olarak yapılamaz. Aralarında kalan boşluğa “dual-write hole” denir.
Ogrenilen Muhendislik Prensipleri
- Veritabanı yazması ile event yayınlama aynı transaction'da değilse, dual-write hole açık kalır; outbox bu boşluğu gönderen tarafında kapatır.
- Inbox, outbox'ın at-least-once garantisini tüketici tarafında idempotent hale getiren zorunlu tamamlayıcıdır.
- Effectively-once, exactly-once'ın yerini tutmaz; at-least-once teslimat ile idempotent tüketimin birlikte ürettiği pratik sonuçtur.
Okumaya devam et
Okumaya devam et
Seride sonraki yazi
Ödeme Kanıtı ve Ödeme Durumu: Neden Karıştırmamalısınız?
Evidence, PSP'nin ne dediğidir. State, sizin ne karar verdiğinizdir. Bu ikisini aynı kayıtta tutarsanız, kurtarma sırasında hangisine güveneceğinizi…
Seride sonraki yazi
Ödeme Sistemlerinde Webhook Güvenilirliği
Webhook'lar tekrarlanır, kaybolur, sırasız ve gecikmeli gelir. İmzayı doğrulayın, hızlı ACK verin, ağır işi asla senkron çalıştırmayın.
Ayni seriden
API (Application Programming Interface) İsteğinin Ötesinde Idempotency (Tekrarlanabilir Güvenli İşlem)
Idempotency tek bir header değildir. API anahtarından adım işaretçisine kadar beş farklı katmanda ayrı ayrı kurulması gereken bir savunma yığınıdır.