İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat