HHangfire Handbook

ORTA

Retry & Error Handling

Hangfire, başarısız job'ları otomatik retry mekanizması ile yeniden çalıştırır. Varsayılan davranış: 10 deneme, artan bekleme süreleri.

Processing Worker çalıştırır Failed Exception! Scheduled Retry beklemede Succeeded Artı: Tamamlandı Failed (Final) Max retry aşıldı retry (exponential backoff) max attempts

Karar Rehberi

Durum Öneri Örnek veya gerekçe
Geçici hata (network) Uygun: Retry External API timeout
Kalıcı hata (validation) Uygun değil: Retry anlamsız Geçersiz e-posta adresi
İdempotent olmayan iş Uygun değil: Dikkatli retry Ödeme çekme (çift çekim riski)
Rate limit aşımı Uygun: Artan delay ile API 429 Too Many Requests

Retry Stratejileri

// 1. Varsayılan: 10 deneme, otomatik exponential backoff
// Bekleme: ~30s, 1m, 2m, 5m, 10m, 20m, 30m, 1h, 2h, 4h

// 2. Custom delays
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 30, 120, 600, 3600, 14400 })]
public async Task CallExternalApiAsync(int requestId) { ... }

// 3. Belirli exception'larda retry'ı atla (1.8.15+ ExceptOn)
[AutomaticRetry(Attempts = 5, ExceptOn = new[] { typeof(ValidationException), typeof(ArgumentException) })]
public async Task ProcessOrderAsync(int orderId) { ... }
// ValidationException veya ArgumentException fırlatılırsa retry YAPILMAZ, direkt Failed state.
// SmartRetryFilter yazmadan native çözüm!

// 4. Retry yok — idempotent olmayan kritik iş
[AutomaticRetry(Attempts = 0)]
public async Task DebitAccountAsync(int transactionId) { ... }

// 5. Başarısız olunca Failed state'te bekle (dashboard'da görünsün)
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
public async Task ImportDataAsync(int batchId) { ... }

// 6. Başarısız olunca sil (önemsiz job)
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public async Task SendAnalyticsAsync(int eventId) { ... }

Exception-Based Retry Control

1.8.15+ kullanıyorsanız: Basit "bu exception'da retry yapma" senaryoları için yukarıdaki ExceptOn property yeterlidir. Aşağıdaki SmartRetryFilter yaklaşımı, daha karmaşık logic gerektiğinde kullanılır (rate limit → custom schedule, conditional retry, logging).

public class SmartRetryFilter : JobFilterAttribute, IElectStateFilter
{
    public void OnStateElection(ElectStateContext context)
    {
        if (context.CandidateState is FailedState failedState)
        {
            // Kalıcı hatalar için retry'ı durdur
            // AutomaticRetry filter'dan ÖNCE çalışması için Order ayarla
            if (failedState.Exception is ValidationException or
                ArgumentException or
                UnauthorizedAccessException)
            {
                // DeletedState ile retry tamamen engellenir
                // FailedState atarsanız AutomaticRetry yine retry yapar!
                context.CandidateState = new DeletedState
                {
                    Reason = "Permanent failure: " + failedState.Exception.GetType().Name
                };
                return;
            }

            // Rate limit: retry'ı manual schedule et
            if (failedState.Exception is RateLimitException rle)
            {
                var delay = TimeSpan.FromSeconds(rle.RetryAfterSeconds);
                context.CandidateState = new ScheduledState(delay)
                {
                    Reason = $"Rate limited, retrying after {rle.RetryAfterSeconds}s"
                };
            }
        }
    }
}

// Registration — Order önemli! SmartRetry, AutomaticRetry'dan önce çalışmalı
GlobalJobFilters.Filters.Add(new SmartRetryFilter(), order: 0);

Global Retry Policy

// Program.cs'de global ayar
builder.Services.AddHangfire(config =>
{
    config.UseFilter(new AutomaticRetryAttribute
    {
        Attempts = 5,
        DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 },
        OnAttemptsExceeded = AttemptsExceededAction.Fail
    });
});

Idempotency zorunludur: Hangfire "at-least-once" garantisi verir. Aynı job birden fazla kez çalışabilir (retry, server crash). Job'larınızı mutlaka idempotent tasarlayın — aynı giriş ile aynı sonuç, yan etki tekrarı yok.

Örnek: Payment gateway entegrasyonunda, ödeme isteği gönderildi ama response timeout oldu. Job retry eder — ama gateway'e idempotency key göndererek aynı ödemenin tekrar çekilmesini önler. Gateway "already processed" döner, job succeeded olarak kapanır.