İçeriğe geç
academia.sh

Ders 12 / 19

Zaman Aşımları

Her dış çağrının süre sınırı: sınırın çağrı yerine mi istemci sarmalayıcısına mı konduğunun kaynakta ölçülmesi, sarmalanan ve atlanan çağrı yerinin sayılması, iç sınır dış sınırdan büyük olduğunda üretilen öksüz tamamlanma ve iki yerleşimin sınanabilirlik farkı.

İçindekiler

Bir önceki konu göstergeleri kurdu ve şunu bıraktı: bir sınırın aşıldığı artık görülüyor, ama sistem o sırada hâlâ aynı davranıyor — ölçmek bir şeyi değiştirmiyor. Değiştiren şey koda konan bir sınırdır ve bu dersin işi o sınırın nereye konduğunu ölçmektir.

Zaman aşımının etkisi M19/K05 Dayanıklılık ve Güvenilirlik kursunda ölçüldü ve burada tekrarlanmaz. Burada ölçülen şey başkadır: aynı sınır iki ayrı katmana konduğunda kaynakta kaç satır ve kaç yapılandırma değeri oluşur, kaç çağrı yeri kapsanır ve kaç çağrı yeri atlanır.

DY1 — ölçü birimi çağrı yeridir: kaynakta dış çağrı kuran her ifade bir çağrı yeridir. Ödünç servisi dört bağımlılığa yedi çağrı yerinden gidiyor.

Düzenek

Bağımlılıklar yerel süreçlerdir: katalog ve üyelik hızlı yanıtlar, ücretlendirme 500 ms sürer, bildirim ise bağlantıyı açık tutar ve yanıtı hiç yazmaz. Ödünç servisinin kendisi de ayaktadır; iç içe sınırlar bölümünde orta katman odur.

// servisler.mjs — bolunmus odunc sisteminin bagimliliklari ve odunc servisinin kendisi.
// DY2: bildirim baglantiyi acik tutar, yanit hic yazmaz. 8103/sayac tum sayaclari verir.
import { createServer, request } from "node:http";
const s = { acik: { bildirim: 0 }, toplam: { bildirim: 0 }, oksuz: 0 };  // oksuz: cagiran vazgectikten sonra biten is
for (const [port, ad, gecikme] of [[8101, "katalog", 5], [8102, "uyelik", 5], [8104, "ucretlendirme", 500]]) {
  s.acik[ad] = 0; s.toplam[ad] = 0;
  createServer((i, y) => { s.acik[ad] += 1; s.toplam[ad] += 1;
    setTimeout(() => { s.acik[ad] -= 1; y.end("{}"); }, gecikme);
  }).listen(port);
}
createServer((i, y) => {                            // 8103 bildirim + sayac ucu
  if (i.url === "/sayac") return y.end(JSON.stringify(s));
  if (i.url === "/sifirla") { s.oksuz = 0; for (const k in s.toplam) s.toplam[k] = 0; return y.end("{}"); }
  s.acik.bildirim += 1; s.toplam.bildirim += 1;
  i.on("close", () => { s.acik.bildirim -= 1; });   // yanit yazilmaz: cagri asili kalir
}).listen(8103);
createServer((i, y) => {                            // 8100 odunc: kendi ic sinirini uygular
  let vazgecti = false;
  i.on("close", () => { vazgecti = !y.writableEnded; });
  const r = request({ port: 8104, path: "/ucret", agent: false }, (c) => {
    c.resume(); c.on("end", () => { if (vazgecti) s.oksuz += 1; else y.end('{"ucret":1}'); });
  });
  r.setTimeout(Number(new URL(i.url, "http://x").searchParams.get("ic")), () => r.destroy(new Error("ic")));
  r.on("error", () => { if (!vazgecti) y.end('{"hata":"ic"}'); });
  r.end();
}).listen(8100);

