İçeriğe geç
academia.sh

Ders 18 / 19

Arıza Kipleri

Arıza kiplerinin uygulama kodundaki karşılığı: aynı ucun sekiz ayrı biçimde bozulması, çağrı yerinde beş farklı hata kimliği üretmesi, kalıp yığınının bunlardan beşini görüp üçünü hiç görmemesi ve altı kipte çağrı yerine aynı sıfır değerinin ulaşması.

İçindekiler

Bu konuda altı kalıp kuruldu. Her biri bir bozulma biçimine karşı yazıldı, her birinin kodda bir yeri var ve her birinin kapsamadığı çağrı yerleri sayıldı. Kalıpların ne yaptığı belli; hangi arızaya karşılık geldikleri ise tek tek yazılmadı.

Arıza kipi sözlüğü — bir bağımlılığın kaç ayrı biçimde bozulabileceği ve her birinin sistem üzerindeki etkisi — Dayanıklılık ve Güvenilirlik kursunda kuruldu. Burada sorulan soru başkadır: bu kiplerin her biri uygulama kodunda ne olarak görünür. Çağrı yeri bir hata nesnesi mi alır, bir zaman aşımı mı, boş bir gövde mi, yoksa hiçbir belirti göstermeyen geçerli bir yanıt mı. Bu, konunun kapanışıdır ve kalıpların toplam kapsamını sayar.

Sekiz Kipin Kodda Üretilmesi

DY15 — sekiz kip. Ücretlendirme servisinin tek bir ucu, sekiz ayrı biçimde bozulur: bağlantı reddi, geciken yanıt, sunucu hatası, boş gövde, ortasından kesilen gövde, doğru yapıda ama yanlış tipte alan, geçerli ve doğru tipte ama yanlış değer, ve zaman aşımının hemen altında kalan yavaşlık. Son üçü HTTP 200 ve geçerli JSON döndürür. Gerçek borç 12,5’tir ve gecikme ücreti hesabı bu değere beş ekler.

DY16 — yığının ayarları. Zaman aşımı 120 ms, deneme sayısı 2, devre kesici eşiği 3 ardışık başarısızlık, kip başına 5 çağrı. Bu değerler önceki derslerde ölçüldü; burada sabit tutulurlar.

// kip/kip.mjs — bagimliligin sekiz bozulma bicimi ve uzerine yiginlanmis kaliplar.
import http from "node:http";

export const GERCEK = 12.5, ZAMAN_ASIMI = 120, DENEME = 2, ESIK = 3, CAGRI = 5;
const gonder = (res, g) => { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(g)); };

// Her kip ayni ucu farkli bir bicimde bozar. Sonuncu ikisi HTTP 200 ve gecerli JSON dondurur.
export const KIP = {
  "baglanti-reddi": null,                                     // dinleyici yok
  "zaman-asimi": (res) => setTimeout(() => gonder(res, { borc: GERCEK }), 400),
  "sunucu-hatasi": (res) => { res.writeHead(500, { "content-type": "application/json" }); res.end("{}"); },
  "bos-govde": (res) => { res.writeHead(200, { "content-type": "application/json" }); res.end(""); },
  "kesik-govde": (res) => {
    res.writeHead(200, { "content-type": "application/json", "content-length": "20" });
    res.write('{"borc":'); res.socket.destroy();
  },
  "yanlis-tip": (res) => gonder(res, { borc: String(GERCEK.toFixed(2)) }),
  "sessiz-yanlis": (res) => gonder(res, { borc: 0 }),
  "sinir-alti-yavaslik": (res) => setTimeout(() => gonder(res, { borc: GERCEK }), 90),
};

export function kesici() {
  let ard = 0;
  return { acik: () => ard >= ESIK, basari() { ard = 0; }, hata() { ard += 1; } };
}

