Oyun Kitabı
Lease ile Veritabanı Destekli İşler (Lease İle Veritabani Destekli İsler)
Conditional UPDATE ile lease alma, sıkışmış işleri kurtaran watcher ve neden yalnızca mesaj Nack etmek production ödemede yetmez.
Dağıtık Ödeme Motoru (Distributed Payment Engine)
Bolum 13 / 22
Capture ile complete arasındaki boşluğu kapatan dağıtık ödeme mimarisi serisi.
Önceki bölümde retry algoritmasını kurduk; ama bu algoritma bir işin hangi worker tarafından çalıştırıldığını varsayıyordu. Birden çok worker aynı iş kuyruğundan çekiyorsa, aynı ödeme işi iki worker tarafından aynı anda işlenebilir mi?
Mesaj broker'ları bu sorunu 'visibility timeout' veya 'ack/nack' ile çözer. Fakat veritabanı destekli bir iş kuyruğunda (birçok ödeme sisteminde tercih edilen yaklaşım, çünkü iş durumu zaten veritabanında yaşar) aynı garanti lease deseniyle sağlanır.
Worker A Worker B
│ SELECT ... FOR UPDATE? │
│ ya da conditional UPDATE │
▼ ▼
Job'ı kilitlemeye çalışır Job'ı kilitlemeye çalışır
→ sadece biri kazanır
Kavramlar ilk geçtiği yerde
📦 Lease
Bir worker'ın bir işi belirli bir süre için 'sahiplendiğini' işaretleyen zaman damgalı kayıt.
📦 Conditional UPDATE
Sadece beklenen koşul (örn. status = Pending) doğruysa satırı güncelleyen atomik SQL işlemi.
📦 Lease süresi (Lease TTL)
Bir worker'ın işi ne kadar süreyle sahiplenebileceğinin üst sınırı; bu süre geçince iş tekrar alınabilir hale gelir.
📦 Stuck Watcher
Lease'i süresi dolmuş ama tamamlanmamış işleri periyodik olarak tarayıp yeniden kuyruğa alan arka plan süreci.
Lease bir kilit değildir; kilidi tutan process çökerse kilit sonsuza kadar kalabilir. Lease'in süresi olduğu için, worker çökse bile iş bir süre sonra otomatik olarak serbest kalır.
Lease almanın atomik yolu
Bir işi almak için önce okuyup sonra güncellemek (read-then-write) bir yarış durumuna açıktır: iki worker aynı satırı okuyabilir, ikisi de 'boş' görebilir, ikisi de güncellemeye çalışabilir. Doğru yol, okuma ve koşulu aynı atomik ifadeye taşımaktır:
UPDATE payment_jobs
SET status = 'Processing',
lease_owner = :workerId,
lease_until = now() + interval '60 seconds'
WHERE id = :jobId
AND (status = 'Pending' OR (status = 'Processing' AND lease_until < now()))
RETURNING id;
Bu UPDATE hiçbir satır döndürmezse, iş zaten başka bir worker tarafında ya da hâlâ geçerli bir lease altındadır — worker sessizce bir sonraki işe geçer. Satır döndüyse, o worker artık işin tek sahibidir; ta ki lease süresi dolana kadar.
Lease süresi neden heartbeat ile uzatılmalı
Sabit bir lease süresi (örn. 60 saniye) her işlem için yetmeyebilir. Uzun sürebilecek bir işlem (örneğin bir PSP çağrısı beklenmedik şekilde uzarsa), lease süresi dolmadan önce heartbeat ile uzatılmalıdır:
Worker işe başlar → lease_until = now + 60s
... iş sürüyor ...
Worker heartbeat gönderir → lease_until = now + 60s (yenilenir)
... iş tamamlanır ...
Worker status = Completed olarak işaretler
Heartbeat gönderemeyen bir worker (çökmüş, ağdan kopmuş) lease'i yenileyemez; süre dolar ve iş tekrar alınabilir hale gelir. Bu, çökme senaryosunun otomatik olarak ele alınmasını sağlayan mekanizmadır.
Stuck watcher: lease'i süresi dolmuş işleri kim fark eder
Lease süresi dolduğunda iş otomatik olarak 'kurtarılmaz' — bir worker'ın onu tekrar SELECT etmesi gerekir. Bu yüzden periyodik çalışan bir watcher süreci, lease'i süresi dolmuş ama hâlâ Processing durumunda kalan işleri tarar ve bunları tekrar alınabilir hale getirir (veya doğrudan yeni bir worker'a atar).
Watcher (her 30 saniyede bir)
SELECT id FROM payment_jobs
WHERE status = 'Processing' AND lease_until < now()
→ bu işler 'stuck' olarak işaretlenir veya doğrudan Pending'e döndürülür
Watcher olmadan, çökmüş bir worker'ın bıraktığı iş sonsuza kadar 'Processing' görünebilir; kimse fark etmeden bir ödeme asla tamamlanmaz.
Neden yalnızca broker'ın Nack'i yeterli değil
Bir mesaj broker'ında bir worker mesajı Nack ederse (veya visibility timeout dolarsa), mesaj tekrar kuyruğa döner. Bu, DB'deki lease'e çok benzer — fakat iki fark önemlidir: birincisi, broker'ın kendi görünürlük penceresi genellikle iş durumunuzun kalıcı kaydıyla senkron değildir (mesaj kaybolabilir, ya da iki kez teslim edilebilir); ikincisi, Nack yalnızca 'bu mesajı bırak' der, işin hangi aşamada kaldığını veya kaç kez denendiğini kalıcı olarak bilmez. Veritabanı destekli lease, iş durumunu ve deneme geçmişini aynı transaction sınırında, sorgulanabilir biçimde tutar.
Sık karıştırılan ayrımlar
❌ Lease = kilit (lock)
✓ Lease süresi dolan bir zaman sınırıdır; kilit process çökerse sonsuza kalabilir
❌ Read-then-write yeterlidir
✓ Read-then-write yarış durumuna açıktır; conditional UPDATE atomik olmalıdır
❌ Nack, lease'in yerini tamamen tutar
✓ Nack mesaj görünürlüğünü yönetir; lease iş durumunu ve deneme geçmişini kalıcı olarak tutar
DB-backed lease ile broker visibility timeout karşılaştırması
| Kriter | Broker visibility timeout | DB-backed lease |
|---|---|---|
| Durum sorgulanabilirliği | Sınırlı | SQL ile tam |
| Deneme geçmişi kalıcılığı | Broker'a bağlı | Kolayca aynı satırda |
| Stuck job'ı tespit etme | Dolaylı | Doğrudan sorgu ile |
Lease tasarlarken kontrol listesi
- Lease alma işlemi tek bir atomik
UPDATE ... WHEREifadesi mi, yoksa read-then-write mi? - Lease süresi, en uzun beklenen işlem süresinden gerçekten uzun mu?
- Uzun sürebilecek işlemler için heartbeat ile lease yenileme var mı?
- Stuck watcher periyodik çalışıyor mu, yoksa lease süresi dolan işler sonsuza kadar 'Processing' mi kalıyor?
lease_owneralanı, hangi worker instance'ının işi aldığını tanılama için saklıyor mu?- Deneme sayısı, her lease alımında artırılıp kalıcı olarak saklanıyor mu?
Bu yazıdan aklında ne kalmalı
- Lease bir kilit değildir; zaman sınırlı bir sahiplik iddiasıdır ve otomatik olarak sona erer.
- Lease almak, atomik bir conditional UPDATE gerektirir; read-then-write yarış durumuna açıktır.
- Uzun işlemler heartbeat ile lease'i yenilemelidir; aksi halde lease erken sona erebilir.
- Stuck watcher, çökmüş worker'ların bıraktığı işleri kurtaran zorunlu bir arka plan sürecidir.
Bir iş kuyruğunun güvenilirliği, mutlu yolda değil; bir worker'ın ortada çöktüğü anda test edilir.
Bir sonraki bölümde bu lease mekanizmasının üzerine kurulan bir worker türünü inceliyoruz: PSP'de başarılı ama sistemde hâlâ bekleyen ödemeleri iyileştiren mutabakat worker'ı.
SSS
Sık sorulan sorular
Lease nedir?
Bir worker'ın bir işi belirli bir süre için 'sahiplendiğini' işaretleyen zaman damgalı kayıt.
Conditional UPDATE nedir?
Sadece beklenen koşul (örn. status = Pending) doğruysa satırı güncelleyen atomik SQL işlemi.
"Lease = kilit (lock)" doğru mu?
Lease süresi dolan bir zaman sınırıdır; kilit process çökerse sonsuza kalabilir
Bu bölüm neyi sabitler?
Mesaj broker'ları bu sorunu 'visibility timeout' veya 'ack/nack' ile çözer. Fakat veritabanı destekli bir iş kuyruğunda (birçok ödeme sisteminde tercih edilen yaklaşım, çünkü iş durumu zaten veritabanında yaşar) aynı garanti **lease** deseniyle sağlanır. Lease bir kilit değildir; zaman sınırlı bir sahiplik iddiasıdır ve otomatik olarak sona erer. Önceki bölümde retry algoritmasını kurduk; ama bu algoritma bir işin *hangi worker tarafından* çalıştırıldığını varsayıyordu. Birden çok worker aynı iş kuyruğundan çekiyorsa, aynı ödeme işi iki worker tarafından aynı anda işlenebilir mi?
Ogrenilen Muhendislik Prensipleri
- Lease bir kilit değildir; zaman sınırlıdır ve otomatik sona erer.
- Lease almak atomik conditional UPDATE gerektirir, read-then-write değil.
- Stuck watcher olmadan çökmüş worker'ın işi sonsuza kadar kaybolabilir.
Okumaya devam et
Okumaya devam et
Seride sonraki yazi
Ödeme Mutabakat Worker İnşası
Sweeper'lar drift'i nasıl iyileştirir: PSP başarılı derken local kayıt expired olabilir; aged FinalizePending nasıl toparlanır.
Seride sonraki yazi
Ödeme Worker'ları İçin Retry Algoritmaları
Exponential backoff, jitter, cap, defer ile retry farkı ve circuit breaker — bir önceki bölümdeki taksonomiyi çalışan koda dönüştürün.
Ayni seriden
Ödendi Ama Sipariş Yok: İyileştirme
Olay müdahale kılavuzu: müşteri ücretlendirildi ama sipariş oluşmadı; çok-niyetli sepet karmaşası; dedup'ı dikkatle temizlemek.