Node.js ile geliştirme yaparken, hata yönetimi başlangıçta neredeyse çok kolay gelir. Bir rotayı try-catch içine alır, 500 durum kodu gönderirsiniz ve istemci bir sonraki adımda ne yapacağına karar verir. Bu model HTTP için gayet iyi çalışır, ancak arka plan işlerine (background jobs) geçtiğiniz an çöker. Bir kuyruk sisteminde bekleyen bir istemci yoktur. Sadece bir worker, bir payload ve Redis, RabbitMQ veya SQS'de bir yerlerde artan bir yeniden deneme sayacı vardır. Hataları, başarısız web isteklerini yönettiğiniz gibi yönetirseniz, sadece tek bir işlemi kaçırmakla kalmazsınız; tüm hattınızı durdurur, işlem gücünü tüketir veya worker'larınızı aynı zehirli mesaj (poisoned message) üzerinde tekrar tekrar çökertirsiniz.

HTTP Zihniyeti Arka Plan İşlerinde Çöker

İstek-yanıt döngüsünde geri bildirim anlıktır. Bir kullanıcı bir düğmeye tıklar, sunucu bir hata fırlatır ve kullanıcı bir hata ekranı görür. Temizlik basittir. Bir kuyruk worker'ı izole bir şekilde çalışır. Bir iş alır, saniyeler veya dakikalar boyunca üzerinde çalışır ve ardından başarıyı onaylar (acknowledge). Ortada bir şeyler ters giderse, kuyruk nedenini bilmez. Sadece onaylamanın gelmediğini bilir. Yapılandırmanıza bağlı olarak, belki de sonsuza kadar yeniden deneyecektir. Hatalı bir payload, CPU'yu boşa harcayarak ve aslında işlenmesi gereken meşru işlerin arkasına saklanarak worker'lar arasında yüzlerce kez gidip gelebilir.

İki Tür Hata

Dayanıklı kuyrukların ilk kuralı, her hatayı aynı şekilde işlemeyi bırakmaktır. Hataları oluştuğu anda iki gruba ayırmanız gerekir.

Yeniden denenebilir (retryable) hatalar geçicidir. Ağ zaman aşımlarını, üçüncü taraf bir API'den gelen hız sınırlarını (rate limits) veya havuzun kısa süreliğine tükenmesi nedeniyle sıfırlanan bir veritabanı bağlantısını düşünün. Bunlar, baskı altındaki canlı bir sistemin belirtileridir. İki dakika sonraki bir sonraki denemede başarılı olabilirler.

Kalıcı (permanent) hatalar ise zehirli haplar (poison pills) gibidir. Bunlar hatalı payload'ları, şema doğrulama hatalarını veya bir üst servis sözleşmesini (contract) değiştirdiği için eksik bir zorunlu alanı içerir. Bunları yeniden denemek tamamen israftır. Yüzüncü denemede de aynı şekilde başarısız olacaklardır.

Eğer catch bloğunuz bu ikisi arasındaki farkı ayırt edemiyorsa, kuyruğunuz kör uçuş yapıyordur.

Desen 1: Hataları Catch Bloğunda Sınıflandırın

Worker'ınızın catch bloğu, dosyadaki en dikkatli yazılmış kod olmalıdır. Bir hata ortaya çıktığında, onu hemen inceleyin. Hata kodu ECONNRESET mi yoksa bir zaman aşımı mı? Yeniden deneme için kuyruğa alın. Bir SyntaxError, bir Joi doğrulama reddi veya eksik bir yabancı anahtar (foreign key) kısıtlaması mı? Doğrudan bir dead-letter queue (DLQ) içine taşıyın ve bunu yeniden deneme limitinizden düşmeyin.

BullMQ ve Bee Queue dahil çoğu Node.js kuyruk kütüphanesi, özel backoff stratejileri ve hata kancaları (error hooks) tanımlamanıza izin verir. Bunları kullanın. Kalıcı bir hata, varsayılan olarak asla uyumamalı ve üç kez yeniden denememelidir. Diğer işlerinizin akmaya devam etmesi için ana kuyruktan tahliye edilmelidir. DLQ, tam payload'u ve hata bağlamını (error context) korur; bu da hatayı giderdikten veya şemayı düzelttikten sonra işi daha sonra tekrar oynatmanıza (replay) olanak tanır.

