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çık — gonder 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
iptalyolu 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
iptalyolunun 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.