Sertaç Yıldırımsaha notları

Ana sayfa → Teknik

Distributed Transaction: 2PC Neden Kilitler, Saga Neden Kurtarır

İki bacaklı bir emir: EURUSD al + GBPUSD sat. İkisi de olmalı ya da hiçbiri. Birincisi fill oldu, ikincisi reddedildi. Ve elinde ROLLBACK diye bir komut yok.

Özet
  • Tek veritabanındaki ROLLBACK lüksü dağıtık dünyada yok. Onu taklit etmeye çalışmak asıl hata.
  • 2PC bloke eder. Koordinatör çökerse herkes kilitli bekler; trade’de bu kabul edilemez.
  • Saga rollback yapmaz, compensate eder. Aradaki fark ticari: telafinin bir maliyeti var.
  • Basit akış choreography, kritik akış orchestration. Ölçüt: “bu emir şu an nerede?” sorusuna cevap verebiliyor musun?

Tek veritabanında bu sorun yoktu

Her şey tek bir PostgreSQL’deyken hayat kolaydı:

BEGIN;
  UPDATE teminat SET kullanilan = kullanilan + 1200 WHERE hesap = 42;
  INSERT INTO emirler (...) VALUES (...);
  INSERT INTO emirler (...) VALUES (...);
COMMIT;   -- ya hepsi, ya hicbiri

Şimdi teminat ayrı bir serviste, risk kontrolü ayrı bir serviste, emir gönderimi köprü üzerinden dış bir sistemde. Ortada tek bir COMMIT yok ve olmayacak.

Two-phase commit: kitaptaki cevap, sahadaki sorun

İki aşamalı onay, tam olarak bunu çözmek için tasarlandı. Bir koordinatör var ve iki tur soruyor:

Nasıl işliyor
1. FAZ — HAZIRLIK
   Koordinator -> teminat-servisi : "hazir misin?"   -> HAZIR (kilitledi)
   Koordinator -> risk-servisi    : "hazir misin?"   -> HAZIR (kilitledi)

2. FAZ — ONAY
   Koordinator -> herkese         : "ONAYLA"
   ...ya da biri HAYIR dediyse    : "IPTAL"

Kağıt üzerinde kusursuz. Peki koordinatör 1. faz ile 2. faz arasında çökerse ne olur?

Bloke edici protokol

Teminat servisi “hazırım” dedi ve o hesabın teminatını kilitledi. Şimdi karar bekliyor. Koordinatör yok. Ne yapacak?

  • Kendi başına onaylayamaz — belki diğeri hayır demişti.
  • Kendi başına iptal edemez — belki diğerleri onayladı.
  • Yani bekler. Koordinatör geri gelene kadar.

Bu sırada o müşterinin hesabı kilitli. Yeni emir açamıyor, pozisyon kapatamıyor. Piyasa hareket ediyor ve müşteri ekrana bakıp bekliyor. Bir trading sisteminde bu cümleyi kurabileceğin bir senaryo yok.

İkinci ve daha pratik sorun: dış sistemler bu protokolü konuşmuyor. MT5 köprüsüne ya da likidite sağlayıcısına “hazır mısın, birazdan onaylayacağım” diyemezsin. Onlar tek bir şey biliyor: emri gönder, sonucu al.

3PC diye bir şey var: araya bir ön-onay fazı koyup, koordinatör çökerse katılımcıların zaman aşımıyla (timeout) ilerlemesini sağlıyor. Bloke etme sorununu azaltıyor ama ağ bölünmesinde hâlâ yanlış karar üretebiliyor ve bir tur daha gecikme ekliyor. Pratikte trading tarafında kullanıldığını görmedim; bilinmesi iyi, kurulması gereksiz.

Saga: rollback yok, compensation var

Saga’nın fikri şu: uzun işlemi küçük, kendi başına tamamlanan adımlara böl. Her adımın bir de telafisi olsun. Bir yerde iş patlarsa, o ana kadar yapılanları geriye doğru telafi et.

Kritik fark: adımlar anında kesinleşiyor. Kimse kilit tutmuyor, kimse kimseyi beklemiyor.

AdımİleriTelafi
1Teminat ayırAyrılan teminatı serbest bırak
2Risk kontrolü kaydetKaydı iptal et
31. bacağı gönder (EURUSD al)Ters pozisyon aç — geri alınamaz
42. bacağı gönder (GBPUSD sat)Aynı şekilde
5Müşteriye bildirGeri çağrılamaz — düzeltme bildirimi gönder