// Zaman asimi + yeniden deneme + devre kesici tek sarmalayicida. Hangisinin tetiklendigi iz'e yazilir.
export async function korumali(port, k, iz) {
  if (k.acik()) { iz.add("kesici"); throw new Error("devre acik"); }
  let son;
  for (let d = 1; d <= DENEME; d += 1) {
    try {
      const y = await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(ZAMAN_ASIMI) });
      if (y.ok === false) { const e = new Error(`HTTP ${y.status}`); e.name = `HTTP ${y.status}`; throw e; }
      const g = await y.json();
      k.basari();
      return g;
    } catch (e) {
      son = e;
      iz.add(e.name === "TimeoutError" ? "asim" : "hata");
      if (d < DENEME) iz.add("deneme");
    }
  }
  k.hata();
  throw son;
}

export const belirti = (e) => (e.cause?.code ? `${e.name}/${e.cause.code}` : e.name);

// Cagri yeri: bozulma yedegi burada. Yedek deger, sessiz-yanlis kipinin dondurdugu degerle ayni.
export async function cagriYeri(port, k, iz) {
  try { return { g: await korumali(port, k, iz), yedek: 0, b: "HTTP 200" }; }
  catch (e) { iz.add("yedek"); return { g: { borc: 0 }, yedek: 1, b: belirti(e) }; }
}

Kalıp Yığını ve Ölçüm

Ölçüm her kipi sırayla üretir ve beş çağrı yapar. Kaydedilen dört şey var: çağrı yerinde görünen hata kimliği, tetiklenen kalıplar, yedeğe düşmeden geçen çağrı sayısı ve çağrı yerine ulaşan borc değeriyle onun üzerinden hesaplanan gecikme ücreti.

// kip/olcum.mjs — her kipi tek tek uretir, cagri yerinde ne gorundugunu ve hangi kalibin tetiklendigini yazar.
import http from "node:http";
import { KIP, GERCEK, ZAMAN_ASIMI, DENEME, ESIK, CAGRI, kesici, cagriYeri } from "./kip.mjs";

let simdi = "sessiz-yanlis";
const s = http.createServer((req, res) => KIP[simdi](res));
await new Promise((r) => s.listen(0, "127.0.0.1", r));
const PORT = s.address().port;
const olu = http.createServer(() => {});            // dinleyicisi kapatilmis port
await new Promise((r) => olu.listen(0, "127.0.0.1", r));
const OLU_PORT = olu.address().port;
await new Promise((r) => olu.close(r));

console.log(`kosumdan bagimsiz: zaman asimi ${ZAMAN_ASIMI} ms, deneme ${DENEME}, kesici esigi ${ESIK}, kip basina ${CAGRI} cagri`);
console.log(`gercek borc ${GERCEK}; gecikme ucreti hesabi borc + 5 = ${GERCEK + 5} olmali\n`);
console.log(`${"kip".padEnd(20)}${"cagri yerinde gorunen".padEnd(25)}${"tetiklenen kalip".padEnd(25)}${"gecen".padStart(6)}${"borc".padStart(8)}${"borc+5".padStart(9)}`);

const satir = [];
for (const kip of Object.keys(KIP)) {
  simdi = kip;
  const k = kesici(), iz = new Set();
  let gecen = 0, ilk = null;
  for (let c = 0; c < CAGRI; c += 1) {
    const y = await cagriYeri(kip === "baglanti-reddi" ? OLU_PORT : PORT, k, iz);
    if (y.yedek === 0) gecen += 1;
    ilk ??= y;                                     // belirti ilk cagridan okunur
  }
  const gorulen = iz.size > 0, dogru = ilk.g.borc === GERCEK;
  satir.push({ kip, gorulen, dogru, iz });
  console.log(`${kip.padEnd(20)}${ilk.b.padEnd(25)}${([...iz].join("+") || "hicbiri").padEnd(25)}` +
    `${`${gecen}/${CAGRI}`.padStart(6)}${String(ilk.g.borc).padStart(8)}${String(ilk.g.borc + 5).padStart(9)}`);
}
s.close();

