UZMAN
Anti-Patterns & Common Mistakes
Aşağıdaki hatalar production'da en sık karşılaşılan sorunlardır. Her birini "neden kötü" ve "ne yapmalı" şeklinde açıklıyoruz. Korkmana gerek yok — hepsinin basit çözümü var.
Seviye: Uzman — Production deneyimi ve failure mode bilgisi gerektirir. Bu bölümü checklist olarak kullanın.
Ne Zaman Dikkat Etmeli
| Anti-Pattern | Tetiklenme Anı | Doğru Yaklaşım | Gerçek Hayat |
|---|---|---|---|
| Auto-ack | Consumer coding | Manual ack + error handling | Fintech: ödeme mesajı kaybı = finansal zarar |
| Connection-per-message | Publish logic | Singleton connection + channel pool | E-ticaret: 1K msg/s → FD exhaustion |
| Unbounded queue | Queue declare | x-max-length + overflow policy | SaaS: disk alarm → cluster-wide publish stop |
| No publisher confirms | Publish logic | ConfirmSelect + WaitForConfirms | Chat: sessiz mesaj kaybı kullanıcı görmez |
| Monolith queue | Architecture | Event tipine göre ayrı queue | Marketplace: PDF generation tüm event’leri bloklar |
Anti-Pattern Detayları
1. Auto-ack in Production
// ❌ Mesaj deliver edildiği anda siliniyor
await channel.BasicConsumeAsync(
queue: "payments",
autoAck: true, // ← TEHLİKELİ
consumer: consumer);
// Consumer crash → mesaj kayboldu, kurtarılamaz!
// ✅ İşlem başarılı olduktan SONRA ack
await channel.BasicConsumeAsync(
queue: "payments",
autoAck: false, // ← GÜVENLİ
consumer: consumer);
// Consumer crash → mesaj requeue → başka consumer alır
Neden tehlikeli: Consumer crash'te mesaj kurtarılamaz şekilde kaybolur. Ödeme, sipariş gibi kritik iş akışlarında veri kaybı = finansal zarar.
2. Connection Per Message
// ❌ Her publish'te TCP handshake + SASL auth + channel creation
public async Task PublishAsync(byte[] body)
{
var factory = new ConnectionFactory { HostName = "rabbitmq" };
using var conn = await factory.CreateConnectionAsync();
using var ch = await conn.CreateChannelAsync();
await ch.BasicPublishAsync("ex", "rk", body: body);
}
// 1000 msg/s = 1000 TCP connection open/close → broker FD exhaustion
// ✅ Singleton connection, scoped channel
public class MessagePublisher(IConnection connection)
{
public async Task PublishAsync(byte[] body)
{
using var ch = await connection.CreateChannelAsync();
await ch.ConfirmSelectAsync();
await ch.BasicPublishAsync("ex", "rk", body: body);
await ch.WaitForConfirmsOrDieAsync(TimeSpan.FromSeconds(5));
}
}
// 1 TCP connection, binlerce channel → minimal overhead
Neden tehlikeli: Her TCP connection = file descriptor + memory. 1000 conn/s açıp kapamak broker'ı doyurur. Connection limit aşılınca tüm client'lar bağlanamaz.
3. Unbounded Queues (No Length Limit)
// ❌ Hiçbir limit yok — queue sonsuza büyüyebilir
await channel.QueueDeclareAsync("events", durable: true,
exclusive: false, autoDelete: false);
// Consumer down → queue GB'larca büyür → disk alarm → TÜM publish durur
// ✅ Limit + overflow stratejisi + DLX
var args = new Dictionary<string, object?>
{
["x-queue-type"] = "quorum",
["x-max-length"] = 500_000,
["x-overflow"] = "reject-publish",
["x-dead-letter-exchange"] = "dlx.main",
["x-message-ttl"] = 86_400_000 // 24h
};
await channel.QueueDeclareAsync("events", durable: true,
exclusive: false, autoDelete: false, arguments: args);
// Limit aşılırsa: publisher'a nack → retry → alert
Neden tehlikeli: Kontrolsüz büyüyen queue disk alarm tetikler. Disk alarm tetiklenince cluster genelinde tüm publishing durur — sadece o queue değil, tüm queue'lara publish yapılamaz.
4. No Publisher Confirms
// ❌ Mesaj gönder, confirmed olup olmadığını kontrol etme
await channel.BasicPublishAsync("ex", "rk", body: body);
// Network glitch, broker crash → mesaj kayboldu ama bilmiyorsun
// ✅ Confirm aktif, hata durumunda retry
await channel.ConfirmSelectAsync();
await channel.BasicPublishAsync("ex", "rk", body: body);
try
{
await channel.WaitForConfirmsOrDieAsync(TimeSpan.FromSeconds(5));
}
catch (Exception)
{
// Confirm gelmedi → mesaj broker'a ulaşmamış olabilir → RETRY
await RetryPublishAsync(body);
}
Neden tehlikeli: Confirm olmadan mesajın broker'a gerçekten ulaşıp ulaşmadığını bilemezsiniz. Network packet loss, broker restart gibi senaryolarda sessiz mesaj kaybı yaşarsınız.
5. Monolith Queue
// ❌ Tüm event tipleri tek queue'da
await channel.QueueDeclareAsync("all-events", durable: true, ...);
// OrderCreated, PaymentProcessed, EmailSent, UserSignup hepsi aynı yerde
// Consumer: giant switch statement
// Bir tipin slow processing'i diğerlerini bloklar (head-of-line blocking)
// ✅ Her event tipi kendi queue'sunda
await channel.QueueDeclareAsync("orders.created", ...);
await channel.QueueDeclareAsync("payments.completed", ...);
await channel.QueueDeclareAsync("notifications.email", ...);
// Topic exchange ile routing
await channel.QueueBindAsync("orders.created", "domain.events", "order.*.created");
await channel.QueueBindAsync("payments.completed", "domain.events", "payment.*.completed");
// Her queue bağımsız scale, bağımsız retry, bağımsız monitoring
Neden tehlikeli: Head-of-line blocking: Yavaş bir event tipi (örn: PDF generation) tüm queue'yu yavaşlatır. Monitoring zorlaşır (hangi event tipi birikti?). Bağımsız scaling imkansız.
Genel Kural: Her bağımsız iş birimi kendi queue'sunda olmalı. Queue'ları domain boundary + processing characteristic'e göre ayırın. Bir queue'daki tüm mesajlar aynı tipte ve benzer processing time'a sahip olmalı.