Mesaj tabanlı mimarilerde en pahalı cümle şudur: “Bu mesaj bir kere gelir.” Üretimde mesajlar tekrar eder, gecikir, sıralaması bozulur veya aynı olay farklı kanallardan yeniden üretilir. Bu yüzden gerçek soru “tekrar olur mu?” değil; “tekrar olduğunda ne yaparız?” olmalıdır.
Bu yazıda replay (yeniden oynatma) ve idempotency (tekrar işleme dayanıklılığı) konusunu sadece tasarım deseni olarak değil; operasyonel runbook ve gözlemlenebilirlik perspektifiyle ele alacağım.
Neden “en az bir kez” gerçeğiyle barışmak gerekir?
Çoğu broker ve dağıtık sistem pratikte “at-least-once” davranır. Bunun sebebi basittir: ağ hatası, consumer restart, timeout ve ack belirsizliği. Bu ortamda güvenilirlik için iki zorunluluk doğar:
- Idempotent consumer: aynı mesajı tekrar alsa da sonucu bozmamalı
- Replay stratejisi: DLQ veya saklanan event’leri kontrollü biçimde yeniden işleyebilmelisin
Idempotency anahtarı: işin merkezindeki karar
Idempotency için en temel soru: “Bu işlemi benzersiz yapan şey nedir?”
Pratikte üç yaygın anahtar tipi:
- Business key: sipariş no, fatura no, işlem id
- Event id: üretici tarafından verilen benzersiz event kimliği
- Natural key + version: (entity id, version/sequence)
Sahada en çok gördüğüm hata: “idempotency key yok, ama log’da correlation var.” Correlation id, izleme içindir; idempotency için tek başına yeterli olmayabilir.
Outbox: kaynağı ve mesajı aynı transaction’a bağlamak
Outbox deseni, “veri yazıldı ama event çıkmadı” veya “event çıktı ama veri yazılmadı” tutarsızlığını azaltır. Basit özet:
- Uygulama transaction içinde hem domain değişimini hem outbox kaydını yazar
- Ayrı bir publisher, outbox’tan broker’a güvenli şekilde yayınlar
- Yayınlandı işareti idempotent güncellenir
Operasyonel kazanım: replay gerektiğinde “kaynak veri + event geçmişi” birlikte yönetilir.
DLQ: çöplük değil, kontrollü karantina
Dead Letter Queue, mesajı “kurtarmak” için vardır; “görmezden gelmek” için değil. DLQ tasarımında runbook’un şu soruları cevaplaması gerekir:
- DLQ’ya düşme sebepleri sınıflandırıldı mı? (şema, doğrulama, downstream hata)
- Hangi sebeplerde otomatik retry, hangilerinde manuel müdahale var?
- Replay nasıl yapılacak ve yan etkiler nasıl kontrol edilecek?
Replay runbook: güvenli yeniden oynatma adımları
Replay bir “komut” değil, bir operasyon akışıdır. Benim kullandığım şablon:
- Dondur: Aynı mesajların tekrar DLQ’ya düşmesini engelle (root cause)
- Örnekle: 10–50 mesaj al, sınıflandır, etkisini anla
- Kuru koşu: Staging’de veya izole consumer’da dene (mümkünse)
- Kademeli replay: batch/ratelimit ile üretimde uygula
- Doğrula: iş metrikleri + teknik metrikler
- Kapat: postmortem ve kalıcı iyileştirme
Kademeli replay için basit bir yaklaşım
Sistemden bağımsız bir prensip: “1x hızla başlama”.
- İlk 5 dk: düşük hız, gözlem
- Sonra: kontrollü artır
- Hata görürsen: otomatik durdurma eşiği
Bu yaklaşım, replay’in ikinci bir incident’e dönüşmesini engeller.
Gözlemlenebilirlik: idempotency görünür olmalı
Idempotency kontrolü yaptıysan, bunu metrikleştir:
idempotency_hit(tekrar geldi, işlem yapılmadı)idempotency_miss(ilk kez geldi, işlem yapıldı)dedup_store_latency(state store gecikmesi)replay_batch_success/fail
Bu metrikler olmadan “idempotent miyiz?” sorusu, üretimde sadece inanç olur.
Sonuç
Mesajlaşma mimarisinde replay ve idempotency, sadece yazılım tasarım kararı değildir; operasyonel olgunluk konusudur. Outbox ile tutarlılığı güçlendirir, DLQ’yu karantina gibi yönetir ve replay’i runbook’a bağlarsan; “tekrar eden mesaj” üretimde korkutucu bir bilinmezlik olmaktan çıkar. Operasyonel liderlik açısından en büyük kazanım, incident anında “tekrar oldu” paniği yerine “replay planımız var” sakinliğini kurabilmektir.