const gormeyen = satir.filter((x) => x.gorulen === false);
console.log(`\n${satir.length} kipten ${satir.length - gormeyen.length}'i en az bir kalibi tetikledi, ` +
  `${gormeyen.length}'i hicbir kalibi tetiklemedi: ${gormeyen.map((x) => x.kip).join(", ")}`);
const yanlis = satir.filter((x) => x.dogru === false);
console.log(`gercek deger ${yanlis.length}/${satir.length} kipte cagri yerine ulasmadi: ${yanlis.filter((x) => x.gorulen).length} kipte bir kalip bunu bildirdi, ` +
  `${yanlis.filter((x) => !x.gorulen).length} kipte hicbir bildirim yok`);
console.log("\nkalip basina kapsam: " + ["asim", "hata", "deneme", "kesici", "yedek"]
  .map((kl) => `${kl} ${satir.filter((x) => x.iz.has(kl)).length}/${satir.length}`).join(", "));
console.log(`kosumdan bagimsiz ust sinir: basarisiz bir cagri en cok ${DENEME * ZAMAN_ASIMI} ms bekler, devre acildiktan sonra 0 ms`);
kosumdan bagimsiz: zaman asimi 120 ms, deneme 2, kesici esigi 3, kip basina 5 cagri
gercek borc 12.5; gecikme ucreti hesabi borc + 5 = 17.5 olmali

kip                 cagri yerinde gorunen    tetiklenen kalip          gecen    borc   borc+5
baglanti-reddi      TypeError/ECONNREFUSED   hata+deneme+yedek+kesici    0/5       0        5
zaman-asimi         TimeoutError             asim+deneme+yedek+kesici    0/5       0        5
sunucu-hatasi       HTTP 500                 hata+deneme+yedek+kesici    0/5       0        5
bos-govde           SyntaxError              hata+deneme+yedek+kesici    0/5       0        5
kesik-govde         TypeError/UND_ERR_SOCKET hata+deneme+yedek+kesici    0/5       0        5
yanlis-tip          HTTP 200                 hicbiri                     5/5   12.50   12.505
sessiz-yanlis       HTTP 200                 hicbiri                     5/5       0        5
sinir-alti-yavaslik HTTP 200                 hicbiri                     5/5    12.5     17.5

8 kipten 5'i en az bir kalibi tetikledi, 3'i hicbir kalibi tetiklemedi: yanlis-tip, sessiz-yanlis, sinir-alti-yavaslik
gercek deger 7/8 kipte cagri yerine ulasmadi: 5 kipte bir kalip bunu bildirdi, 2 kipte hicbir bildirim yok

kalip basina kapsam: asim 1/8, hata 4/8, deneme 5/8, kesici 5/8, yedek 5/8
kosumdan bagimsiz ust sinir: basarisiz bir cagri en cok 240 ms bekler, devre acildiktan sonra 0 ms

Çağrı Yerinde Beş Kimlik, İki Yol

İkinci sütun sekiz kipin uygulama tarafındaki kimliklerini veriyor ve bunlar birbirine benzemiyor. Bağlantı reddi bir TypeError üretiyor ve gerçek neden ancak cause.code alanında görünüyor. Zaman aşımı ayrı bir hata sınıfı, TimeoutError. Sunucu hatası hiç hata fırlatmıyor — çözülen bir yanıt nesnesi geliyor ve durum kodunun denetlenmesi çağıran tarafa kalıyor; denetim yazılmazsa 500 yanıtı başarılı sayılır. Boş gövde bir SyntaxError, yani bir ayrıştırma hatası; ağ katmanında hiçbir sorun yok, gövde geçersiz. Kesilen gövde yine TypeError, ama başka bir cause.code ile.

Beş ayrı kimlik, beş ayrı yerden okunuyor: hata sınıfı, hata nedeni, durum kodu, ayrıştırma sonucu. Bir arıza kipini kodda tanımak, bu dört kaynağın hepsine aynı anda bakmayı gerektiriyor.

