RRabbitMQ Handbook

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 Severity Matrix CRITICAL: Auto-ack in production CRITICAL: No DLX configured CRITICAL: 2-node cluster HIGH: Connection per message HIGH: Unbounded queues HIGH: No publisher confirms MEDIUM: God exchange pattern MEDIUM: Prefetch unlimited MEDIUM: Monolith queue (all-in-one) Data loss / Outage Performance / Reliability Maintainability / Waste

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