Sağ sütuna dikkat: ilk ikisi gerçek bir geri alma. Üçüncüden itibaren geri alma yok, sadece iş anlamında tersine çevirme var. Ve bunun bir bedeli oluyor.

Compensating transaction: en yanlış anlaşılan kısım

“Telafi işlemi” kulağa temiz bir ROLLBACK gibi geliyor. Değil. Somut örnek:

Compensation’ın gerçek maliyeti
10:30:00.120  1. bacak fill oldu:  EURUSD AL  1.0 lot @ 1.08450
10:30:00.480  2. bacak REDDEDILDI: likidite yok
10:30:00.610  TELAFI: EURUSD SAT 1.0 lot @ 1.08442

Sonuc: pozisyon kapandi ama 0.8 pip zarar kaldi.
       Bu zarar KAYBOLMADI - birinin ustune yazilacak.

Yani telafi teknik bir işlem değil, ticari sonucu olan bir karar. “Bu farkı kim ödeyecek” sorusunun cevabı kodda değil sözleşmede yazıyor — ve o cevap yazılmamışsa telafi kodunu yazmanın anlamı yok.

Üç kural, üçü de sahada öğrenildi:

  • Telafisi olmayan adımı en sona koy. Müşteriye bildirim, e-posta, muhasebe kaydı — bunlar geri alınamaz. Akışın en sonunda olsunlar ki telafi ihtimali kalmasın.
  • Telafi de başarısız olabilir. Ters pozisyon açarken piyasa kapanmış olabilir. Telafinin telafisi yoktur; o noktada insana git. Otomatik olarak sonsuza kadar denemek durumu kötüleştiriyor.
  • Telafi de idempotent olmalı. Telafi mesajı iki kez gelirse iki ters pozisyon açarsın ve bu sefer gerçekten yanlış yöne pozisyon taşırsın (detay).

Choreography mi, orchestration mı?

Choreography: herkes kendi işini bilir

Merkezi yönetici yok. Her servis işini yapar ve bir olay yayınlar; sıradaki servis o olayı dinler.

Basit emir akışı
emir-servisi     : EmirOlusturuldu      yayinla
teminat-servisi  : EmirOlusturuldu  dinle -> TeminatAyrildi   yayinla
kopru-servisi    : TeminatAyrildi   dinle -> EmirGonderildi   yayinla
bildirim-servisi : EmirGonderildi   dinle -> (mail at)

Kurulumu kolay, servisler birbirini tanımıyor, yeni bir dinleyici eklemek bedava. Basit akışlarda gerçekten iyi.

Ama altıncı adımdan sonra

Müşteri arıyor: “emrim ne oldu?” Cevap vermek için altı ayrı servisin loguna bakman gerekiyor, çünkü akışın tamamı hiçbir yerde yazmıyor. Akış, servislerin dinleme kurallarının içine dağılmış durumda.

Daha kötüsü: bir adım patladığında telafi zincirini kim yürütecek? Koreografide her servisin bunu bilmesi gerekiyor ve bu, bağımsızlık iddiasını çürütüyor.

Orchestration: bir yönetici, tek yerde akış

Merkezi bir orchestrator adımları sırayla çağırıyor ve durumu kalıcı olarak yazıyor.

Durum makinesi, diske yazılı
CREATE TABLE saga_durum (
  saga_id     uuid PRIMARY KEY,
  tur         text,            -- 'MULTI_LEG', 'OCO', 'BRACKET'
  adim        text,            -- 'TEMINAT', 'BACAK_1', 'BACAK_2', 'BILDIRIM'
  durum       text,            -- 'DEVAM', 'TELAFI', 'BITTI', 'MUDAHALE'
  yapilanlar  jsonb,           -- telafi icin gereken her sey
  guncelleme  timestamptz
);

Bu tablo sayesinde “emir nerede?” sorusu tek bir SELECT ile cevaplanıyor. Ve orchestrator çökse bile, ayağa kalkınca yarım kalan saga’ları tablodan okuyup kaldığı yerden devam edebiliyor.

Orchestrator’ın kendisi çökerse?

Bu yüzden durumun her adımdan önce diske yazılması şart. Bellekte tutulan bir durum makinesi, ilk yeniden başlatmada yarım kalmış emirleri unutur ve onları kimse toparlamaz.

Ayrıca aynı saga’yı iki orchestrator kopyasının yürütmemesi gerekiyor — bu da doğrudan leader election konusu.

