Aşağı akıştaki bir API yirmi dakika düştü. Dört bin mesaj retry’larını tüketip dead-letter kuyruğuna düştü. API artık geri geldi, mesajlar hâlâ geçerli, ve bariz hamle onları geldikleri yere geri koymak. Broker konsoluna uzanıp orders.dlq’dan orders’a mesaj taşımaya başlıyorsun.
Orada dur. Bir dead-letter kuyruğu bir bekleme alanıdır, mezarlık değil — ama mesajları oradan çıkarmak yüklü bir operasyondur ve “sadece geri taşı”, atlatılmış bir kesintiyi ikinci bir olaya çevirmenin yoludur.
Naif bir replay’in işi daha kötü yapmasının iki yolu
Kuyruğu yeniden zehirler. DLQ’n nadiren sadece kesintinin kurbanlarını tutar. Aralarına gerçek bir nedenle ölen mesajlar karışmıştır — bozuk bir payload, bir bug, yönlendirilemeyen bir URN — ve onlar replay ettiğin an yine başarısız olacak. Replay’in her şeyi körlemesine geri kürelerse, gerçekten bozuk mesajlar başarısız olur, tekrar DLQ’ya düşer, sen onları yine replay edersin. CPU yiyen, logları dolduran ve aslında başarılı olacak mesajları gömen bir döngü.
Yan etkileri yeniden tetikler. Üç denemeden sonra dead-letter’a düşen bir mesaj her seferinde kısmen çalışmış olabilir. Kart çekildi ama onay e-postası patladı; mesaj başarısız oldu, retry oldu, dead-letter’a düştü. Onu naifçe replay et, handler baştan sona yine çalışır — şimdi nihayet gönderdiği e-postanın üstüne ikinci bir tahsilat. Kuyruk bir mesaj taşıdı; müşterin iki kez faturalandı.
İki tuzak da replay’i “byte’ları geri taşı” sanmaktan gelir. Öyle değil. “Bir mesajı sıfırla ve güvenle yeniden işle” — ve bu kelimelerin her biri bir emniyet kilidi.
Sıfırla, ama kimliği koru
Dead-letter’a düşmüş bir mesaj, nasıl öldüğünü anlatan ek bir blok taşır — neden, orijinal kuyruk, deneme sayısı. Onu replay etmek için sıfırlaman gerekir: o bloğu düşür, ve deneme sayacını sıfıra geri al ki taze bir retry bütçesi alsın, anında yeniden tükenmesin.
Ama reset, yeni ile aynı şey değil. Mesajı tanımlayan her şeyi koru — id’sini, payload’ını, ve hepsinden önemlisi korelasyon kimliğini (trace_id). İki nedenle:
- İzleyemediğin replay edilmiş bir mesaj operasyonel bir kör nokta.
trace_id’yi koru, replay orijinal hatayla aynı trace’te görünsün. - Korunan mesaj id’si, replay’i retry’a güvenli kılan şey. Tüketicilerin idempotent ise — mesaj id’si üzerinden dedupe ediyorsa — zaten yarı-başarılı olmuş bir mesajı replay etmek dedupe tarafından yakalanır, yeniden çalıştırılmaz. Idempotency ve replay ortaktır: idempotency, bir kuyruğu nefesini tutmadan replay etmeni sağlayan şeydir.
Önce production’a doğrultma
Reset ve idempotent olsa bile, bir replay canlı bir sisteme yazmadır. Güveni aşamalarla kazan:
- Dry-run. DLQ’yu oku ve neyin replay edileceğini raporla — sayılar, hedefler, nedenler — ve her mesajı dokunmadan geri koy. Tek el atmadan patlama yarıçapını öğrenirsin.
- Sandbox. Replay’i, tüketicilerinin dış yan etkileri stub’lanmış production-dışı bir kuyruğa yönlendir. Mesajlar gerçek handler mantığından akar; kimse faturalandırılmaz. “İyi” mesajların gerçekten iyi olduğunu burada öğrenirsin.
- Sonra production, ilk ikisi sıkıcı hâle geldiğinde.
Sıra, herhangi bir tek adımdan daha önemli. Mesajlar “iyi görünüyor” diye doğrudan production’a replay etmek, ilk paragraftaki broker konsolunda onları taşıyan güvenin aynısıdır.
Select et; saçma
Kesintinin kurbanları ortak bir imza taşır — bir zaman penceresi, bir hata reason’ı, bir URN. O kümeyi replay et, tüm kuyruğu değil. Reason’a göre seçmek, “timeout’tan ölen 4.000’i replay et” ile “4.000 timeout artı yine geri gelecek 30 gerçekten-zehirli mesajı replay et” arasındaki farktır. Zehirli mesajlar replay etme sorunun değil; düzeltme sorunun, ve onları görebildiğin DLQ’da kalmalılar.
Resetleyemeyeceğin yan etki
İşte dürüst zor kısım. Reset envelope’u temizler; idempotency birebir replay’leri dedupe eder; sandbox sen test ederken production’ı korur. Ama production’a kasıtlı bir replay — gerçek olanı, aslında istediğin — handler’ı çalıştırır, ve handler ne yapıyorsa onu yapar: tahsilat çeker, e-posta atar, üçüncü tarafı çağırır. Idempotency bir kopyayı durdurur; amaçlanan yeniden işlemenin yan etkileriyle birlikte işini yapmasını durdurmaz.
Temiz çözüm, bir handler’a replay çalıştırdığını bildirip zaten olmuş dış etkileri atlamasını sağlamak — veritabanı yazmasını yeniden yap, ama e-postayı yeniden gönderme. İş şu ki o bayrağı nereye koyacaksın: donmuş bir mesaj envelope’unda ekleyecek yer yok. Cevap, dağıtık izlemenin aynı kısıt için kullandığıyla aynı — onu bant dışında taşı, mesajın yanında giden ve runtime’ın handler’a sunduğu bir transport header olarak. Handler’ın kontrol edebileceği, yalnızca replay edilmiş mesajlarda set edilen, normal yolda hiçbir maliyeti olmayan bir replay-bypass işareti. En zahmetli parça, ve en sona bırakılacak doğru parça — reset, dry-run, sandbox ve select replay’i rutin olacak kadar güvenli hâle getirdikten sonra.
Bir dead-letter kuyruğu, çeyreğin en kötü gününde sistemindeki en faydalı şeydir. Onu replay etmeyi olduğu şey gibi ele al — canlı trafiği yeniden işlemek — ve faydalı kalır. Byte taşımak gibi ele al, ve DLQ’nun devam filmi gelir.
İlgili: BabelQueue redrive-and-replay spec’i bu sıfırla-ve-yeniden-işle şeklini standartlaştırır; dlq-redrive örneği ise onun çalıştırılabilir bir hâlidir.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.