İLERİ
Storage Seçimi
Storage, Hangfire'ın kalbidir — tüm job verileri, state'ler ve queue'lar burada saklanır.
Karar Rehberi
| Durum | Öneri | Örnek veya gerekçe |
|---|---|---|
| SQL Server zaten altyapıda | Uygun: SQL Server storage (ücretsiz) | Kurumsal .NET projelerinin %80'i |
| Günlük 100K+ job, <1s latency | Uygun: Redis ( Pro lisansı gerekli) | E-ticaret, fintech real-time |
| Sadece PostgreSQL var | Uygun: PG community paketi (ücretsiz, resmi değil) | Startup, PG-native stack |
| Basit PoC / prototype | Uygun: InMemory (ücretsiz) | Storage kararını ertele |
| Startup production'ı, bütçe yok | Uygun değil: Redis ( Pro lisansı) | SQL Server yeterli |
| Mission-critical, SLA gerekli | Uygun değil: PG community (test coverage düşük) | Resmi storage tercih et |
Karar Matrisi
| Kriter | SQL Server | Redis (Pro) | PostgreSQL |
|---|---|---|---|
| Maliyet | Varsa bedava | Ücretli Hangfire Pro planı gerekir | Bedava |
| Latency | 15s polling | <100ms push | ~1s NOTIFY |
| Throughput | ~1K/s | ~10K+/s | ~2-3K/s |
| Reliability | Çok yüksek | Yüksek | Orta |
| Resmi destek | Evet | Evet | Hayır (community) |
| Kurulum kolaylığı | Kolay | Orta | Kolay |
| Ne zaman | Başlangıç, kurumsal | Yüksek hacim, düşük latency | PG-only altyapı |
builder.Services.AddHangfire(config => config
.UseSqlServerStorage(connectionString, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero, // Long-polling (SlidingInvisibility ile birlikte)
UseRecommendedIsolationLevel = true, // ReadCommitted
DisableGlobalLocks = true, // Schema 7+ gerektirir
TryAutoDetectSchemaDependentOptions = true, // 1.8: Schema'ya göre otomatik optimizasyon
SchemaName = "hangfire", // İzole schema
PrepareSchemaIfNecessary = true // Auto-migration
}));SqlClient seçimi: Hangfire 1.8, varsayılan olarak Microsoft.Data.SqlClient kullanır. Eski projelerde System.Data.SqlClient referansı varsa her ikisi de desteklenir — Hangfire otomatik algılar. Explicit seçim gerekiyorsa: SqlClientFactory = Microsoft.Data.SqlClient.SqlClientFactory.Instance
Bu bölüm Hangfire.Pro.Redis NuGet paketi gerektirir (ücretli). Community sürümde Redis storage YOKTUR. Önce hangfire.io/pricing adresinden lisans alınız.
builder.Services.AddHangfire(config => config
.UseRedisStorage(redisConnectionString, new RedisStorageOptions
{
Prefix = "hangfire:", // Key prefix
Db = 3, // Ayrı Redis DB
InvisibilityTimeout = TimeSpan.FromMinutes(30),
ExpiryCheckInterval = TimeSpan.FromMinutes(5)
}));dotnet add package Hangfire.PostgreSql// NuGet: Hangfire.PostgreSql (community, Frank.HangFire.Mongo da var)
builder.Services.AddHangfire(config => config
.UsePostgreSqlStorage(options => options
.UseNpgsqlConnection(postgresConnectionString), new PostgreSqlStorageOptions
{
SchemaName = "hangfire",
QueuePollInterval = TimeSpan.FromSeconds(5), // PG: LISTEN/NOTIFY destekli, Zero KULLANMAYIN (PG'de long-polling yok)
PrepareSchemaIfNecessary = true,
InvisibilityTimeout = TimeSpan.FromMinutes(5)
}));Ayrı DB mi, Aynı DB + Schema mı?
Hangfire tablolarının nerede yaşayacağı, production'da en çok tartışılan altyapı kararlarından biridir.
| Kriter | Ayrı DB | Aynı DB + Schema | Neden Önemli |
|---|---|---|---|
| Backup/Restore | Bağımsız | App ile birlikte | Hangfire DB restore'u app data'yı etkilemez |
| Connection Pool | İzole | Paylaşımlı | Worker spike'ı app pool'u tüketebilir |
| Disk I/O | Farklı disk/volume | Aynı disk | Job polling + state update yoğun write |
| Operasyonel basitlik | 2 DB yönetimi | Tek DB | Küçük ekiplerde overhead |
| Compliance / audit | Data ayrımı net | Karışık | GDPR: job arg'larda PII olabilir |
| Transaction scope | Distributed TX gerekir | Aynı TX'te enqueue | "Kaydet + job at" atomic olsun |
// appsettings.Production.json
// Connection string'leri secret manager'dan alın (User Secrets, Azure Key Vault, AWS SSM)
{
"ConnectionStrings": {
"DefaultConnection": "Server=db-primary;Database=MyApp;User Id=app_user;Password=${DB_PASSWORD};Encrypt=true;TrustServerCertificate=false",
"HangfireConnection": "Server=db-hangfire;Database=HangfireDb;User Id=hangfire_svc;Password=${HANGFIRE_DB_PASSWORD};Max Pool Size=100;Encrypt=true;TrustServerCertificate=false"
}
}// Program.cs — ayrı connection string
builder.Services.AddHangfire(config => config
.UseSqlServerStorage(
builder.Configuration.GetConnectionString("HangfireConnection"),
new SqlServerStorageOptions
{
SchemaName = "hangfire",
PrepareSchemaIfNecessary = true,
DisableGlobalLocks = true
}));// Aynı DB, farklı schema — basit ama riskli
builder.Services.AddHangfire(config => config
.UseSqlServerStorage(
builder.Configuration.GetConnectionString("DefaultConnection"),
new SqlServerStorageOptions
{
SchemaName = "hangfire", // Default "HangFire" schema
PrepareSchemaIfNecessary = true
}));// Connection pool tükenmesini önlemek için pool size artırın
// Hangfire worker'lar + app request'leri aynı pool'u paylaşır
// Connection string'e ekleyin: "Max Pool Size=200"Anti-Pattern: Hangfire tablolarını app schema'sında (dbo) bırakmak. EF Core migration'ları Hangfire tablolarını "unknown table" olarak algılar ve silmeye çalışabilir. Mutlaka SchemaName belirtin.
Örnek: Bir fintech'te Hangfire aynı DB'deydi. Black Friday'de 50K job enqueue edilince connection pool tükendi (default 100), app 503 vermeye başladı. Çözüm: Hangfire'ı ayrı DB'ye taşıyıp dedicated pool (Max Pool Size=150) tanımladılar. App pool'u 100'de kaldı, sıfır downtime.
Örnek: Bir e-ticaret sitesi başlangıçta SQL Server storage ile başladı (zaten MSSQL kullanıyor). Günlük 10K job sorunsuz. Black Friday'de 100K+ job/gün gerekince Redis storage'a geçiş yaptılar — zero downtime migration: eski job'lar SQL'de tamamlandı, yeniler Redis'e yazıldı.