İçeriğe geç
academia.sh

Ders 14 / 19

Devre Kesici

Kesicinin kodda nereye yerleştiğinin ölçülmesi: durumun bağımlılık başına mı çağrı yeri başına mı tutulduğu, iki yerleşimde açılmaya kadar bağımlılığa giden çağrı, aynı eşiğin iki yerleşimde iki ayrı anlam taşıması ve seyrek çağrılan bir yolun kesicisinin hiç açılmaması.

İçindekiler

Bir önceki ders yeniden denemenin kalıcı arızada durumu ağırlaştırdığını ölçtü: 27 çağrının 27’si başarısız oldu ve düşmüş bir bağımlılığın üzerine yirmi yedi kat yük bindi. Eksik olan bir sayı değil, bir karardır — bağımlılığın düştüğünü anlayıp çağrıyı hiç yapmamak.

Devre kesicinin ne olduğu, kapalı, açık ve yarı açık durumları M14/K06 Uygulama Mimarisi kursunda kuruldu; kalıbın sistem üzerindeki etkisi, arızasız günün bedeli ve yanlış açılmanın maliyeti M19/K05 Dayanıklılık ve Güvenilirlik kursunda ölçüldü. İkisi de burada tekrarlanmaz. Bu dersin sorusu şudur: kesicinin durumu nerede tutuluyor. Bir kesici bir sayı ve bir zaman damgasından ibarettir; o sayının nereye konduğu kalıbın ne yaptığını baştan belirler.

İki yerleşim vardır ve aralarındaki fark koddaki tek bir simgedir: durum bağımlılık adıyla mı saklanıyor, yoksa çağrı yeri adıyla mı. Ödünç servisi aynı bildirim bağımlılığına üç ayrı çağrı yerinden gidiyor; birinci yerleşimde üçü tek bir durumu paylaşır, ikincisinde her biri kendi durumunu tutar.

Düzenek

Bildirim bağımlılığı kalıcı olarak arızalıdır ve her çağrıya 503 döner. Katalog sağlıklıdır ve kesiciden geçmesine rağmen hiç açılmaz — kesicinin kapsamının nerede bittiğini göstermek için oradadır. DY7 — kesik süresi koşumdan uzundur, böylece açılan kesici kapanmaz ve yarı açık geçişi ölçüme karışmaz.

// servisler.mjs — 8302 bildirim (surekli arizali), 8303 katalog (saglikli), 8301 odunc.
// Devre kesici iki yerlesimde de ayni koddur; degisen tek sey durumun hangi anahtarla tutuldugudur.
import { createServer, request } from "node:http";
const s = { katalog: 0, gonder: 0, hatirlat: 0, iptal: 0 };
createServer((i, y) => {                            // 8302 bildirim: her cagride 503
  if (i.url === "/sayac") return y.end(JSON.stringify(s));
  if (i.url === "/sifirla") { for (const k in s) s[k] = 0; return y.end("{}"); }
  s[i.url.slice(1)] += 1; y.statusCode = 503; y.end("{}");
}).listen(8302);
createServer((i, y) => { s.katalog += 1; y.end("{}"); }).listen(8303);