Buna karşılık son sütunlardaki davranış tek tip. Beş kipin beşinde de aynı dört kalıp tetikleniyor ve beş çağrının beşi de yedeğe düşüyor. Kalıp yığını kipleri ayırt etmiyor; hepsini tek bir kategoriye indiriyor: “çağrı başarısız oldu.” Bu, kalıpların bir kusuru değil, kapsamlarının sınırıdır — ama sonucu şudur ki bağlantı reddiyle boş gövde arasındaki fark, ki biri servisin düştüğünü biri servisin bozuk yanıt ürettiğini söyler, kalıp yığınından geçtikten sonra kaybolur. Bu farkı taşıyacak tek şey, o hata kimliğini kaydeden bir günlük kaydıdır.

Kalıp başına kapsam satırı bunu sayıya döküyor. Zaman aşımı sekiz kipin yalnız birinde tetikleniyor: geciken yanıtta. Kalan dört başarısız kip zaten kendiliğinden hata fırlatıyor ve zaman aşımına hiç ihtiyaç duymuyor. Yeniden deneme, devre kesici ve bozulma yedeği beşer kipte tetikleniyor. Yığındaki en pahalı kalıp — devre kesici — en yaygın tetiklenen kalıp aynı zamanda; en dar kapsamlı olan zaman aşımı ise onsuz kalındığında çağrının süresiz asılı kalmasına yol açan tek kalıptır. Kapsam genişliği ile önem aynı şey değildir.

Hiçbir Kalıbın Karşılamadığı Kipler

Alt üç satır bu dersin asıl sonucudur. Yanlış tipte alan, sessiz yanlış değer ve sınır altı yavaşlık kiplerinde tetiklenen kalip sütunu hicbiri yazıyor ve beş çağrının beşi de geçiyor. Kalıp yığını bu kipleri görmüyor, çünkü yığındaki her kalıp taşıma katmanındaki bir belirtiyle tetikleniyor: bağlantı kurulamadı, süre doldu, durum kodu hatalı, gövde ayrıştırılamadı. Bu üç kipte taşıma katmanı kusursuz çalışıyor. Bağlantı kuruldu, yanıt zamanında geldi, durum kodu 200, gövde geçerli JSON.

Sonuçları ise kusursuz değil. Yanlış tipte alan geldiğinde gecikme ücreti hesabı 12.505 üretiyor: sayı beklenen yere metin geldiği için toplama işlemi birleştirmeye dönüşüyor. Ortaya çıkan değer bir hata değil, yanlış bir ücrettir ve üyenin hesabına yazılır. Sessiz yanlış değerde ücret 5 çıkıyor — gerçek olması gereken 17,5 yerine. Sınır altı yavaşlıkta değer doğru, tek bedel harcanan süredir; kalıp yığını için bu kip sağlam bir çağrıdan ayırt edilemez.

Kalıpların bu kipleri karşılamamasının nedeni tasarım hatası değil, girdi eksikliğidir. Bir kalıbın tetiklenmesi için bir belirti gerekir; bu üç kipte belirti taşıma katmanında değil, verinin kendisindedir. Onu görecek şey bir dayanıklılık kalıbı değil, yanıtın şemasını ve değerini denetleyen bir doğrulamadır — ve o doğrulama bu konuda hiç yazılmadı.

Sıfırın Üç Anlamı

Son bir ölçü, çağrı yerine ulaşan değer sütununda duruyor. Sekiz kipin altısında borc değeri sıfır. Bunlardan beşinde sıfır bozulma yedeğidir: bir kalıp tetiklendi, arıza görüldü ve yerine konan değer sıfır oldu. Altıncısında ise sıfır, servisin gerçekten döndürdüğü değerdir ve arkasında hiçbir kalıp, hiçbir kayıt, hiçbir belirti yok.

