Ders 15 / 19
Bölme Kalıbı
Kaynak havuzunun kodda nereye yerleştiğinin ölçülmesi: tek paylaşılan havuz ile bağımlılık başına havuzun aynı toplam yuvayla karşılaştırılması, havuzu hiç kullanmayan çağrı yerinin sınırı aşması ve yanlış boyutlandırmanın eş zamanlılık ile süre cinsinden davranışı.
İçindekiler
Bir önceki ders devre kesicinin durumunu koda yerleştirdi ve şunu gösterdi: koruma ancak yeterince başarısızlık biriktikten sonra başlıyor, seyrek çağrılan yollarda hiç başlamıyor. Kesici bir bağımlılığın bozuk olduğunu anlar; o bağımlılığı bekleyen işin çağıranın kaynağını tükettiğini anlamaz. Üç çağrı yeri de aynı süreçte, aynı yuva havuzundan çalışıyor.
Bölme kalıbı ve sistem üzerindeki etkisi M19/K05 Dayanıklılık ve Güvenilirlik kursunda kuruldu ve orada ölçüldü. Burada tekrarlanmaz. Bu dersin sorusu şudur: havuz kodda nerede tanımlı, onu kullanmak kaç çağrı yerini değiştirir ve hangi yollar sınırın dışında kalır.
Düzenek
İki bağımlılık: bildirim 300 ms sürer, katalog 5 ms. Her ikisine de kırk eş zamanlı çağrı yapılır. Her bağımlılık kendi en çok eş zamanlı isteğini kaydeder — havuz sınırının bağımlılık tarafından görünen karşılığı budur. DY8 — iki yerleşimde toplam yuva sayısı eşittir: tek havuzda sekiz, bağımlılık başına havuzda dört artı dört. Kıyas ancak toplam sabitken yerleşimi ölçer.
// servisler.mjs — 8402 bildirim (yavas), 8403 katalog (hizli), 8401 odunc: havuzlar burada tanimlanir. import { createServer, request, Agent } from "node:http"; const N = 40; // her akista 40 es zamanli cagri const s = { bildirim: { acik: 0, enCok: 0 }, katalog: { acik: 0, enCok: 0 } }; const servis = (ad, gecikme, port) => createServer((i, y) => { const d = s[ad]; d.acik += 1; d.enCok = Math.max(d.enCok, d.acik); setTimeout(() => { d.acik -= 1; y.end("{}"); }, gecikme); }).listen(port); servis("bildirim", 300, 8402); servis("katalog", 5, 8403); // >>> havuz tanimi: iki yerlesim, ayni toplam yuva const havuzlar = (yerlesim, b, k) => { if (yerlesim === "tek") { const a = new Agent({ maxTotalSockets: b + k }); return () => a; } const t = { 8402: new Agent({ maxTotalSockets: b }), 8403: new Agent({ maxTotalSockets: k }) }; return (port) => t[port]; }; // <<< const cagir = (port, yol, havuz) => new Promise((coz) => { const r = request({ port, path: yol, agent: havuz }, (y) => { y.resume(); y.on("end", coz); }); r.on("error", coz); r.end(); }); createServer(async (i, y) => { // 8401 odunc: bir kosumu bastan sona yurutur const q = new URL(i.url, "http://x").searchParams; const al = havuzlar(q.get("yerlesim"), Number(q.get("b")), Number(q.get("k"))); const atla = Number(q.get("atla")); // havuzu hic kullanmayan cagri yeri sayisi for (const d of Object.values(s)) { d.acik = 0; d.enCok = 0; } const t0 = Date.now(); let biten = 0, katalogOnce = null; const yavas = Array.from({ length: N }, (_, j) => cagir(8402, "/gonder", j < atla ? false : al(8402))); const hizli = Array.from({ length: N }, () => cagir(8403, "/kitap", al(8403)).then(() => { biten += 1; })); Promise.race(yavas).then(() => { katalogOnce = biten; }); await Promise.all([...yavas, ...hizli]); y.end(JSON.stringify({ n: N, katalogOnce, bildirim: s.bildirim.enCok, katalog: s.katalog.enCok, sure: Math.round((Date.now() - t0) / 500) * 500 })); }).listen(8401);
katalogOnce bu dersin ana ölçüsüdür: ilk bildirim çağrısı tamamlanana kadar kaç katalog çağrısı
bitebildi. Katalog beş milisaniyede yanıt verdiği için, kaynağı olan bir akış kırk çağrısını
bildirim daha ilk yanıtını vermeden bitirmiş olmalıdır. Bitiremiyorsa yuva bulamamış demektir.
atla parametresi havuzu hiç kullanmayan çağrı yerini üretir: agent: false ile yapılan istek
kendi yuvasını açar ve havuz tablosuna hiç bakmaz. Kaynakta bu bir eksiklik gibi görünmez; yalnızca
başka bir seçenektir.
// olc.mjs — havuzun nerede tanimlandigi, atlanan yol ve yanlis boyutlandirma. import { readFileSync } from "node:fs"; import { request } from "node:http"; const cagir = (yol) => new Promise((c) => request({ port: 8401, path: yol, agent: false }, (r) => { let g = ""; r.on("data", (p) => (g += p)); r.on("end", () => c(JSON.parse(g))); }).end()); const yaz = (a, ...r) => console.log(String(a).padEnd(30) + r.map((x) => String(x).padStart(18)).join("")); console.log("her akista 40 es zamanli cagri: bildirim 300 ms, katalog 5 ms; toplam yuva her yerlesimde 8"); console.log("sure sutunu bu kosumun olcumudur, 500 ms'ye yuvarlandi; beklenen deger ceil(40/yuva) x 300 ms"); yaz("yerlesim", "katalog once biten", "bildirim eszaman", "katalog eszaman", "sure ms"); for (const [ad, y] of [["tek paylasilan havuz", "yerlesim=tek&b=4&k=4&atla=0"], ["bagimlilik basina havuz", "yerlesim=ayri&b=4&k=4&atla=0"], ["bagimlilik basina + 8 atlanan", "yerlesim=ayri&b=4&k=4&atla=8"]]) { const r = await cagir(`/kos?${y}`); yaz(ad, `${r.katalogOnce} / ${r.n}`, r.bildirim, r.katalog, r.sure); } console.log("\nyanlis boyutlandirma: bagimlilik basina havuzda yuva sayilari"); yaz("bildirim yuva / katalog yuva", "bildirim eszaman", "katalog eszaman", "sure ms"); for (const [b, k] of [[1, 4], [4, 4], [40, 4], [4, 1]]) { const r = await cagir(`/kos?yerlesim=ayri&b=${b}&k=${k}&atla=0`); yaz(`${b} / ${k}`, r.bildirim, r.katalog, r.sure); } const kaynak = readFileSync("servisler.mjs", "utf8"); const blok = kaynak.match(/\/\/ >>> havuz tanimi[^\n]*\n([\s\S]*?)\/\/ <<</)[1].trim().split("\n").length; const cy = (kaynak.match(/cagir\(\d+, "/g) || []).length; console.log(`\nhavuz tanimi ${blok} satir, tek yerde; dis cagri yeri ${cy} ve hicbiri degismedi — ` + `havuzu cagri yerine tasiyan sey ${(kaynak.match(/al\(\d+\)/g) || []).length} adet al(port) ifadesidir.`); process.exit(0);
node servisler.mjs > /dev/null 2>&1 & SP=$! sleep 1; node olc.mjs; kill $SP
her akista 40 es zamanli cagri: bildirim 300 ms, katalog 5 ms; toplam yuva her yerlesimde 8 sure sutunu bu kosumun olcumudur, 500 ms'ye yuvarlandi; beklenen deger ceil(40/yuva) x 300 ms yerlesim katalog once biten bildirim eszaman katalog eszaman sure ms tek paylasilan havuz 0 / 40 8 1 2000 bagimlilik basina havuz 40 / 40 4 4 3000 bagimlilik basina + 8 atlanan 40 / 40 12 4 2500 yanlis boyutlandirma: bagimlilik basina havuzda yuva sayilari bildirim yuva / katalog yuva bildirim eszaman katalog eszaman sure ms 1 / 4 1 4 12000 4 / 4 4 4 3000 40 / 4 40 4 500 4 / 1 4 1 3000 havuz tanimi 5 satir, tek yerde; dis cagri yeri 2 ve hicbiri degismedi — havuzu cagri yerine tasiyan sey 2 adet al(port) ifadesidir.
Havuz Kodda Nerede Duruyor
Son satır yerleşimin kod bedelini veriyor: havuz tanımı beş satır ve tek yerde. İki dış çağrı
yerinin ikisi de değişmedi; havuzu çağrı yerine taşıyan şey al(port) biçiminde iki ifadedir. Bu,
bölme kalıbının önceki üç kalıptan ayrıldığı noktadır. Zaman aşımı bir davranış ekler, yeniden
deneme bir döngü ekler, kesici bir durum tutar — hepsi çağrı yerinin yaptığı işi değiştirir.
Havuz ise yalnızca çağrının hangi kaynağı kullanacağını söyler; çağrı yerinin kodu aynı kalır.
Bunun sonucu şudur: havuz yerleşimi kaynakta neredeyse görünmezdir. Bir çağrı yerinin doğru
havuzu kullanıp kullanmadığı, o çağrı yerinin kendisine bakılarak anlaşılmaz; hangi ifadenin
geçirildiğine bakmak gerekir. Bir satırda al(8402) yerine false yazmak sözdizimsel olarak
kusursuzdur.
İki Yerleşim, Aynı Sekiz Yuva
İlk tablonun ilk iki satırı aynı sekiz yuvayı iki ayrı biçimde dağıtıyor.
Tek paylaşılan havuzda katalog once biten 0 / 40. İlk bildirim yanıtı gelene kadar tek bir
katalog çağrısı bile bitemedi. Nedeni bildirim eszaman sütununda: yavaş bağımlılık sekiz yuvanın
sekizini de tutuyor ve katalog akışı en fazla bir yuva bulabiliyor. Katalog bağımlılığında hiçbir
sorun yok, katalog beş milisaniyede yanıt veriyor; yine de kırk isteğin kırkı bekliyor. Havuz
paylaşıldığında bir bağımlılığın yavaşlığı, onunla hiç ilgisi olmayan akışın gecikmesine
dönüşüyor.
Bağımlılık başına havuzda aynı sütun 40 / 40. Katalog akışı kendi dört yuvasını buluyor ve kırk çağrısını bildirim daha ilk yanıtını vermeden bitiriyor. Bildirim akışının eş zamanlılığı sekizden dörde düşüyor ve toplam süre 2000 ms’den 3000 ms’ye çıkıyor — bölmenin bedeli budur ve tabloda doğrudan görünür. Kalıp bulaşmayı algılamaya hiç bağlamadan kesiyor: hiçbir eşik, hiçbir sayaç, hiçbir öğrenme süresi yok. Sınır baştan konmuştur.
Atlanan Yol
Üçüncü satır bu dersin en değerli ölçüsüdür. Havuz yapılandırması değişmedi — bildirim için hâlâ dört yuva yazıyor. Kırk çağrı yerinin sekizi havuzu kullanmıyor, kendi yuvasını açıyor. Sonuç: bildirim eş zamanlılığı 4 yerine 12.
Havuz sınırı dört diyor, bağımlılık on iki görüyor. Fazlalık tam olarak atlanan çağrı yeri sayısıdır ve yapılandırmanın hiçbir yerinde görünmez: yuva sayısını okuyan biri dört görür, bağımlılığı izleyen biri on iki. Bir önceki iki dersin kapsam sorunu burada en sinsi hâlindedir, çünkü havuz yerleşimi çağrı yerinin kodunu değiştirmediği için eksikliği de değiştirmez — atlanan çağrı yeri, sarılmış olanla neredeyse aynı görünür.
Komşu akışın hâlâ kurtulduğuna dikkat etmek gerekir: katalog once biten yine 40 / 40. Atlanan yol
bildirim havuzunun sınırını deliyor ama katalog havuzunu delmiyor. Bölme kalıbının koruması,
kaçağın kendi bölmesinde kalmasıdır; kaçak yine de o bölmenin sözünü geçersiz kılar.
Yanlış Boyutlandırma
İkinci tablo havuz boyutunun iki ucunu gösteriyor ve ikisi de kalıbı işlevsiz bırakıyor, ama farklı yönlerden.
Bir yuva bildirim akışını sıraya diziyor: eş zamanlılık 1, süre 12000 ms. Bağımlılıkta hiçbir sorun yokken kırk çağrı arka arkaya yapılıyor. Bölme kalıbı burada koruma değil, darboğaz üretiyor; komşu akış kurtuluyor ama korunan akışın kendisi sistemin en yavaş parçası oluyor.
Kırk yuva ters uçtur: eş zamanlılık 40, süre 500 ms. Havuz kodda vardır, tanımlıdır, adı geçmektedir ve hiçbir şeyi bağlamamaktadır. Bu, yapılandırmanın en tehlikeli hâlidir — kalıbın olmadığı durumdan ayırt edilemez, ama kaynağa bakan biri bölmenin kurulduğunu görür.
Son satır sınırın komşu tarafta da geçerli olduğunu söylüyor: katalog havuzu bire indirildiğinde katalog eş zamanlılığı 1 oluyor. Her havuz kendi akışının tavanıdır; bölme yalnız yavaş bağımlılığı sınırlamaz, hızlı olanı da sınırlar ve yanlış boyutlandırma iki yönde de aynı biçimde bedelini verir.
Sınanabilirlik
// test/havuz.test.mjs — havuz siniri gercekten baglayici mi. import { test } from "node:test"; import assert from "node:assert/strict"; import { request } from "node:http"; const kos = (yol) => new Promise((c) => request({ port: 8401, path: yol, agent: false }, (r) => { let g = ""; r.on("data", (p) => (g += p)); r.on("end", () => c(JSON.parse(g))); }).end()); test("bagimlilik basina havuz komsu akisi kurtarir", async () => { assert.equal((await kos("/kos?yerlesim=tek&b=4&k=4&atla=0")).katalogOnce, 0); assert.equal((await kos("/kos?yerlesim=ayri&b=4&k=4&atla=0")).katalogOnce, 40); }); test("atlanan cagri yeri havuz sinirini asar", async () => { assert.equal((await kos("/kos?yerlesim=ayri&b=4&k=4&atla=0")).bildirim, 4); assert.equal((await kos("/kos?yerlesim=ayri&b=4&k=4&atla=8")).bildirim, 12); // 4 + 8 }); test("havuz siniri yalniz kendi bagimliligini baglar", async () => { for (const b of [1, 4, 40]) assert.equal((await kos(`/kos?yerlesim=ayri&b=${b}&k=4&atla=0`)).katalog, 4); });
node servisler.mjs > /dev/null 2>&1 & SP=$! sleep 1 node --test --test-force-exit --test-reporter=tap test/havuz.test.mjs | grep -E "^(ok|not ok|# (tests|pass|fail) )" kill $SP
ok 1 - bagimlilik basina havuz komsu akisi kurtarir ok 2 - atlanan cagri yeri havuz sinirini asar ok 3 - havuz siniri yalniz kendi bagimliligini baglar # tests 3 # pass 3 # fail 0
Bölme kalıbının sınanabilirliği önceki üç kalıptan farklıdır: kalıbın davranışı çağıranın kodunda değil, bağımlılığın gördüğü eş zamanlılıkta ortaya çıkar. Bir zaman aşımı ya da kesici testi çağıranın aldığı hataya bakarak yazılabilir; havuz testi ancak karşı tarafın kaç bağlantı gördüğünü sayarak yazılabilir. İkinci test bunu doğrudan kullanıyor: havuz dört diyor, iddia on iki üzerine kuruluyor ve atlanan çağrı yeri böylece bir sayı hâline geliyor. Üçüncü test sınırın kapsamını sabitliyor — bildirim havuzu ne olursa olsun katalog dörttedir.
Özet
- Bölme kalıbının etkisi M19/K05 Dayanıklılık ve Güvenilirlik kursunda ölçüldü; burada ölçülen
havuzun kodda nerede tanımlandığıdır. Havuz tanımı beş satır ve tek yerdedir; iki dış çağrı yeri de
değişmez, havuzu taşıyan şey çağrıya geçirilen
al(port)ifadesidir. - Aynı sekiz yuva tek havuzda toplandığında, yavaş bağımlılık yuvaların hepsini tutar ve ilk bildirim yanıtına kadar biten katalog çağrısı 0 / 40 olur; katalog eş zamanlılığı 1’e iner.
- Bağımlılık başına havuzda aynı sayı 40 / 40’tır. Bedeli tablodadır: bildirim eş zamanlılığı 8’den 4’e, süre 2000 ms’den 3000 ms’ye. Koruma bir eşiğe ya da öğrenmeye bağlı değildir, baştan konmuştur.
- Havuzu kullanmayan sekiz çağrı yeri, yapılandırma dört yuva derken bağımlılığa on iki eş zamanlı bağlantı ulaştırır. Fazlalık tam olarak atlanan çağrı yeri sayısıdır ve yapılandırmada görünmez.
- Havuz boyutunun iki ucu da kalıbı işlevsiz bırakır: bir yuva eş zamanlılığı 1’e indirip süreyi 12000 ms’ye çıkarır, kırk yuva ise sınırı hiç bağlamaz (eş zamanlılık 40, süre 500 ms) ve kaynakta bölme kurulmuş gibi görünür.
- Her havuz kendi akışının tavanıdır: katalog havuzu 1’e indirildiğinde katalog eş zamanlılığı da 1 olur. Havuz testi ancak bağımlılığın gördüğü eş zamanlılık sayılarak yazılabilir.
Sonraki Adım
Dört kalıp da aynı şeyi yaptı: bir sınır koydu ve o sınırın ötesindeki isteği reddetti. Zaman aşımı bekleyeni bıraktı, yeniden deneme çoğalttı ve vazgeçti, kesici çağrıyı hiç yapmadı, bölme yuva vermedi. Hepsinde geriye aynı şey kaldı — karşılanmayan bir istek. Reddedilen o isteğin nereye gideceği bu dört kalıbın hiçbirinin sorusu değildi; hepsi arızayı çevreledi, hiçbiri onu karşılamadı. Bir sonraki adım bu boşluğu doldurur: sınıra çarpan isteğin tam yanıt yerine eksik ama kullanılabilir bir yanıt alması, yani sistemin bozulurken bile bir şey döndürmesi.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.