const cagir = (port, yol) => new Promise((coz) => {
  const r = request({ port, path: yol, agent: false }, (y) => { y.resume(); y.on("end", () => coz(y.statusCode)); });
  r.on("error", () => coz(0)); r.end();
});
let ESIK = 5, durum = new Map();                    // DY7: kesik suresi kosumdan uzun; yari acik gecis M14/K06'da kuruldu
const KESIK = 3_600_000;
const kesici = (a) => { if (!durum.has(a)) durum.set(a, { ardisik: 0, acikBitis: 0 }); return durum.get(a); };
const istek = async (port, yol, anahtar) => {       // kesici burada; anahtar yerlesimi belirler
  const d = kesici(anahtar);
  if (Date.now() < d.acikBitis) return 0;           // acik: cagri hic yapilmaz
  const k = await cagir(port, yol);
  if (k >= 500) { d.ardisik += 1; if (d.ardisik >= ESIK) d.acikBitis = Date.now() + KESIK; } else d.ardisik = 0;
  return k;
};
createServer(async (i, y) => {                      // 8301 odunc: uc cagri yeri ayni bagimliliga gider
  const q = new URL(i.url, "http://x").searchParams;
  if (i.url.startsWith("/kur")) { ESIK = Number(q.get("esik")); durum = new Map(); return y.end("{}"); }
  if (i.url.startsWith("/durum"))
    return y.end(JSON.stringify([...durum].map(([a, d]) => [a, Date.now() < d.acikBitis])));
  const n = Number(q.get("n")), yer = q.get("yer");
  const an = (bagimlilik, cagriYeri) => (yer === "bagimlilik" ? bagimlilik : cagriYeri);
  await istek(8303, "/kitap", an("katalog", "kitap"));
  await istek(8302, "/gonder", an("bildirim", "gonder"));
  await istek(8302, "/hatirlat", an("bildirim", "hatirlat"));
  if (n % 20 === 0) await istek(8302, "/iptal", an("bildirim", "iptal"));
  y.end("{}");
}).listen(8301);

an işlevi bu dersin tamamıdır. Kesicinin kodu değişmiyor, eşik değişmiyor, çağrı yerleri değişmiyor; değişen tek şey istek çağrısına geçirilen anahtarın bağımlılığı mı yoksa çağrı yerini mi adlandırdığı. Üç çağrı yerinden ikisi her istekte, iptal yolu her yirmi istekte bir çağrılır; gerçek bir kod tabanında sık ve seyrek yollar hep birlikte bulunur.

// olc.mjs — kesicinin durumu nerede tutuluyor: bagimlilik basina mi, cagri yeri basina mi.
import { request } from "node:http";
const N = 60;                                       // 60 kullanici istegi; iptal yolu her 20'de bir cagrilir
const cagir = (port, yol) => new Promise((coz) => {
  const r = request({ port, path: yol, agent: false }, (y) => {
    let g = ""; y.on("data", (p) => (g += p)); y.on("end", () => coz(g));
  });
  r.on("error", () => coz("")); r.end();
});
const yaz = (a, ...r) => console.log(String(a).padEnd(18) + r.map((x) => String(x).padStart(16)).join(""));

const kos = async (esik, yer) => {
  await cagir(8302, "/sifirla");
  await cagir(8301, `/kur?esik=${esik}`);
  for (let n = 1; n <= N; n += 1) await cagir(8301, `/istek?n=${n}&yer=${yer}`);
  const s = JSON.parse(await cagir(8302, "/sayac"));
  const d = JSON.parse(await cagir(8301, "/durum"));
  return { ...s, nesne: d.length, acik: d.filter(([, a]) => a).length };
};

console.log(`${N} kullanici istegi; gonder ve hatirlat her istekte, iptal her 20 istekte bir cagrilir`);
console.log("bildirim bagimliligi kalici arizalidir: her cagriya 503 doner");
yaz("esik / yerlesim", "durum nesnesi", "acik nesne", "bildirim cagri", "gonder", "hatirlat", "iptal", "katalog");
const t = {};
for (const esik of [3, 5, 10])
  for (const [yer, ad] of [["bagimlilik", "bagimlilik"], ["cagriYeri", "cagri yeri"]]) {
    const r = await kos(esik, yer);
    t[`${esik}${yer}`] = r;
    yaz(`${esik} / ${ad}`, r.nesne, r.acik, r.gonder + r.hatirlat + r.iptal, r.gonder, r.hatirlat, r.iptal, r.katalog);
  }
const top = (r) => r.gonder + r.hatirlat + r.iptal;
console.log(`\nyapilandirma yuzeyi: 2 sayi (esik, kesik suresi) ve 1 karar (durumun anahtari).`);
console.log(`ayni esik, iki yerlesimde iki ayri anlam: esik 5 icin bagimliliga giden cagri ` +
  `${top(t["5bagimlilik"])} -> ${top(t["5cagriYeri"])}; esik 10 icin ${top(t["10bagimlilik"])} -> ${top(t["10cagriYeri"])}.`);
