Ana Sayfa / Blog / Uzun süreli job’lar için retry ve idempotency stratejisi

Uzun süreli job’lar için retry ve idempotency stratejisi

Bir email gönderimi iki kez gitti, bir ödeme iki kez çekildi. Retry tasarımında idempotency olmayınca neler yaşadığımı ve nasıl düzelttiğimi yazdım.

Bir fatura otomasyonu sistemi yazmıştım. Kullanıcıya ayda bir fatura gönderiyordu. Bir gün müşteri aradı, aynı ay iki fatura gelmiş, kredi kartından iki kez çekim yapılmış. Log’a bakınca gördüm, bir retry zinciri yanlış tetiklenmiş, iki iş iki kez koşmuş. Idempotency tasarımındaki büyük dersimi aldığım gün.

Retry ve idempotency el ele gitmek zorunda. Biri varsa diğeri olmak zorunda. Yoksa hem problem çözmüyorsunuz hem yeni problem yaratıyorsunuz.

Retry ne zaman gerekli?

  • Geçici ağ hatası
  • Kısa süreli servis kesintisi
  • Rate limit dönüşü
  • Database deadlock
  • Timeout (ama bu tehlikeli, altta başarılı olabilir)

Basit kural: kesin başarısız olduğunu bildiğiniz durumlarda retry. Belirsiz durumlarda, “acaba oldu mu” diye düşünüyorsanız, idempotency olmadan retry vurmayın.

Idempotency nedir?

Aynı operasyonu n kez çağırmak bir kez çağırmakla aynı sonucu vermesi. Matematiksel olarak f(f(x)) = f(x). Pratikte, aynı fatura gönderme isteği 5 kez tetiklense bile sadece bir fatura oluşuyor.

Idempotency nasıl tasarlanır?

En yaygın yaklaşım idempotency key. Her istek için unique bir ID üretilir, sunucu bu ID’yi daha önce işlediyse aynı sonucu döndürür. Stripe’ın yaklaşımı ünlü. Idempotency-Key header ile gelen istekler, 24 saat boyunca aynı key ile gelirse ilk işlemin cevabını dönüyor.

İmplementasyon şeması:

  1. İstek gelir, idempotency_key header’ı okunur.
  2. Veritabanında kayıt aranır, varsa kayıtlı cevap döndürülür.
  3. Yoksa işlem başlatılır.
  4. İşlem sırasında lock koyulur (aynı anda iki istek gelirse ikincisi bekler).
  5. Sonuç kaydedilir, lock açılır.

Lock kritik. Lock olmazsa iki istek paralel gelirse ikisi de “daha önce işlenmedi” görür, iki kez işlem yapar.

Bizim fatura sisteminde yaptığım hata tam buydu. Idempotency key vardı ama lock yoktu. Cron iki kez tetiklendi, iki job paralel koştu, ikisi de “daha önce gönderilmedi” gördü, iki fatura gönderdi.

Uzun süreli job’larda retry

Uzun süreli işler için exponential backoff uygun. 1 saniye bekle, sonra 2, sonra 4, sonra 8. Üst sınır koyun (örneğin 60 saniye). Jitter ekleyin (random ±30 saniye) ki sunucu yeniden kaldırıldığında tüm client’lar aynı anda vurmasın.

Retry bütçesi olsun. Sınırsız retry flood yaratır, sınırlı retry güvence verir. 3-5 kez retry çoğu durum için yeter.

Retry sayısını header’da takip edin:
X-Retry-Count: 3
X-Original-Request-At: 2026-02-16T10:00:00Z

Bu izlenebilirlik için kritik.

Queue tabanlı job’lar

Job queue (Sidekiq, Laravel Queue, BullMQ) kullanıyorsanız queue’nun kendi retry mekanizması var. Ama queue bazen job’u “başarılı olmadı sanırım” diye iki kez veriyor. Worker “done” demeden ölürse job tekrar queue’ya gelir. Sizin worker’ın tamamen idempotent olması şart.

Özellikle at-least-once delivery veren kuyruklar (SQS, RabbitMQ default mode) duplicate mesaj göndereceğini önceden bildirir. Tam olarak idempotency tasarımı şart.

Fallback senaryosu

Retry zincirinin sonunda başarısız olursa ne olacak? Dead letter queue (DLQ) kullanın. Job DLQ’ya gider, manuel inceleme kuyruğuna girer. Ötesinde log, alert, oncall ping.

Database level idempotency

Meblağ işlemi gibi kritik durumlarda veritabanı seviyesinde de garanti olmalı. Unique constraint şart. Örneğin transactions tablosunda (idempotency_key, user_id) üzerinde unique index. Iki kez aynı işlem eklenmek istenirse constraint violation fırlar, retry başladığı yere döner.

Asenkron işlemlerde completion garantisi

Bir kullanıcıya “işleminiz başarılı” demek için işlem gerçekten tamamlanmış olmalı. Uzun iş için polling veya webhook kullanın. “Başladı” cevabı değil, “bitti” cevabı sonra dönün.

Bizim fatura sisteminde iki çekim çıktıktan sonra uyguladıklarım:

  1. Fatura tablosunda unique constraint: (user_id, billing_period_start, billing_period_end). İki kez gönderilmek istenirse constraint patlıyor.
  2. Cron yerine distributed lock. Job başlamadan önce Redis’ten SETNX ile lock alıyor. Alamıyorsa “zaten koşuyor” deyip atıyor.
  3. Job log tablosu. Her tetiklemenin izi kalıyor, analiz edilebiliyor.

O tarihten beri duplicate fatura problemi olmadı. Ama bu deneyim öğretti, retry tasarımında idempotency ilk adım, retry ikinci. Tersine değil.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç