tolgacelik.net
Tüm yazılar
·3 dk

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.

queuerabbitmqredisidempotencyproduction

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_id var. 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_processed sorusunu cevaplamanın yolu: ya DB'de processed_payments tablosu, 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.queuepayments.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:

  1. Hata pattern'ini bul. Aynı hata hep mi geliyor?
  2. Kod hatası mı, veri sorunu mu?
  3. Düzeltilebilir mi → düzelt + replay et (mesajı ana queue'ya geri at)
  4. 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, fraud
  • transactional.queue — bildirim, e-posta
  • bulk.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:

  1. Queue depth — kuyrukta bekleyen mesaj sayısı. Yükselen trend = sorun var
  2. Job duration — job ortalama ne kadar sürüyor. Artan duration → downstream sorun
  3. 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.

PaylaşLinkedInX
Bir sonraki yazı doğrudan kutuna düşsün

Distributed sistemler, üretim stabilitesi, AI destekli geliştirme üzerine uzun yazılar. Spam yok, çıkmak kolay.

Sadece e-posta. Bu sunucuda saklanır. Hiçbir yerde paylaşılmaz, satılmaz.

Yorumlar

Henüz yorum yok. İlk yorumu sen bırak.

Yorum bırak

Hiçbir yerde gösterilmez. Sadece gerekirse cevap vermek için.