Oyun Kitabı

Ödemelerde Effectively-Once İşleme (Odemelerde Effectively Once İsleme)

Exactly-once messaging bir yalandır. Defense in depth ile idempotency, dedup, outbox ve reconciliation birleşince effectively-once iş sonucu nasıl elde edilir?

Dağıtık Ödeme Motoru (Distributed Payment Engine)

Bolum 20 / 22

Capture ile complete arasındaki boşluğu kapatan dağıtık ödeme mimarisi serisi.

Distributed payment engine architecture diagram

Önceki bölümde recovery pipeline'ın otomasyon önce prensibiyle çalıştığını ve uniqueness duvarlarında kanıta dayalı runbook'ların devreye girdiğini gördük. Bu bölüm, pipeline'ın üzerinde yatan messaging garantilerine iniyor — ve endüstrinin en yaygın yanılgısına: exactly-once delivery.

Broker satıcıları 'exactly-once semantics' vaat eder. Gerçek şu: dağıtık bir sistemde bir mesajın tam olarak bir kez işlenmesi fiziksel olarak garanti edilemez. Ağ kopar, worker çöker, broker yeniden gönderir. Soru 'mesaj kaç kez geldi' değil; iş sonucu kaç kez gerçekleşti.

Exactly-once messaging → imkansız (at-least-once + failure = duplicate)
Effectively-once outcome → mümkün (defense in depth)

Kavramlar ilk geçtiği yerde

📦 At-Least-Once Delivery
Mesaj en az bir kez ulaşır; ağ hatasında tekrar gönderilebilir.

📦 Effectively-Once
Mesaj birden fazla kez işlense bile iş sonucu (charge, refund, order) yalnızca bir kez gerçekleşir.

📦 Defense in Depth
Tek bir mekanizmaya güvenmek yerine, birden fazla bağımsız koruma katmanı.

📦 Idempotent Consumer
Aynı mesajı ikinci kez aldığında aynı sonucu üreten, yan etki yaratmayan tüketici.

Exactly-once messaging bir broker özelliği değildir; effectively-once bir sistem tasarımıdır.

Exactly-once neden yalan

Bir worker mesajı alır, işler, acknowledgment gönderir — ama acknowledgment ağda kaybolur. Broker mesajı tekrar gönderir. Worker ikinci kez işler. Bu, at-least-once delivery'nin doğasında vardır; broker ne kadar 'exactly-once' iddia ederse etsin, tüketici tarafında duplicate kaçınılmazdır.

Worker mesajı işler → sonuç: Captured
Ack ağda kaybolur
Broker mesajı tekrar gönderir
Worker tekrar işler → sonuç: ??? (çift charge riski)

Sorun mesajın kaç kez geldiği değil; ikinci gelişte ne olduğudur. İkinci gelişte hiçbir yan etki yoksa, effectively-once elde edilmiş demektir.

Defense in depth: tek katman yetmez

Effectively-once iş sonucu, birbirini tamamlayan katmanların birleşimidir. Hiçbiri tek başına yeterli değildir; hepsi birlikte 'en az bir kez gelen mesaj, en fazla bir kez etki eder' sonucunu üretir.

Katman 1: Idempotency key (API)
  → aynı key ile ikinci charge isteği reddedilir

Katman 2: Consumer dedup (messaging)
  → aynı message id ikinci kez işlenmez

Katman 3: DB uniqueness constraint
  → aynı idempotency key ile ikinci satır yazılamaz

Katman 4: Outbox pattern
  → event yalnızca transaction commit ile birlikte yayınlanır

Katman 5: Reconciliation
  → katman 1-4'ün kaçırdığı drift'i periyodik olarak düzeltir

Bir katman başarısız olursa diğeri devreye girer. Idempotency key atlanırsa uniqueness constraint yakalar; constraint atlanırsa reconciliation düzeltir.

Her katmanın sorusu farklıdır

Katman Sorusu Koruduğu
Idempotency key Aynı niyet tekrar mı? API düzeyinde çift charge
Consumer dedup Aynı mesaj tekrar mı? Messaging düzeyinde çift işlem
Uniqueness constraint Aynı key ile ikinci satır mı? DB düzeyinde çift kayıt
Outbox Event commit olmadan mı yayınlandı? Kayıp veya erken event
Reconciliation Local ile PSP uyumsuz mu? Kaçan her şey

Idempotent consumer nasıl yazılır

Idempotent consumer'ın kuralı: aynı input, aynı output; ikinci çağrıda yan etki yok.

PaymentCaptured webhook (messageId=wh_991)
  → dedup tablosuna bak: wh_991 işlendi mi?
  → evet → skip (log: duplicate suppressed)
  → hayır → finalize, dedup tablosuna yaz

Finalize adımının kendisi de idempotent olmalıdır: payment zaten Captured ise tekrar event yayınlanmamalı, tekrar order oluşturulmamalıdır. Dedup tablosu messaging katmanını korur; terminal statü kontrolü iş mantığını korur.

