Prowid Photos-da bildiriş servisi var: o, saytın başlığındakı «zəng» işarəsinin üzərindəki rəqəmi hesablayır. Cümə günü axşam o, 20 ms əvəzinə 5 saniyəyə cavab verməyə başlayır — və yalnız bir rəqəmə ehtiyacı olan lent hamı üçün açılmır, halbuki fotolar və baza qaydasındadır. Bu dərsdə kiçik nasazlığın necə qəzaya çevrildiyini və onu dayandıran üsulları öyrənəcəyik: timeout-lar, təkrar cəhdlər, idempotentlik, circuit breaker, rate limiting, deqradasiya və health check-lər.
Hər şey sıradan çıxır: kaskad nasazlıq
Şəbəkə paketləri itirir, deploy serveri yenidən işə salır, diskdə yer qurtarır. Bir neçə hissədən ibarət sistemdə nəsə demək olar ki, həmişə nasazdır (halqaların əlçatanlığı vurulur — modulun birinci dərsini xatırlayın). Etibarlılıq «heç nə sıradan çıxmır» demək deyil, «hissə sıradan çıxanda qalanı işləyir» deməkdir.
Donub qalmış servis nə edir? Sistemdə eyni anda «asılı qalan» sorğuların sayı saniyədə sorğu × cavab vaxtı qədərdir. 350 RPS və 50 ms cavabla bu 350 × 0,05 ≈ 18 sorğudur. Hər biri bildirişləri 5 saniyə gözləyirsə, 350 × 5 = 1750 olur. Hər biri soket, yaddaş və bəlkə də bazaya qoşulma saxlayır. Nüsxələr limitlərə dirənir, balanslaşdırıcı health check-in uğursuzluğunu görür, istifadəçilər isə «yenilə» düyməsinə basıb yükü artırırlar. Bu, kaskad nasazlıqdır (cascading failure): ikinci dərəcəli hissənin sıradan çıxması əsas hissələri də özü ilə aşağı çəkir. Məqsədimiz nasazlığı baş verdiyi yerdə saxlamaqdır.
Timeout-lar: hər çağırışın həddi var
Dostunuza zəng edirsiniz, o, dəstəyi götürmür. Yarım dəqiqədən sonra dəstəyi qoyarsınız, axşama qədər siqnal dinləməzsiniz. Kod da belə davranmalıdır. Node.js-də fetch susmaya görə dəqiqələrlə gözləyə bilər, bir çox drayverdə isə limit ümumiyyətlə yoxdur. Müasir üsul — AbortSignal.timeout(): vaxt bitəndə fetch TimeoutError xətası ilə başa çatır.
export async function fetchJson(url, { timeoutMs = 2000, ...init } = {}) {
const res = await fetch(url, {
...init,
signal: AbortSignal.timeout(timeoutMs), // abort after timeoutMs
});
if (!res.ok) {
const err = new Error(`HTTP ${res.status}`);
throw Object.assign(err, { status: res.status });
}
return res.json(); // the same signal also covers reading the body
}
try {
await fetchJson('http://notifications:4001/unread/42', { timeoutMs: 300 });
} catch (err) {
if (err.name === 'TimeoutError') console.warn('notifications is too slow');
else throw err;
}Dəyəri necə seçmək olar? Birinci dərsdəki persentillərə baxın: bildirişlərin p99-u 200 ms-dirsə, 30 saniyə «ehtiyat üçün» yox, 300–500 ms qoyun. Çağırışlar zənciri üçün vaxt büdcəsi lazımdır: lent p95-də 300 ms-ə cavab verir, ona görə də hərəsi 2 saniyə olan üç ardıcıl çağırış mümkün deyil.
⚠️ Timeout serverdəki işi ləğv etmir: bildirişlər sorğunu icra etmiş ola bilər, sadəcə cavab çatmayıb. Timeout-dan sonrakı təkrar işi iki dəfə görə bilər — bu barədə aşağıda.
Təkrar cəhdlər: bir də, amma ağılla
Bir çox nasazlıqlar qısa olur: şəbəkə «göz qırpıb», server deploy zamanı yenidən işə düşüb. Təkrar belə halları xilas edir, amma hər şeyi təkrarlamaq olmaz: