Background Job Sistemleri: Idempotency, Retry ve DLQ
Asenkron iş kuyrukları üretimin sessiz omurgasıdır. Doğru kurmazsan duplicate işlemler, kayıp mesajlar ve tutarsız veri çıkarır. Üretimde bizi koruyan 3 temel pattern.
Bir kullanıcı ödeme yaptı. Anlık bildirim, e-posta, paneldeki istatistik güncelleme, faturalandırma sistemine event yayınlama, fraud kontrol — hepsi paralel işler. HTTP isteğin içinde bunları senkron yapmaya kalkarsan, kullanıcı 10 saniye bekler. Doğru yaklaşım: hepsini queue'ya at, async işle.
Ama "queue'ya at" demek, problemi yarısını çözmek. Diğer yarısı: o queue gerçekten güvenilir çalışıyor mu?
Üretimde 5 yıl önce başlayıp bugüne kadar uyguladığım 3 temel pattern.
1. Idempotency — başarının temeli
Bir job iki kere çalışırsa ne olur?
Düşünmek istemediğin senaryo: kullanıcı 100 TL ödedi, "bakiye güncelle" job'u iki kere çalıştı, bakiyesi 200 TL fazla artmış. Customer support başında.
Çözüm: her job idempotent olmalı. İki kere çalışsa da bir kere çalışmış gibi sonuç versin.
Pratik:
- Idempotency key kullan. Ödeme job'unun message body'sinde
payment_idvar. Job çalışırken önce bakar: bu payment için zaten balance update yapılmış mı? Yapılmışsa skip eder.
def process_payment(message):
payment_id = message['payment_id']
if already_processed(payment_id):
return # idempotent skip
update_balance(...)
mark_processed(payment_id)
-
already_processedsorusunu cevaplamanın yolu: ya DB'deprocessed_paymentstablosu, ya Redis'te 24 saatlik bir set. Hangisi sana uygunsa. -
Operasyonun kendisi idempotent olabilir. Örnek: "set user.email = X" iki kere çalışsa da sonuç aynı. "increment user.balance by 100" değil — bunu idempotency key'e bağlamak gerekir.
2. Retry + exponential backoff
Job hata aldı. Network timeout, downstream servis yanıt vermiyor. Ne yaparsın?
Yanlış cevap: hemen yeniden dene, başarısızsa bir daha hemen, bir daha hemen. Sonuç: downstream servisi DDoS'larsın.
Doğru cevap: artan beklemeyle yeniden dene.
deneme 1: hemen
deneme 2: 5 saniye sonra
deneme 3: 30 saniye sonra
deneme 4: 2 dakika sonra
deneme 5: 10 dakika sonra
Bu exponential backoff. Çoğu queue sistemi (RabbitMQ, AWS SQS, Sidekiq) built-in destekler.
Tuzak: belirli hata tiplerinde retry etmemek. Örnek:
- Validation hatası → retry'da düzelmez, DLQ'ya at
- 4xx HTTP'ler → retry boşa
- 5xx HTTP'ler → retry mantıklı
- Timeout → retry mantıklı
Job'un retry kararını verecek mantığı varsa kod içinde, yoksa başka bir queue'ya yönlendir.
3. Dead Letter Queue (DLQ)
Bir job 5 kere denedi, hepsi başarısız. Şimdi ne?
Yanlış cevap: silmek. Veri kaybı.
Doğru cevap: DLQ — Dead Letter Queue. Başarısız mesajları ayrı bir queue'ya koy. Düzenli olarak gözden geçir.
Bizim üretim setup'ında:
- Her ana queue'nun bir DLQ'su var (
payments.queue→payments.dlq) - DLQ'ya düşen mesajlara ait alarm var — DLQ depth > 10 ise PagerDuty
- Haftada bir kere DLQ inceleme zamanı (genellikle on-call kişisinin görevi)
DLQ inceleme sürecinde:
- Hata pattern'ini bul. Aynı hata hep mi geliyor?
- Kod hatası mı, veri sorunu mu?
- Düzeltilebilir mi → düzelt + replay et (mesajı ana queue'ya geri at)
- Düzeltilemezse → manuel intervention (CS notifikasyonu, log, vs.)
DLQ olmadan başarısız mesajlar sessizce kaybolur. Aylar sonra "kullanıcılar bildirim almadı" şikayetiyle keşfedilirsin.
At-least-once vs exactly-once
Çoğu queue sistemi at-least-once delivery garantisi verir: mesaj en az bir kere ulaşacak, belki birden fazla. Exactly-once garantisi pahalı ve kısıtlı (Kafka exactly-once'u, sadece Kafka içinde).
Pratik kural: at-least-once + idempotency = exactly-once efektif.
Yani: queue'dan duplicate mesaj alabilirsin diye varsay, ama job idempotent olduğu için duplicate sorun olmaz. Bu yaklaşım sade ve sağlam.
Job priority ve isolation
Ödeme job'u ile e-posta job'u aynı queue'da olmamalı. Sebep: e-posta queue'su şişmiş, 1 saatlik gecikme var. Ödemeyi de geciktirir.
Pratik: kritiklik seviyesine göre ayrı queue:
critical.queue— ödeme, bakiye, fraudtransactional.queue— bildirim, e-postabulk.queue— analitik, raporlama
Her queue'nun kendi worker pool'u, kendi rate limit'i, kendi alarmı.
Ölçüm
Queue health'ini ölçen 3 metric:
- Queue depth — kuyrukta bekleyen mesaj sayısı. Yükselen trend = sorun var
- Job duration — job ortalama ne kadar sürüyor. Artan duration → downstream sorun
- Failure rate — başarısız job %. > %1 yükselme alarmı
Bu üç metric'e bakmadan queue'ya güvenmek = göz kapalı uçmak.
Sonuç
Background job'lar bir kere kurulup unutulan şey değil. Idempotency, retry, DLQ — bu üçü yoksa, "queue'muz var" demenin pratik anlamı yok. Yarım çözüm, tam çözümden tehlikeli.
İyi haber: bunları bir kere doğru kurarsan, sistem çok düşük operasyonel maliyetle yıllarca çalışır. Beş yıllık aktivetif kullanıyorum, üretimde haftada 2 dakika queue'larla ilgilenmem yeter.
Distributed sistemler, üretim stabilitesi, AI destekli geliştirme üzerine uzun yazılar. Spam yok, çıkmak kolay.
Yorumlar
Henüz yorum yok. İlk yorumu sen bırak.