Bir önceki derste yedek değerin gerçek değerden ayırt edilemediği ölçülmüştü; buradaki tablo aynı sorunun bir basamak daha derinini gösteriyor. Aynı sayı üç ayrı şey anlatıyor: gerçek veri, kalıbın koyduğu yedek ve bağımlılığın sessizce ürettiği yanlış. Yığının çıktısına bakan kod bu üçünü ayırt edemez. Ayırt etmenin tek yolu değerin yanına nereden geldiğinin yazılmasıdır: yedek işareti bunu beş kipte sağlıyor, altıncısında sağlayacak bir şey yok.

Konu buraya kadar kalıpların koda nereye yerleştiğini saydı: sarmalanan ve atlanan çağrı yerleri, sınırın dışında kalan yollar, yanlış anahtarın ürettiği davranış. Bu son ölçüm kapsamı bir bütün olarak veriyor. Sekiz arıza kipinden beşi görülüyor, üçü görülmüyor ve görülmeyen üçünün ikisi çağrı yerine yanlış bir değer taşıyor. Kalıplar arızayı görünür kılmaz; yalnızca kendilerini tetikleyen belirtiyi görünür kılar. Belirtisi olmayan arıza, kalıplı bir sistemde de kalıpsız bir sistemde de aynı biçimde görünmezdir.

Özet

  • Arıza kipi sözlüğü Dayanıklılık ve Güvenilirlik kursunda kuruldu; buradaki ölçü her kipin uygulama kodunda hangi kimlikle göründüğüdür.
  • Sekiz kip çağrı yerinde beş ayrı kimlik üretiyor ve bu kimlikler dört ayrı yerden okunuyor: hata sınıfı (TimeoutError), hata nedeni (ECONNREFUSED, UND_ERR_SOCKET), durum kodu (HTTP 500) ve ayrıştırma sonucu (SyntaxError).
  • Kalıp yığını bu beş kimliği ayırt etmiyor; beşinde de aynı dört kalıp tetikleniyor ve beş çağrının beşi yedeğe düşüyor. Kipler arasındaki fark yığından geçtikten sonra kaybolur.
  • Kalıp başına kapsam eşit değil: zaman aşımı 8 kipten 1’inde, yeniden deneme, devre kesici ve bozulma yedeği 5’inde tetikleniyor. Kapsam genişliği önemle aynı şey değildir.
  • 3 kip hiçbir kalıbı tetiklemiyor — yanlış tip, sessiz yanlış değer ve sınır altı yavaşlık — çünkü üçünde de taşıma katmanı kusursuz çalışıyor. İkisi çağrı yerine yanlış değer taşıyor: gecikme ücreti 17.5 yerine 12.505 ve 5 çıkıyor.
  • 8 kipin 6’sında çağrı yerine sıfır ulaşıyor; 5’inde bu bir yedek değerdir ve işareti vardır, 1’inde bağımlılığın sessiz yanlışıdır ve hiçbir işareti yoktur.

Sonraki Adım

Bu konu kalıpları kurdu, kodda nereye yerleştiklerini saydı ve kapsamlarının dışında kalanı ölçtü. Ölçülen her sayı bir sistemi tarif ediyor: on dört çağrı yerinin ikisi sarmalanmamış, sekiz yol-erişim çiftinin ikisi hiçbir sınırın dışında, sekiz arıza kipinin üçü hiçbir kalıp tarafından görülmüyor. Bu boşlukların tamamı yazıldıkları anda değil, bir arıza onlara dokunduğunda öğrenildi.

Öğrenmenin kendisi ise henüz bir düzenek değil. Bir kesinti yaşandığında zaman çizelgesinin nasıl kurulacağı, ilk üç konuda üretilen sinyallerden hangilerinin o çizelgeyi doldurduğu, çıkan sonucun kime ne söylediği ve çıkarılan dersin koda nasıl döneceği — hiçbiri tasarlanmadı. Bir sonraki ders bu düzeneği ele alır ve şunu sorar: bir olayın ardından yazılan metin, bu konudaki sayılardan hangilerini değiştirebilir.

İ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