console.log(`cagri yeri basina yerlesimde bagimlilik duzeyindeki etkin esik = esik x acilabilen cagri yeri; ` +
  `esik 10'da iptal yolu ${t["10cagriYeri"].iptal} cagriyla esige hic ulasmadi ve kesicisi kapali kaldi.`);
process.exit(0);
node servisler.mjs > /dev/null 2>&1 & SP=$!
sleep 1; node olc.mjs; kill $SP
60 kullanici istegi; gonder ve hatirlat her istekte, iptal her 20 istekte bir cagrilir
bildirim bagimliligi kalici arizalidir: her cagriya 503 doner
esik / yerlesim      durum nesnesi      acik nesne  bildirim cagri          gonder        hatirlat           iptal         katalog
3 / bagimlilik                   2               1               3               2               1               0              60
3 / cagri yeri                   4               3               9               3               3               3              60
5 / bagimlilik                   2               1               5               3               2               0              60
5 / cagri yeri                   4               2              13               5               5               3              60
10 / bagimlilik                  2               1              10               5               5               0              60
10 / cagri yeri                  4               2              23              10              10               3              60

yapilandirma yuzeyi: 2 sayi (esik, kesik suresi) ve 1 karar (durumun anahtari).
ayni esik, iki yerlesimde iki ayri anlam: esik 5 icin bagimliliga giden cagri 5 -> 13; esik 10 icin 10 -> 23.
cagri yeri basina yerlesimde bagimlilik duzeyindeki etkin esik = esik x acilabilen cagri yeri; esik 10'da iptal yolu 3 cagriyla esige hic ulasmadi ve kesicisi kapali kaldi.

Durum Nesnesi Sütunu

En sol sayısal sütun yerleşimin doğrudan tanımıdır: bağımlılık başına yerleşimde 2 durum nesnesi, çağrı yeri başına yerleşimde 4. İkisi de aynı iki bağımlılığa gidiyor; fark, birinde durumun bağımlılık sayısınca, ötekinde çağrı yeri sayısınca çoğalmasıdır. Bu sayı kod tabanıyla birlikte büyür: çağrı yeri eklendiğinde bağımlılık başına yerleşimde hiçbir şey değişmez, çağrı yeri başına yerleşimde bir durum nesnesi daha doğar ve o nesne sıfırdan öğrenmeye başlar.

Öğrenmenin paylaşılıp paylaşılmadığı bildirim çağrı sütununda görünüyor. Eşik 5’te bağımlılık başına yerleşim bozuk bağımlılığa 5 çağrı yapıyor: gonder üç kez, hatirlat iki kez başarısız oluyor, beşinci başarısızlıkta ortak kesici açılıyor ve iptal yolu bağımlılığa hiç gitmiyor — sütunda 0. Bir çağrı yerinin öğrendiğini öteki de biliyor.

Çağrı yeri başına yerleşimde aynı eşikle 13 çağrı gidiyor: gonder 5, hatirlat 5, iptal 3. Her çağrı yeri aynı gerçeği baştan öğreniyor. Bağımlılık zaten düşmüşken üçüncü çağrı yeri hiçbir şey bilmiyormuş gibi davranıyor.

Aynı Eşiğin İki Ayrı Anlamı

Eşik satırları birlikte okunduğunda yapılandırma yüzeyinin asıl sorunu çıkıyor. Eşik 3, 5 ve 10 için bağımlılığa giden çağrı bağımlılık başına yerleşimde 3, 5 ve 10; çağrı yeri başına yerleşimde 9, 13 ve 23. Eşik 5 yazan kişi bir yerleşimde “bağımlılığa en çok beş başarısız çağrı” demiş oluyor, ötekinde “her çağrı yeri için beş, toplamda on beşe kadar”. Sayı aynıdır, anlamı değildir ve kodda hangi anlamın geçerli olduğunu söyleyen tek şey bir anahtarın adıdır.