Outbox: event ile state aynı transaction'da

Outbox pattern, 'state güncellendi ama event yayınlanmadı' veya 'event yayınlandı ama state güncellenmedi' ikilemini çözer. Event, local transaction ile birlikte outbox tablosuna yazılır; ayrı bir publisher outbox'ı okur ve broker'a gönderir.

BEGIN TRANSACTION
  UPDATE payment SET status=Captured
  INSERT INTO outbox (event=PaymentCaptured, paymentId=8812)
COMMIT
  → publisher outbox'ı okur → broker'a gönderir
  → gönderim başarılı → outbox satırını siler

Outbox, messaging exactly-once sağlamaz — ama state ile event arasındaki tutarlılığı garanti eder. Reconciliation, outbox'tan kaçanları yakalar.

Sık karıştırılan ayrımlar

❌ Broker exactly-once = sistem exactly-once
✓ Broker dedup + consumer idempotency + DB constraint = effectively-once

❌ Idempotency key her yerde yeterli
✓ Idempotency key API'yi korur; webhook ve async worker ayrı katman ister

❌ Effectively-once = perfect system
✓ Effectively-once = iş sonucu bir kez; tutarsızlık penceresi reconciliation ile kapanır

Delivery guarantee vs business outcome

Garanti Ne vaat eder Ödemelerde yeterli mi
At-most-once Mesaj bir kez, kayıp olabilir Hayır — kayıp ödeme
At-least-once Mesaj en az bir kez, duplicate olabilir Hayır — tek başına
Exactly-once (broker) Teorik, pratikte tüketici tarafında kırılır Hayır
Effectively-once (sistem) İş sonucu bir kez Evet

Effectively-once kontrol listesi

  1. API charge istekleri idempotency key ile korunuyor mu?
  2. Webhook ve async consumer'larda message-id dedup var mı?
  3. Payment tablosunda idempotency key üzerinde uniqueness constraint var mı?
  4. State değişikliği ile event yayını outbox pattern ile aynı transaction'da mı?
  5. Finalize handler terminal statüde tekrar işlem yapmadan skip ediyor mu?
  6. Reconciliation, katman 1-5'in kaçırdığı drift'i periyodik tarıyor mu?

Bu yazıdan aklında ne kalmalı

  1. Exactly-once messaging bir yalandır; at-least-once + failure = duplicate kaçınılmazdır.
  2. Effectively-once iş sonucu, defense in depth ile mümkündür — tek katman yetmez.
  3. Her koruma katmanı farklı bir soruya cevap verir; hepsi birlikte çalışmalıdır.
  4. Reconciliation son katmandır; önceki katmanların yerine geçmez, tamamlar.

Broker'ınız exactly-once vaat etse bile, ödeme sisteminiz buna inanmamalı — inanması gereken, effectively-once iş sonucunu defense in depth ile inşa etmektir.

Bir sonraki bölümde bu 22 bölümlük yolculuğun sentezine geçiyoruz: production ödeme motoru tasarım checklist'i.

SSS

Sık sorulan sorular

At-Least-Once Delivery nedir?

Mesaj en az bir kez ulaşır; ağ hatasında tekrar gönderilebilir.

Effectively-Once nedir?

Mesaj birden fazla kez işlense bile iş sonucu (charge, refund, order) yalnızca bir kez gerçekleşir.

"Broker exactly-once = sistem exactly-once" doğru mu?

Broker dedup + consumer idempotency + DB constraint = effectively-once

Bu bölüm neyi sabitler?

Broker satıcıları 'exactly-once semantics' vaat eder. Gerçek şu: dağıtık bir sistemde bir mesajın tam olarak bir kez işlenmesi fiziksel olarak garanti edilemez. Ağ kopar, worker çöker, broker yeniden gönderir. Soru 'mesaj kaç kez geldi' değil; **iş sonucu kaç kez gerçekleşti**. Exactly-once messaging bir yalandır; at-least-once + failure = duplicate kaçınılmazdır. Önceki bölümde recovery pipeline'ın otomasyon önce prensibiyle çalıştığını ve uniqueness duvarlarında kanıta dayalı runbook'ların devreye girdiğini gördük. Bu bölüm, pipeline'ın üzerinde yatan messaging garantilerine iniyor — ve endüstrinin en yaygın yanılgısına: exactly-once delivery.

Ogrenilen Muhendislik Prensipleri

  • Exactly-once messaging bir yalandır; effectively-once iş sonucu mümkündür.
  • Defense in depth: idempotency, dedup, uniqueness, outbox, reconciliation birlikte çalışır.
  • Her koruma katmanı farklı bir soruya cevap verir; tek katman yetmez.

Okumaya devam et

Okumaya devam et

Seride sonraki yazi

Seride sonraki yazi

Ayni seriden

Paylaş