Çağıran taraf üç dizinde üç kez yazılır. yok/ sınırsız hâldir; a/ sınırı çağrı yerine koyar, her isteğin kendi setTimeout satırı ve kendi sayı değeri vardır; b/ sınırı istemci sarmalayıcısına koyar. Üçü de aynı yedi çağrı yerinden üretilir, böylece fark yalnız yerleşimden gelir.

// kur.mjs — ayni yedi cagri yerini uc yerlesimde uretir: sinirsiz, cagri yerinde, sarmalayicida.
import { mkdirSync, writeFileSync } from "node:fs";
const ATLANAN = "bildirimYinele";                   // DY4: sarmalayiciya tasinmasi unutulan cagri yeri
const C = [["odunc", "kitapAra", 8101, "/ara", 200], ["odunc", "kitapDetay", 8101, "/kitap", 200],
  ["odunc", "uyeDogrula", 8102, "/uye", 200], ["odunc", "uyeCezasi", 8102, "/ceza", 200],
  ["odunc", "gecikmeUcreti", 8104, "/ucret", 800],
  ["bildirim", "bildirimGonder", 8103, "/gonder", 800],
  ["bildirim", ATLANAN, 8103, "/yinele", 800]];
const ham = ([, ad, port, yol, sinir], koy) => `export function ${ad}() {
  return new Promise((coz, at) => {
    const r = request({ port: ${port}, path: "${yol}" }, (y) => { y.resume(); y.on("end", coz); });${
  koy ? `\n    r.setTimeout(${sinir}, () => r.destroy(new Error("zaman asimi")));` : ""}
    r.on("error", at); r.end();
  });
}`;
const sarili = ([, ad, port, yol]) => `export const ${ad} = () => istek(${port}, "${yol}", "${yol.slice(1)}");`;
for (const [d, koy, sarmala] of [["yok", false, false], ["a", true, false], ["b", false, true]]) {
  mkdirSync(d, { recursive: true });
  for (const m of ["odunc", "bildirim"]) {
    const g = C.filter((x) => x[0] === m);
    const s = sarmala ? g.filter((x) => x[1] !== ATLANAN) : [], h = g.filter((x) => !s.includes(x));
    writeFileSync(`${d}/${m}.mjs`, `// ${d}/${m}.mjs\n` +
      (h.length ? 'import { request } from "node:http";\n' : "") +
      (s.length ? 'import { istek } from "../istek.mjs";\n' : "") +
      [...s.map(sarili), ...h.map((x) => ham(x, koy))].join("\n") + "\n");
  }
}

ATLANAN bu dersin çekirdeğidir: bildirim modülündeki yineleme yolu sarmalayıcıya taşınmadı. Sınır bir yere konduğunda, oraya uğramayan çağrı yeri kaynakta hiçbir eksiklik göstermez.

// istek.mjs — istemci sarmalayicisi: her dis cagri buradan gecer, sure siniri burada uygulanir.
import { request } from "node:http";
export const VARSAYILAN = { sure: 800 };            // DY3: tek varsayilan, cagri yerinde gecilebilir
export function istek(port, yol, ad, ayar = {}) {
  const sure = ayar.sure ?? VARSAYILAN.sure;
  return new Promise((coz, at) => {
    const r = request({ port, path: yol }, (y) => { y.resume(); y.on("end", () => coz(ad)); });
    r.setTimeout(sure, () => r.destroy(new Error(`zaman asimi ${sure} ms`)));
    r.on("error", at); r.end();
  });
}

Yerleşimin Kaynakta ve Koşumdaki Bedeli

Ölçüm betiği üç dizini tarar, üç sürümü canlı servislere karşı koşturup bağımlılıkta kaç isteğin açık kaldığını bağımlılığın kendi sayacından okur, sonra dış ve iç sınırı dört çiftle dener.

// olc.mjs — kapsanan/atlanan cagri yeri, asili cagri ve ic ice sinirlarin celismesi.
import { readdirSync, readFileSync } from "node:fs";
import { request } from "node:http";
const TUR = 4, PENCERE = 1500, N = 10, BAGIMLILIK = 500;   // ucretlendirmenin sabit gecikmesi 500 ms
const V = { yok: "sinir yok", a: "cagri yerinde", b: "istemci sarmalayicisinda" };
const say = (m, d) => (m.match(d) || []).length;
const yaz = (g, a, ...r) => console.log(String(a).padEnd(26) + r.map((x) => String(x).padStart(g)).join(""));
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
const sayac = (yol = "/sayac") => new Promise((c) => request({ port: 8103, path: yol }, (r) => {
  let g = ""; r.on("data", (p) => (g += p)); r.on("end", () => c(g === "{}" ? null : JSON.parse(g)));
}).end());
const tara = (d) => readdirSync(d).map((a) => readFileSync(`${d}/${a}`, "utf8"))   // [dosya, satir, ham, sarmali, sinir]
  .reduce((a, m) => [a[0] + 1, a[1] + m.split("\n").filter((x) => x.trim()).length,
    a[2] + say(m, /\brequest\(\{/g), a[3] + say(m, /\bistek\(/g), a[4] + say(m, /\.setTimeout\(/g)], [0, 0, 0, 0, 0]);

async function kosum(d) {                           // yedi cagri yerini canli servislere karsi kosar
  const m = { ...(await import(`./${d}/odunc.mjs`)), ...(await import(`./${d}/bildirim.mjs`)) };
  const c = { biten: 0, hata: 0 }, ad = Object.keys(m), once = (await sayac()).acik.bildirim;
  for (let t = 0; t < TUR; t += 1) for (const a of ad) m[a]().then(() => { c.biten += 1; }, () => { c.hata += 1; });
  await bekle(PENCERE);
  return { n: ad.length * TUR, ...c, acik: (await sayac()).acik.bildirim - once };
}
const gecit = (dis, ic) => new Promise((coz) => {   // ag gecidi -> odunc:8100 -> ucretlendirme:8104
  const t0 = process.hrtime.bigint();
  const bit = (s) => coz({ s, ms: Number(process.hrtime.bigint() - t0) / 1e6 });
  const r = request({ port: 8100, path: `/ucret?ic=${ic}`, agent: false }, (y) => {
    let g = ""; y.on("data", (p) => (g += p)); y.on("end", () => bit(g.includes("hata") ? "ic hata" : "yanit"));
  });
  r.setTimeout(dis, () => r.destroy(new Error("dis")));
  r.on("error", () => bit("dis zaman asimi")); r.end();
});

const sar = readFileSync("istek.mjs", "utf8").split("\n").filter((x) => x.trim()).length;
yaz(13, "yerlesim", "dosya", "satir", "cagri yeri", "sarmalanan", "atlanan", "sinir satiri");
for (const [d, ad] of Object.entries(V)) {
  const [dos, sat, ham, srm, snr] = tara(d), b = d === "b";
  yaz(13, ad, dos + b, sat + (b ? sar : 0), ham + srm, d === "a" ? snr : srm, d === "a" ? 0 : ham, snr + b);
}
const sayi = ["a/odunc.mjs", "a/bildirim.mjs"].flatMap((f) => readFileSync(f, "utf8").match(/setTimeout\((\d+)/g));
console.log(`yapilandirma yuzeyi: cagri yerinde ${sayi.length} sayi, ${new Set(sayi).size} ayri deger ` +
  `(${[...new Set(sayi)].map((x) => x.slice(11)).join(" / ")} ms); sarmalayicida 1 varsayilan, 0 gecersiz kilma`);

console.log(`\n${TUR} tur x 7 cagri yeri; gozlem penceresi ${PENCERE} ms`);
yaz(13, "yerlesim", "cagri", "yanit", "zaman asimi", "asili kalan", "acik istek");
for (const [d, ad] of Object.entries(V)) {
  const r = await kosum(d);
  yaz(13, ad, r.n, r.biten, r.hata, r.n - r.biten - r.hata, r.acik);
}

console.log(`\nic ice sinirlar: bagimliligin sabit gecikmesi ${BAGIMLILIK} ms, her cift icin ${N} istek;` +
  `\n  olculen ms bu kosumun ortalamasidir, 100'e yuvarlandi`);
yaz(18, "dis sinir / ic sinir", "sonuc", "olculen ms", "bagimliliga cagri", "oksuz tamamlanma");
for (const [dis, ic] of [[300, 800], [800, 300], [800, 800], [800, 100]]) {
  await sayac("/sifirla");
  const r = await Promise.all(Array.from({ length: N }, () => gecit(dis, ic)));
  await bekle(BAGIMLILIK + 300);                    // bagimliligin isini bitirmesi beklenir
  const s = await sayac();
  yaz(18, `${dis} / ${ic}`, [...new Set(r.map((x) => x.s))].join("+"),
    Math.round(r.reduce((a, x) => a + x.ms, 0) / N / 100) * 100, s.toplam.ucretlendirme, s.oksuz);
}
process.exit(0);
node servisler.mjs > /dev/null 2>&1 & SP=$!
sleep 1; node kur.mjs && node olc.mjs; kill $SP
yerlesim                          dosya        satir   cagri yeri   sarmalanan      atlanan sinir satiri
sinir yok                             2           46            7            0            7            0
cagri yerinde                         2           53            7            7            0            7
istemci sarmalayicisinda              3           28            7            6            1            1
yapilandirma yuzeyi: cagri yerinde 7 sayi, 2 ayri deger (200 / 800 ms); sarmalayicida 1 varsayilan, 0 gecersiz kilma

4 tur x 7 cagri yeri; gozlem penceresi 1500 ms
yerlesim                          cagri        yanit  zaman asimi  asili kalan   acik istek
sinir yok                            28           20            0            8            8
cagri yerinde                        28           20            8            0            0
istemci sarmalayicisinda             28           20            4            4            4

ic ice sinirlar: bagimliligin sabit gecikmesi 500 ms, her cift icin 10 istek;
  olculen ms bu kosumun ortalamasidir, 100'e yuvarlandi
dis sinir / ic sinir                   sonuc        olculen ms bagimliliga cagri  oksuz tamamlanma
300 / 800                    dis zaman asimi               300                10                10
800 / 300                            ic hata               300                10                 0
800 / 800                              yanit               500                10                 0
800 / 100                            ic hata               100                10                 0

Sınırsız satır, istemcinin varsayılan bir zaman aşımı olmadığını söylüyor: 28 çağrının 20’si yanıt aldı, 8’i ne yanıt ne hata aldı, pencere kapandığında bağımlılıkta hâlâ 8 istek açıktı. Bu 8 koşum süresinden bağımsızdır — pencere 15 dakika olsaydı da 8 olurdu, çünkü o çağrıları bitirecek bir mekanizma yok.

Çağrı yerine konan sınır kaynağı 46 satırdan 53 satıra çıkarıyor — çağrı yeri başına tam bir satır — ve yapılandırma yüzeyini yedi ayrı sayıya dağıtıyor; bu yedi sayının yalnız iki ayrı değeri var, yani beşi kopya. Buna karşılık kapsam tamdır: 7 sarmalanan, 0 atlanan çağrı yeri, 0 asılı çağrı. Sınır çağrı yerindeyken hiçbir çağrı yeri unutulamaz, çünkü unutulan sınır kaynakta yazılmamış bir satır olarak görünür.

Sarmalayıcı sürümü ters yöndedir. Toplamı daha kısadır — iki modül ve sarmalayıcı birlikte 28 satır, sınırsız hâlin bile altında — çünkü sarmalayıcı yalnız sınırı değil, istek kurmanın tekrarını da soğuruyor. Kaybedilen şey bağımlılık başına ayardır: çağrı yerinde katalog 200 ms’ye ayarlıydı, sarmalayıcı sürümünde o da 800 ms’dir.

Atlanan Çağrı Yeri

Sarmalayıcı satırının son iki sütunu bu dersin asıl ölçüsüdür: 6 sarmalanan, 1 atlanan çağrı yeri. Bedeli koşumda görünüyor: 28 çağrının 4’ü asılı kalıyor, sınırsız hâlin tam yarısı.

Fark yalnız sayıda değil, bilgi durumundadır: sınırsız hâlde her çağrı yeri şüphelidir ve yazan bunu bilir; sarmalayıcılı hâlde altı çağrı yeri güvenlidir ve bir tanesi yalan söyler. Buradan çıkan kural şudur: sarmalayıcı yerleşimi kapsam denetimi olmadan eksiktir. tara işlevinin yaptığı — ham çağrı kurucusunu aramak — sürekli koşması gereken bir denetimdir.

İç İçe Sınırların Çelişmesi

Üçüncü tablo aynı işi üç katmandan geçiriyor ve her katmanın sınırı ötekini bilmiyor.

İlk satır yanlış ayarın adıdır: iç sınır dış sınırdan büyük. Ağ geçidi 300 ms’de vazgeçiyor, ödünç servisi beklemeyi sürdürüyor, bağımlılık 500 ms’de işini bitiriyor ve yanıt kimseye gitmiyor: 10 öksüz tamamlanma. Hiçbir gösterge bunu hatalı saymaz; ödünç servisi kendi açısından başarılı bir çağrı yapmıştır.

İkinci satırda iç sınır dıştakinden küçüktür: ödünç servisi önce vazgeçiyor, ağ geçidine adı konmuş bir hata dönüyor, öksüz tamamlanma sıfıra iniyor. Sınırların içten dışa büyümesi bir tercih değil, bir sıralama kuralıdır. Dördüncü satır kuralın öbür ucudur: iç sınır bağımlılığın gecikmesinin altına indiğinde (100 ms’ye karşı 500 ms) bağımlılıkta hiçbir sorun yokken her çağrı başarısız oluyor.

Üç satırın ortak sütunu asıl dersi veriyor: bağımlılığa giden çağrı her ayarda 10. Zaman aşımı bağımlılığın yükünü azaltmaz, yalnız bekleyeni serbest bırakır.

Sınanabilirlik

Sınırın yeri, davranışının testte nasıl tetiklendiğini de belirler.

// test/sinir.test.mjs — ayni davranis iki yerlesimde nasil tetiklenir.
import { test } from "node:test";
import assert from "node:assert/strict";
import { VARSAYILAN } from "../istek.mjs";
import * as A from "../a/bildirim.mjs";
import * as B from "../b/bildirim.mjs";
const kova = (g) => (g < 100 ? "100 ms altinda" : `~${Math.round(g / 100) * 100} ms`);
const sure = async (f) => { const t = Date.now(); await assert.rejects(f()); return Date.now() - t; };

test("cagri yerindeki sinir bir sabittir: test o kadar bekler", async () => {
  const g = await sure(A.bildirimGonder);
  assert.ok(g >= 700, `sinir 800 ms; olculen ${g}`);
  console.log(`  cagri yerinde: red ${kova(g)}`);
});
test("sarmalayicidaki sinir tek yerden dusurulur", async () => {
  VARSAYILAN.sure = 20;
  const g = await sure(B.bildirimGonder);
  assert.ok(g < 200, `sinir 20 ms; olculen ${g}`);
  console.log(`  sarmalayicida: red ${kova(g)}`);
});
test("atlanan cagri yeri sarmalayicinin ayarindan etkilenmez", async () => {
  assert.equal(await Promise.race([B.bildirimYinele().then(() => "yanit", () => "red"),
    new Promise((c) => setTimeout(() => c("hâlâ asili"), 300))]), "hâlâ asili");
});
node servisler.mjs > /dev/null 2>&1 & SP=$!
sleep 1
node --test --test-force-exit --test-reporter=tap test/sinir.test.mjs |
  grep -E "^(ok|not ok|#   |# (tests|pass|fail) )"
kill $SP
#   cagri yerinde: red ~800 ms
ok 1 - cagri yerindeki sinir bir sabittir: test o kadar bekler
#   sarmalayicida: red 100 ms altinda
ok 2 - sarmalayicidaki sinir tek yerden dusurulur
ok 3 - atlanan cagri yeri sarmalayicinin ayarindan etkilenmez
# tests 3
# pass 3
# fail 0

Çağrı yerindeki sınır bir sabittir; testin onu değiştirecek tutamağı yoktur ve gerçek süreyi beklemek zorundadır. Sarmalayıcıdaki sınır bir değişkendir; test varsayılanı 20 ms’ye indirip aynı dalı 100 ms’nin altında doğruluyor. Koşumdan bağımsız nicelik sınırların oranıdır: kırk kat.

Üçüncü test bu dersin ölçüsünü teste çeviriyor: atlanan çağrı yeri, varsayılan 20 ms’ye indirilmiş olmasına rağmen 300 ms sonunda hâlâ asılıdır. Kapsam bir sayı olarak ölçülebiliyorsa testte de doğrulanabilir. Çağrı yerinde yerleşimde böyle bir test yazılamaz, çünkü doğrulanacak bir kapsam kavramı yoktur.

Özet

  • Zaman aşımının etkisi M19/K05 Dayanıklılık ve Güvenilirlik kursunda ölçüldü; burada ölçülen sınırın kodda nereye yerleştiğidir. İstemcinin varsayılan sınırı yoktur: sınırsız sürümde 28 çağrının 8’i hiç sonuçlanmadı ve bağımlılıkta 8 istek açık kaldı.
  • Çağrı yerinde yerleşim 46 satırı 53 satıra çıkarır ve yapılandırma yüzeyini yedi ayrı sayıya dağıtır (iki ayrı değer, beş kopya), ama kapsamı tamdır: 7 sarmalanan, 0 atlanan çağrı yeri.
  • Sarmalayıcı yerleşimi daha kısadır (28 satır) ve yapılandırma yüzeyi tektir, ama kapsamı garanti etmez: 6 sarmalanan, 1 atlanan çağrı yeri, 4 asılı çağrı. Atlanan çağrı yeri kaynakta bir eksiklik olarak görünmez, ancak taramayla bulunur.
  • İç sınır dış sınırdan büyükse dış katman vazgeçer, iç katman çalışmayı sürdürür: 10 isteğin 10’unda öksüz tamamlanma. Sınırların içten dışa büyümesi bir sıralama kuralıdır.
  • Bağımlılığa giden çağrı sayısı dört ayarın hepsinde 10’dur; zaman aşımı bekleyeni serbest bırakır, bağımlılığın yükünü azaltmaz. Sınırın değişken olması sınanabilirliği belirler: atlanan çağrı yeri doğrudan test edilebilir.

Sonraki Adım

Sınır kondu ve bekleyen serbest kaldı, ama elinde bir hata var ve o hatayla ne yapılacağı söylenmedi. En yakın karşılık yeniden denemektir. Bu kararın nereye konduğu da bir yerleşim sorusudur ve zaman aşımından daha tehlikelidir: zaman aşımı bağımlılığın yükünü değiştirmezken yeniden deneme onu doğrudan çoğaltır. Sonraki ders yeniden denemenin hangi katmanda durduğunu ölçer — aynı kural üç ayrı katmana kurulur, sonra üçü aynı anda açılır ve tek bir kullanıcı isteğinin bağımlılıkta kaç çağrıya dönüştüğü sayılır.

İ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