Ders 20 / 25
Yük Boşaltma
Aşırı yükte denetimli reddetme: eşzamanlılık eşiğine dayalı kabul denetimi, kabul edilenle reddedilenin gecikme farkının ölçümü, reddin HTTP'deki biçimi ve yeniden deneme fırtınasının önlenmesi.
İçindekiler
Bir önceki ders geri basıncı kurdu ve bir sınırla bitti: geri basınç sistemin iç halkalarında çalışır, dış ucunda duran istemciye taşınamaz. İstek işleyen bir sunucu, arka plan kuyruğu dolduğu için isteği süresiz bekletemez.
O zaman aşırı yükte ne yapılır. Yanıt, kabul edilmesi güç olduğu için sık atlanır: isteği reddetmek. Reddetmek bir arıza değil, bir tasarım kararıdır. Reddetmeyen bir sunucu istekleri kabul etmeye devam eder, her birinin gecikmesi büyür ve bir noktadan sonra hiçbir istek zamanında karşılanmaz. Bu duruma çöküş denir ve reddetmekten çok daha kötüdür: reddedilen istek en azından hızlıca reddedilmiştir. Bu ders reddin nasıl denetimli yapılacağını kurar ve iki durumun gecikmesini ölçerek karşılaştırır.
Kabul Denetimi
Yük boşaltma (load shedding), sistemin kapasitesini aşan işi kabul etmeyip geri çevirmesidir. Kararı veren yapıya kabul denetimi (admission control) denir ve iş başlamadan önce çalışır.
Kabul denetimi, hız sınırlamadan iki noktada ayrılır. Hız sınırlama belirli bir istemcinin belirli bir sürede yapabileceği istek sayısını sınırlar; ölçütü istemcidir ve amacı adil paylaşımdır. Kabul denetiminin ölçütü ise sistemin o anki durumudur; amacı, kabul edilmiş işlerin zamanında bitmesini korumaktır. Aynı istemci, sistem boşken kabul edilir, doluyken reddedilir.
Eşik Neye Konur
Eşiğin saniyedeki istek sayısına konması yaygın ama yanıltıcıdır. Saniyedeki istek sayısı işin süresini içermez: aynı hız, iş yirmi milisaniye sürüyorsa rahat, üç yüz milisaniye sürüyorsa taşırıcıdır.
Doğru ölçüt eşzamanlılıktır: aynı anda işlenen iş sayısı. Önceki derste kullanılan bağıntısı bunu doğrudan verir; sistemdeki iş sayısı sabitlenirse, ortalama süre de sabitlenmiş olur. Eşzamanlılığı sabitlemek gecikmeyi sabitlemektir.
Aşağıdaki sunucu iki eşik taşır: aynı anda işlenebilecek iş sayısı ve kuyrukta bekleyebilecek iş sayısı. İkisinin toplamı, sistemin kabul ettiği en büyük yüktür.
// kabul.mjs — is sunucusu; iki kipte calisir // node kabul.mjs --sinirsiz gelen her istek kuyruga alinir // node kabul.mjs --denetimli esik asilinca istek 503 ile geri cevrilir import { createServer } from "node:http"; const DENETIMLI = process.argv.includes("--denetimli"); const EN_COK_ESZAMANLI = 2; // ayni anda islenen is sayisi const EN_COK_KUYRUK = 2; // kuyrukta bekleyebilecek is sayisi const IS_SURESI = 300; // bir isin suresi (ms) let calisan = 0; const kuyruk = []; const isYap = () => new Promise((c) => setTimeout(c, IS_SURESI)); async function baslat(gorev) { calisan++; await isYap(); calisan--; gorev.yanit.writeHead(200, { "content-type": "text/plain" }); gorev.yanit.end(`${gorev.durum}\n`); const sonraki = kuyruk.shift(); if (sonraki) baslat(sonraki); } createServer((istek, yanit) => { yanit.sendDate = false; if (calisan < EN_COK_ESZAMANLI) return baslat({ yanit, durum: "kabul" }); if (!DENETIMLI || kuyruk.length < EN_COK_KUYRUK) return kuyruk.push({ yanit, durum: "kuyruk" }); // Esik asildi: istek beklemeye alinmaz, ne zaman tekrar denenecegi bildirilir. yanit.writeHead(503, { "content-type": "text/plain", "retry-after": "1" }); yanit.end("bosaltildi\n"); }).listen(8352, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8352"));
Ölçüm
Aşağıdaki betik sunucuyu iki kipte de ayağa kaldırır, her seferinde on isteği aynı anda gönderir ve sonuçları durum koduna ve yüz milisaniyelik gecikme dilimlerine göre gruplar. Bağlantı noktası 8352 keyfîdir ve boş olmalıdır.
#!/usr/bin/env bash # kabul.mjs'yi iki kipte baslatir, her kipte on istegi ayni anda gonderir, sonucu ozetler. A=http://127.0.0.1:8352 olc() { node kabul.mjs "$1" > /dev/null & sunucu=$! sleep 1 pid=() for i in $(seq 1 10); do curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' "$A/is" & pid+=($!) done > ham.txt 2>&1 wait "${pid[@]}" echo "--- $2 ---" awk '{ printf "durum=%s gecikme=%2d dilim\n", $1, int($2 * 10) }' ham.txt | sort | uniq -c kill "$sunucu" sleep 0.5 } olc --sinirsiz "kabul denetimi yok: her istek kuyruga alinir" olc --denetimli "kabul denetimi var: esik asilinca 503"
--- kabul denetimi yok: her istek kuyruga alinir --- 2 durum=200 gecikme= 3 dilim 2 durum=200 gecikme= 6 dilim 2 durum=200 gecikme= 9 dilim 2 durum=200 gecikme=12 dilim 2 durum=200 gecikme=15 dilim --- kabul denetimi var: esik asilinca 503 --- 2 durum=200 gecikme= 3 dilim 2 durum=200 gecikme= 6 dilim 6 durum=503 gecikme= 0 dilim
Dilim sayısı yüz milisaniyelik birimdir; iş süresi üç yüz milisaniye olduğu için ilk çift üç dilimde, ikinci çift altı dilimde yanıt alır. Yavaş bir makinede dilim sayıları bir üste kayabilir.
İki kip aynı işi yapıyor, ama gecikme dağılımları taban tabana zıt.
Denetimsiz kipte hiçbir istek reddedilmedi ve kimse memnun olmadı: son iki istek bir buçuk saniye bekledi. Yük on yerine yüz istek olsaydı son isteğin gecikmesi on beş saniye olurdu. Kabul edilen her istek, kendinden önce kabul edilmiş herkesin gecikmesini taşıyor.
Denetimli kipte dört istek karşılandı, altısı sıfırıncı dilimde, yani bekletilmeden reddedildi. Kabul edilenlerin gecikmesi üç ve altı dilimde kaldı; sistem, kabul ettiği isteklere verdiği sözü tuttu.
Fark tek cümleyle şudur: kabul denetimi, belirsiz bir gecikmeyi kesin bir redde çevirir. İstemci için bu iyileşmedir, çünkü redde karşı bir davranışı vardır — yeniden denemek, işi ertelemek, kullanıcıya bilgi vermek. Belirsiz bir beklemeye karşı hiçbir davranışı yoktur.
Reddin Biçimi
Reddin nasıl bildirildiği, reddedilme kararı kadar önemlidir. Aşağıdaki betik sunucuyu doygunluğa getirip bir isteğin ham yanıtını gösterir.
#!/usr/bin/env bash # Doygunluga ulasmis sunucudan gelen reddin yanitini basliklariyla gosterir. node kabul.mjs --denetimli > /dev/null & sunucu=$! sleep 1 A=http://127.0.0.1:8352 pid=() for i in 1 2 3 4; do curl -sS -o /dev/null "$A/is" & pid+=($!); done # 2 calisan + 2 kuyruk sleep 0.1 echo "--- doygunlukta gelen istegin yaniti ---" curl -sS -D - "$A/is" wait "${pid[@]}" kill "$sunucu"
--- doygunlukta gelen istegin yaniti --- HTTP/1.1 503 Service Unavailable content-type: text/plain retry-after: 1 Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunked bosaltildi
Üç ayrıntı bilinçli seçimdir. Durum kodu 503’tür, 500 değil: 500 sunucunun hatalı
olduğunu, 503 ise geçici olarak hizmet veremediğini söyler. İkisi arasındaki fark
istemcinin davranışını belirler — 500 alan istemci yeniden denememelidir, 503 alan
denemelidir. Retry-After başlığı bu denemenin ne zaman yapılacağını saniye cinsinden
bildirir; istemcinin tahmin yürütmesi gerekmez. Ve gövde kısadır: doygun bir sunucunun
uzun bir hata gövdesi üretmesi, kaçınmaya çalıştığı işin kendisidir.
Bağlantı başlıkları çalışma zamanının varsayılan ayarına bağlıdır.
Neyi Boşaltmalı
Buraya kadar reddedilecek istek, eşiği aşan istekti. Bu en yalın politikadır ve bir kusuru vardır: tüm işleri eşit görür. Oysa kütüphane servisinde bir ödünç iade işlemi ile aylık rapor talebi eşit değildir.
İki ölçüt bu ayrımı verir. Önem sırası ile boşaltmada her işe bir sınıf verilir ve doygunlukta önce düşük sınıf reddedilir; kritik sınıfa ayrılmış bir eşzamanlılık payı her zaman boş tutulur. Son tarihe göre boşaltmada ise her iş bir gecikme bütçesiyle gelir; kuyrukta beklerken bütçesi dolan iş, sırası geldiğinde işlenmeden atılır. İkincisi özellikle değerlidir, çünkü kuyruğun başındaki işin çoktan anlamsızlaştığı durumu yakalar — istemci çekilmiş olabilir.
Her iki politikada da boşaltma kararı sabit bir eşikten değil, ölçülen bir göstergeden beslenmelidir: kuyrukta geçirilen süre, işçilerin doluluğu, dış bağımlılığın gecikmesi. Sabit eşik, yükün biçimi değiştiğinde ya erken ya geç boşaltır.
Yeniden Deneme Fırtınası
Boşaltmanın kendisi yeni bir tehlike üretir. Aynı anda reddedilen altı istemci, Retry-After
süresi dolduğunda aynı anda geri döner. Bu, sunucuyu doygun tutan bir döngüye yol açar
ve ona yeniden deneme fırtınası (retry storm) denir.
Panzehiri bu kursta zaten kuruldu: mesajlaşma konusunda tanıtılan üstel geri çekilme ve
sarsıntı. Retry-After değeri bir taban süredir; istemci bu tabana rastgele bir sarsıntı
ekler ve her başarısız denemede süreyi büyütür. Ayrıca istemci tarafında bir üst deneme
sınırı bulunmalıdır, yoksa reddedilen iş sonsuza dek geri gelir.
Sunucu tarafında da bir önlem vardır: reddedilen isteğin kütüğe yazılması, kabul edilenden daha ucuz olmalıdır. Doygunlukta her reddi ayrıntılı kütüklemek, boşaltmanın kazandırdığı kapasiteyi kütükleme işine harcar.
Özet
- Yük boşaltma, kapasiteyi aşan işi kabul etmeyip geri çevirmektir; kararı veren kabul denetimi iş başlamadan önce çalışır.
- Eşik saniyedeki istek sayısına değil eşzamanlılığa konur; gereği sistemdeki iş sayısını sabitlemek gecikmeyi sabitler.
- Ölçüm, denetimsiz sunucuda gecikmenin 3 dilimden 15 dilime doğru yayıldığını, denetimli sunucuda ise kabul edilenlerin 3–6 dilimde kaldığını ve fazlanın sıfırıncı dilimde reddedildiğini gösterdi.
- Kabul denetimi belirsiz bir gecikmeyi kesin bir redde çevirir; 503 ile
Retry-Afteristemciye ne yapacağını söyler, 500 ise yeniden denenmemesi gereken bir hatayı bildirir. - Boşaltma yeniden deneme fırtınası doğurur; panzehiri istemci tarafında üstel geri çekilme ile sarsıntı, sunucu tarafında ise reddin ucuz tutulmasıdır.
Sonraki Adım
Buraya kadar kurulan her şey işin sunucu tarafındaki dayanıklılığıyla ilgiliydi: iş bir işçiye düşer, zamanında tetiklenir, ilerlemesi kaydedilir, kuyruk taşmaz, aşırı yükte denetimli olarak reddedilir. Bu zincirin bir ucu hâlâ açık. İşi başlatan istemci, işin kabul edildiğini öğrendi ama sonucun ne zaman hazır olduğunu bilmiyor. Uzun süren raporun ilerleme yüzdesi, rezervasyon sırasındaki konumun değişmesi, şube stok sayacının güncellenmesi — bunların hepsi sunucu tarafında hazır, istemci tarafında görünmez. Sonraki konu bu bilginin istemciye nasıl ulaştığını ele alır ve en yalın yoldan, yani istemcinin tekrar tekrar sormasından başlar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.