Desen 2: Jitter ile Üstel Geri Çekilme (Exponential Backoff)

Anında yeniden denemek agresiftir. Eğer bir alt veritabanı zaten yük altında nefes almakta zorlanıyorsa, elli worker'dan her iki saniyede bir ona tekrar vurmak onu bitirecektir. Geri çekilmeli ve sisteme toparlanması için alan tanımalısınız.

Üstel geri çekilme (exponential backoff) kullanın. İlk hatada bir saniye bekleyin. İkincisinde iki. Sonra dört, sonra sekiz; beş dakika gibi makul bir üst sınıra kadar. Ancak sadece zamanlama yeterli değildir. Eğer her başarısız iş tam olarak aynı aralığı kullanırsa, geri çekilme süresi dolduğunda hepsi çakışacaktır. Bazen "thundering herd" (gök gürültüsü sürüsü) olarak adlandırılan bu senkronize dalga, toparlanmaya çalışan bir servisi altüst edebilir.

Jitter ekleyin. Hesapladığınız gecikmeyi alın ve onu rastgele bir yüzdeyle, belki yüzde on ila yirmi oranında rastgeleleştirin. Dört saniye 4.2 veya 4.7 olur. Bu basit rastgelelik, yeniden deneme sıçramasını dağıtır ve altyapınızın dalgalar halinde darbe almasını engeller.

Desen 3: Idempotency İçin Tasarlayın

Burası kuyruk altyapısının iş mantığıyla (business logic) buluştuğu yerdir. Bir ödeme sağlayıcısı aracılığıyla bir müşteriden ücret alan bir iş hayal edin. Worker ücreti başarıyla çeker, ancak başarıyı veritabanınıza kaydetmeden veya işi onaylamadan önce bağlantı kopar. Kuyruk bir hata görür. Yeniden dener. Müşteriden iki kez ücret alınır.

Node.js'de, her yan etkiyi idempotent hale getirerek bunu önleyin. İş kimliğinden (job ID) veya işe özel bir tanımlayıcıdan bir idempotency anahtarı oluşturun. Ödemeyi oluşturmadan, e-postayı göndermeden veya stok miktarını ayarlamadan önce, işin zaten yapılıp yapılmadığını kontrol edin. Bu anahtarı veritabanınıza ve bir anahtar kabul eden tüm üçüncü taraf API'lere kadar iletin. İşlerinizi, aynı payload'u on kez çalıştırmanın tek bir kez çalıştırmakla aynı sonucu vereceği şekilde yapılandırın. Bu tek alışkanlık, finansal ve veri bütünlüğü hatalarının tüm bir kategorisini ortadan kaldırır.

Desen 4: Dead-Letter Queue'nuzu Bir Dashboard Gibi Ele Alın

Bir DLQ, hatalı işlerin unutulmak üzere gittiği bir mezarlık değildir. O, operasyonel bir araçtır ve en yakından takip ettiğiniz yüzeylerden biri olmalıdır.

DLQ derinliği arttığında tetiklenen uyarılar kurun. DLQ'daki tek bir mesaj bile genellikle doğrulama mantığınızın bozulduğu, bir upstream şemasının değiştiği veya bir downstream servisinin artık tanımadığınız geçersiz veriler gönderdiği anlamına gelir. Bunlar, müşteriler şikayet etmeye başlamadan önce yakalamak isteyeceğiniz sinyallerdir. Ham payload'u, stack trace'i ve zaman damgasını incelemenize olanak tanıyan bir dashboard oluşturun. Hazır bir runbook'unuz olsun: hatayı inceleyin, kodu yamalayın ve ardından mesajları doğru sırayla tekrar oynatın. Eğer kuyruk uygulamanız destekliyorsa, mutlak sayının yanı sıra büyüme oranına göre de uyarı verin; çünkü tek bir hatalı deploy, dakikalar içinde DLQ'yu doldurabilir.

Desen 5: Şüpheye Düştüğünüzde Süreci Çökertin

Node.js, bir V8 isolate içinde tek bir event loop üzerinde çalışır. Ele alınmamış bir promise reddi (unhandled promise rejection) veya bir