Bağımlılık düzeyindeki etkin eşik, çağrı yeri başına yerleşimde eşik ile açılabilen çağrı yeri sayısının çarpımıdır. Bu çarpan yapılandırmada hiçbir yerde yazmaz; kaynaktaki çağrı yeri sayısıdır ve her yeni çağrı yeriyle sessizce büyür.

Açılmayan Kesici

Son satır bu dersin en sert ölçüsüdür. Eşik 10’da çağrı yeri başına yerleşimde 4 durum nesnesinin yalnız 2’si açıkgonder ve hatirlat açıldı, iptal açılmadı. Nedeni aritmetiktir: iptal yolu 60 istekte yalnız 3 kez çağrılıyor ve 3 başarısızlık 10’luk eşiğe hiç ulaşmıyor.

Sonucu şudur: bağımlılık bozuk olduğu bilindiği hâlde o çağrı yeri her çağrıldığında yine dışarı gidiyor. Kesici oradadır, kodu doğrudur, ayarı ötekilerle aynıdır ve hiçbir zaman işe yaramayacaktır. Seyrek çağrılan bir çağrı yeri kendi başına bir eşiği hiçbir zaman dolduramaz — korumasız kalan tam da en az sınanmış yoldur. Bağımlılık başına yerleşimde aynı yol sıfır çağrı yapıyor, çünkü sık çağrılan komşularının öğrendiğini devralıyor.

katalog sütunu her satırda 60’tır. Kesici sağlıklı bağımlılığa hiç dokunmuyor ve iki yerleşimde de aynı davranıyor; durum paylaşımı bağımlılıklar arasında değil, aynı bağımlılığın çağrı yerleri arasında bir sorudur.

Sınanabilirlik

// test/kesici.test.mjs — kesicinin durumu nerede tutuluyorsa iddia oraya baglanir.
import { test } from "node:test";
import assert from "node:assert/strict";
import { request } from "node:http";
const cagir = (port, yol) => new Promise((coz) => {
  const r = request({ port, path: yol, agent: false }, (y) => {
    let g = ""; y.on("data", (p) => (g += p)); y.on("end", () => coz(g));
  });
  r.on("error", () => coz("")); r.end();
});
const kos = async (esik, yer, n = 60) => {
  await cagir(8302, "/sifirla");
  await cagir(8301, `/kur?esik=${esik}`);
  for (let i = 1; i <= n; i += 1) await cagir(8301, `/istek?n=${i}&yer=${yer}`);
  return { s: JSON.parse(await cagir(8302, "/sayac")), d: JSON.parse(await cagir(8301, "/durum")) };
};

test("bagimlilik basina: bir cagri yerinin ogrendigini oteki de bilir", async () => {
  const { s, d } = await kos(5, "bagimlilik");
  assert.equal(s.iptal, 0);                         // iptal yolu bagimliliga hic gitmedi
  assert.equal(d.length, 2);                        // tek iddia: iki bagimlilik, iki durum nesnesi
});
test("cagri yeri basina: seyrek cagri yeri esige hic ulasmaz", async () => {
  const { s, d } = await kos(10, "cagriYeri");
  assert.equal(s.iptal, 3);                         // bagimlilik bozukken uc kez daha cagrildi
  assert.deepEqual(d.find(([a]) => a === "iptal"), ["iptal", false]);
});
test("kesicinin kapsami bagimlilikla sinirlidir", async () => {
  for (const yer of ["bagimlilik", "cagriYeri"]) assert.equal((await kos(5, yer)).s.katalog, 60);
});
node servisler.mjs > /dev/null 2>&1 & SP=$!
sleep 1
node --test --test-force-exit --test-reporter=tap test/kesici.test.mjs |
  grep -E "^(ok|not ok|# (tests|pass|fail) )"
