Bir SMS sağlayıcısı yavaşladı — çökmedi bile, sadece yanıt süresi 30 saniyeye çıktı. Yarım saat içinde bütün uygulama yanıt veremez hâle geldi; SMS ile hiç ilgisi olmayan endpoint’ler bile zaman aşımına uğruyordu. Tek bir yavaş dış servis, koca sistemi birlikte aşağı çekmişti.
Bir dış servis er ya da geç çöker. Soru o değil. Soru şu: sizin sisteminiz onunla birlikte mi çökecek?
Zincirleme çöküş nasıl olur
Mantık basit ve acımasız. Yavaş bir dış çağrı, onu yapan worker’ı — ya da PHP-FPM process’ini — kendine kilitler. Çağrı 30 saniye sürerse, o process 30 saniye boyunca başka hiçbir isteğe bakamaz.
İstekler birikir, process havuzu dolar. Havuz dolunca — SMS ile hiç ilgisi olmayanlar dâhil — yeni istekler de bekler. Sağlıklı endpoint’leriniz, hasta bir bağımlılık yüzünden ölür. Buna cascading failure denir ve neredeyse her zaman bir yavaşlamayla başlar, bir çökmeyle değil.
İlk savunma: timeout
İlk ve en ucuz savunma, her dış çağrıya açık ve kısa bir timeout vermektir. Varsayılan timeout’lar — çoğu HTTP istemcisinde 30 saniye veya daha fazla — production için kabul edilemez.
$response = Http::timeout(5) // toplam süre
->connectTimeout(2) // bağlantı kurma süresi
->get('https://sms-provider.example/send');
Laravel queue yazısındaki kural burada da geçerli: default 30 saniye değil, 5. Bir process’in bir bağımlılığı ne kadar süre bekleyeceğini şansa bırakmayın — bu sayı bir karar olmalı.
İkinci savunma: retry — ama dikkatli
Geçici hatalar için retry mantıklıdır, ama körlemesine retry zarar verir. Çöken bir servisi hemen yeniden denemek ona daha çok yük bindirir — buna retry storm denir; servisi tam ayağa kalkamadan tekrar devirir.
Retry yapıyorsanız iki şey şart: exponential backoff (her denemede artan bekleme) ve jitter (rastgele sapma — tüm client’lar aynı anda denemesin).
Http::retry(3, 200, function ($exception) {
return $exception instanceof ConnectionException;
})->timeout(5)->get($url);
Retry sayısını düşük tutun. Üç denemeden sonra hâlâ hata varsa, sorun geçici değildir; denemeye devam etmek yalnızca kendi process’inizi daha uzun bağlar.
Üçüncü savunma: circuit breaker
Timeout tek bir çağrıyı sınırlar; circuit breaker çağrı yapmayı bırakmaya karar verir. Mantığı bir elektrik sigortasından gelir ve üç durumu vardır:
- Closed — her şey normal, çağrılar geçer. Hatalar sayılır.
- Open — son N çağrının çoğu başarısız oldu; devre açılır. Artık dış servise hiç gidilmez, çağrı anında hata döner.
- Half-open — bir süre sonra devre tek bir deneme çağrısına izin verir. Başarılıysa
closed’a döner, değilse tekraropen.
Kazanç şu: dış servis hastayken siz onu beklemezsiniz. 5 saniyelik timeout bile process başına 5 saniyedir; open durumda bu süre 0’a iner. Fail-fast, yavaş-fail’den iyidir.
// Kavramsal akış — eşik, sayaç ve zamanlayıcıyı olgun bir pakete bırakın.
if ($breaker->isOpen('sms')) {
return $this->queueForLater($message); // devre açık: hiç deneme yok
}
try {
$response = Http::timeout(5)->post($smsUrl, $payload);
$breaker->recordSuccess('sms');
} catch (\Throwable $e) {
$breaker->recordFailure('sms'); // eşik aşılırsa devre açılır
return $this->queueForLater($message);
}
Bu mantığı sıfırdan yazmak yerine olgun bir paket kullanın — durum saymayı, eşiği ve zamanlayıcıyı doğru kurmak göründüğünden inceliklidir.
Fallback: devre açıkken ne yapılır
Circuit breaker’ın değeri, open durumda ne yaptığınızla ortaya çıkar. “Hata döndür” en zayıf seçenektir. Daha iyileri:
- Kuyruğa al, sonra gönder. SMS hemen gitmek zorunda değilse, devre kapanınca işlenmek üzere kuyruğa bırakın.
- Bayat ama geçerli veriyi sun. Bir döviz kuru servisi çökmüşse, son bilinen kuru göstermek hiç göstermemekten iyidir.
- Özelliği zarifçe kıs. “Öneriler şu an yüklenemiyor” mesajı, koca sayfayı çökertmekten iyidir.
Hangi fallback’in doğru olduğu işe bağlıdır — ama “fallback yok” bir cevap değildir.
Ne zaman bu kadarına gerek yok?
Her dış çağrı circuit breaker istemez. Timeout her zaman şart. Retry, yalnızca işlem idempotent ise. Circuit breaker ise asıl olarak çağrı sık ve servis kullanıcı yolundayken değer katar — günde birkaç kez çağrılan bir entegrasyon için timeout ve makul bir retry çoğu zaman yeter.
Dış servis çökmesi bir “ihtimal” değil, bir “ne zaman” sorusudur. Onunla birlikte çökmek ise bir tasarım tercihidir — ve değiştirilebilir bir tercih.
Sisteminizi, en zayıf bağımlılığınız kadar kırılgan bırakmayın.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.