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.
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.