kill $SP
ok 1 - bagimlilik basina: bir cagri yerinin ogrendigini oteki de bilir
ok 2 - cagri yeri basina: seyrek cagri yeri esige hic ulasmaz
ok 3 - kesicinin kapsami bagimlilikla sinirlidir
# tests 3
# pass 3
# fail 0

Kesicinin testte tetiklenmesi kolaydır — bağımlılığı bozuk tutmak ve eşik kadar çağrı yapmak yeter. Zor olan hangi durumun sınandığıdır. Bağımlılık başına yerleşimde tek bir nesne vardır ve tek bir iddia bütün çağrı yerlerini kapsar; birinci testin d.length üzerine kurduğu iddia budur. Çağrı yeri başına yerleşimde her çağrı yerinin kendi durumu ayrı ayrı sınanmalıdır ve ikinci testin gösterdiği gibi, sınanmayan çağrı yeri sessizce korumasız kalır. Yerleşim yalnız çalışma zamanı davranışını değil, testin kaç iddia yazması gerektiğini de belirliyor: bir bağımlılık için bir, ya da çağrı yeri sayısınca.

Özet

  • Devre kesicinin durumları M14/K06 Uygulama Mimarisi kursunda, sistem üzerindeki etkisi M19/K05 Dayanıklılık ve Güvenilirlik kursunda kuruldu; burada ölçülen durumun kodda nerede tutulduğudur. İki yerleşim arasındaki fark tek bir anahtarın adıdır.
  • Durum nesnesi sayısı yerleşimin doğrudan sonucudur: bağımlılık başına 2, çağrı yeri başına 4. Çağrı yeri eklendiğinde ikincisinde bir durum nesnesi daha doğar ve sıfırdan öğrenmeye başlar.
  • Aynı eşikte bozuk bağımlılığa giden çağrı 5’e karşı 13’tür; bağımlılık başına yerleşimde bir çağrı yerinin öğrendiğini öteki devralır ve iptal yolu bağımlılığa hiç gitmez.
  • Aynı eşik iki yerleşimde iki ayrı anlam taşır: 3, 5, 10 eşikleri bağımlılıkta 3, 5, 10 çağrıya karşılık gelirken çağrı yeri başına yerleşimde 9, 13, 23 çağrıya karşılık gelir. Bağımlılık düzeyindeki etkin eşik, eşik ile çağrı yeri sayısının çarpımıdır ve bu çarpan hiçbir yapılandırmada yazmaz.
  • Seyrek çağrılan yol kendi eşiğini dolduramaz: eşik 10’da iptal yolunun kesicisi 3 çağrıyla kapalı kaldı ve bağımlılık bozuk olduğu bilindiği hâlde her çağrıda dışarı gitmeyi sürdürdü.
  • Kesici sağlıklı bağımlılığa dokunmaz (katalog her satırda 60); paylaşım sorusu bağımlılıklar arasında değil, aynı bağımlılığın çağrı yerleri arasındadır. Testte bağımlılık başına yerleşim tek iddiayla kapanır, çağrı yeri başına yerleşim çağrı yeri sayısınca iddia ister.

Sonraki Adım

Kesici bir sınır çizdi ama sınırın malzemesi bilgidir: koruma ancak yeterince başarısızlık biriktikten sonra başlıyor ve seyrek yollarda hiç başlamıyor. Dahası kesici bir bağımlılığın bozuk olduğunu anlar, o bağımlılığı çağıran işin çağıranın kaynaklarını tükettiğini anlamaz — üç çağrı yeri de aynı süreçte, aynı havuzdan çalışıyor ve biri yavaşladığında ötekilere kalan pay küçülüyor. O hâlde bir soru daha vardır: bulaşmayı algılamaya hiç bağlamadan, baştan kesmek mümkün müdür. Sonraki ders kaynak havuzunu bölerek bunu dener ve havuzun kodda nerede tanımlandığını, bir bağımlılığı kendi havuzuna almanın kaç çağrı yerine dokunduğunu, havuz sınırının hangi yollardan atlandığını ve yanlış boyutlandırmanın hangi davranışı ürettiğini ölçer.

İ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