ChoreographyOrchestration
Akış nerede yazılıHiçbir yerde (dağılmış)Tek yerde
“Emir nerede?”6 servis loguTek sorgu
Telafi yönetimiHer servis kendi bilirMerkezden yürütülür
Yeni adım eklemekKolayOrchestrator değişir
Tek nokta bağımlılıkYokVar (lider seçimi gerekir)
Ne zaman3 adıma kadar, basit akışÇok bacaklı, kritik, para dokunan

Bizde ayrım şöyle oturdu: tek bacaklı normal emir koreografiyle akıyor — üç adım, herkes anlıyor. Çok bacaklı emirler, OCO ve bracket orchestrator’dan geçiyor. Kararı belirleyen soru şuydu: “Bu akış yarıda kalırsa, ne olduğunu bir yerden okuyabilir miyim?”

Escrow: üçüncü tarafın tuttuğu kaynak

Bir hesaptan diğerine pozisyon transferi gibi işlerde işe yarayan basit bir kalıp. Kaynak doğrudan A’dan B’ye geçmiyor; arada bir escrow hesabı duruyor.

Üç adım
1. A'dan al  -> emanete koy      (A'nin bakiyesi dustu, B'ye gecmedi)
2. B'ye ver  -> emanetten cikar  (transfer tamam)
3. Patlarsa  -> emanetten A'ya geri koy

Faydası şu: hiçbir anda para iki yerde ya da hiçbir yerde olmuyor. Her an nerede olduğu bir tabloda yazıyor. Mutabakat yaparken “emanette bekleyen” satırı, yarım kalmış transferlerin listesi oluyor.

Şart: emanette bekleyen kayıtların bir zaman aşımı olmalı. Yoksa patlayan bir transfer orada sonsuza kadar durur ve kimse fark etmez. Günlük bir iş, 1 saatten uzun süredir emanette duran her şeyi raporlasın.

Peki her yerde saga mı?

Hayır, ve bu önemli. Saga karmaşık; kurulumu, telafi kodu ve izlenmesi maliyetli. Kullanmadan önce iki soru sor:

Saga’ya gerek yok
  • Bütün adımlar aynı veritabanındaysa → tek transaction yeter
  • Adımlar birbirinden bağımsızsa → sadece kuyruk yeter
  • Yarım kalması kabul edilebiliyorsa → tekrar deneme yeter
Saga gerekiyor
  • Adımlar farklı servislerde/sistemlerde
  • Yarım kalması para anlamına geliyor
  • Her adımın anlamlı bir telafisi tanımlanabiliyor

Sık gördüğüm hata, tek bir servis içinde saga kurmak: aynı veritabanına yazan üç adım için orchestrator yazılmış. Orada BEGIN ... COMMIT zaten var ve kusursuz çalışıyor. Dağıtık işlem, ancak dağıtık olmak zorundaysan gerekiyor.

Kontrol listesi AI agent görev listesi

Dağıtık bir akış tasarlarken
  • Bu gerçekten dağıtık mı, yoksa tek transaction yeterli mi?
  • Her adımın tanımlı bir telafisi var mı?
  • Telafisi olmayan adımlar en sonda mı?
  • Telafi başarısız olursa ne oluyor? İnsana gidiyor mu?
  • Telafi idempotent mi?
  • Saga durumu diske yazılıyor mu, yoksa bellekte mi?
  • “Bu emir şu an nerede?” sorusu tek sorguyla cevaplanıyor mu?
  • Orchestrator kullanıyorsan: aynı saga’yı iki kopya yürütebilir mi?
  • Yarım kalmış saga’ları bulan bir rapor var mı?
  • Telafinin ticari maliyetini kim üstleniyor? Yazılı mı?

Sonuç

Dağıtık işlemlerde asıl zorluk teknik değil kavramsal: ROLLBACK alışkanlığından vazgeçmek gerekiyor. Tek veritabanında geçmiş bir işlemi hiç olmamış gibi silebiliyorsun; dağıtık dünyada olan olmuş oluyor ve elinden gelen tek şey ikinci bir işlemle durumu düzeltmek.

O yüzden en iyi tasarım, telafi gerektirmeyecek şekilde sıralanmış olanı: en riskli ve en geri alınamaz adımı en sona koy, ondan öncekileri ucuz ve geri alınabilir tut. Baştaki iki bacaklı emirde de çözüm bu oldu — ikinci bacağın likiditesini birinciyi göndermeden önce kontrol etmek.