İçeriğe geç
academia.sh

Ders 03 / 15

Ölçüt Seçimi

Aynı koşumdan yedi ayrı sonuç çıkarılması: ortalama ile ortancanın eşiği geçerken kuyruk yüzdeliklerinin kalması, bir yüzdeliğin okunabilmesi için gereken en az örnek sayısının alt örnek taramasıyla sayılması ve ulaşılan hız raporlanmadan bir gecikme sayısının okunamayacağının hedef hız merdiveniyle gösterilmesi.

İçindekiler

Önceki iki ders her kararı tek bir sayıya bağladı: p95. Bu seçim hiç tartışılmadı. Ortalama alınsaydı aynı koşumlar bambaşka görünürdü, p99 seçilseydi karar yine değişirdi ve üç saniyelik bir koşumda p99’un kaç örneğe dayandığı sorulmadı. Bu ders ölçütün kendisini bir karar olarak ele alır.

Ölçüt Bir Seçimdir

Bir koşumun ürettiği şey tek bir sayı değil, binlerce gecikme değeridir. Ölçüt, bu dağılımdan tek bir sayı çıkaran işlevdir ve seçim eşiğin anlamını değiştirir. Ortalama bütün örnekleri eşit ağırlıkla toplar; bir tek çok yavaş istek onu az, bin tane orta yavaş istek çok oynatır. Yüzdelik gecikme ise sıralı örnekte bir konumu okur: p99, isteklerin yüzde birinin daha kötü olduğu değerdir ve kaç kat daha kötü olduklarını hiç söylemez.

Eşik önceki derslerden aynen gelir: IO1 (varsayım + hesap) — p95 ≤ 20 ms. Bu derste eşiğin bir de örnek koşulu yazılır.

IO5 (hesap) — bir yüzdeliğin karar taşıyabilmesi için koşumun en az 1/(1p)1/(1-p) örnek içermesi gerekir: p95 için 20, p99 için 100, p99,9 için 1.000. Gerekçe: yüzdeliğin dışladığı pay en az bir örneğe düşmezse, okunan değer o payın değil, dağılımın daha alçak bir yerinin değeridir. Bu sayı bir varsayım değil, örnek sayısından çıkan bir alt sınırdır ve aşağıda taramayla sınanır.

Kararın sahibi ölçüt seçimidir: p99’a bağlanan bir eşik, koşum süresine de bir alt sınır koyar. Ölçüt seçmek aynı zamanda testin ne kadar süreceğini seçmektir.

Düzenek

Servis önceki dersten aynen gelir: /ara her iki yapıda dizinlidir, /odunc yalnız sağlam yapıda. Üreteç bu kez bütün gecikmeleri biriktirip dosyaya yazar; analiz sunucusuz çalışır.

// kutuphane/sunucu.mjs — gise servisi: /ara katalogda kitap arar, /odunc uyenin acik odunc
// sayisini dogrulayip bes kayit yazar. Argumanlar: <baglanti-noktasi> <yapi 0|1>
// 0 = odunc yolunda uye dizini yok (tam tarama), 1 = dizinli.
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const UYE = 500, BASLANGIC = 110000, KITAP = 5;  // uye basina 220 kayit; gise islemi 5 kitap
const port = Number(process.argv[2]), yapi = process.argv[3];

if (Number.isInteger(port) === false || ["0", "1"].includes(yapi) === false) {
  console.log("kullanim: node kutuphane/sunucu.mjs <baglanti-noktasi> <yapi 0|1>");
} else {
  const db = new DatabaseSync(":memory:");
  db.exec("CREATE TABLE odunc(no INTEGER PRIMARY KEY, uye_no TEXT, kitap_no TEXT, gun INT)");
  db.exec("CREATE INDEX odunc_kitap ON odunc(kitap_no)");      // arama yolu her iki yapida dizinli
  if (yapi === "1") db.exec("CREATE INDEX odunc_uye ON odunc(uye_no)");
  db.exec("BEGIN");
  const doldur = db.prepare("INSERT INTO odunc(uye_no, kitap_no, gun) VALUES(?,?,?)");
  for (let i = 0; i < BASLANGIC; i += 1) doldur.run(`U-${i % UYE}`, `K-${i % 9000}`, i % 400);
  db.exec("COMMIT");

  const ara = db.prepare("SELECT no, uye_no, gun FROM odunc WHERE kitap_no = ? LIMIT 5");
  const say = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ?");
  const ekle = db.prepare("INSERT INTO odunc(uye_no, kitap_no, gun) VALUES(?,?,?)");
  let arama = 0, odunc = 0, kayit = BASLANGIC, tarama = 0;

  createServer((gelen, yanit) => {
    const u = new URL(gelen.url, "http://yerel");
    yanit.writeHead(200, { "content-type": "application/json" });
    if (u.pathname === "/olcut") { yanit.end(JSON.stringify({ arama, odunc, kayit, tarama })); return; }
    if (u.pathname === "/ara") {
      arama += 1;
      yanit.end(JSON.stringify({ bulunan: ara.all(u.searchParams.get("kitap") ?? "K-1").length }));
      return;
    }
    odunc += 1;
    tarama += yapi === "0" ? kayit : Math.ceil(Math.log2(kayit));   // dizinsiz yapida tam tarama
    const uye = u.searchParams.get("uye") ?? "U-1";
    const n = say.get(uye).n;
    for (let j = 0; j < KITAP; j += 1) ekle.run(uye, `K-${(odunc + j) % 9000}`, odunc % 400);
    kayit += KITAP;
    yanit.end(JSON.stringify({ uye, odunc: n + 1 }));
  }).listen(port);
}
// kutuphane/olc.mjs — acik dongulu uretec; bir kosumun butun gecikmelerini biriktirip
// JSON satiri olarak basar. Argumanlar: <nokta> <yapi 0|1> <hedef hiz> <odunc payi> <sure s>
import { Agent, request } from "node:http";

const [port, yapi, hiz, pay, sure] = process.argv.slice(2).map((x, i) => (i === 1 ? x : Number(x)));
const agent = new Agent({ keepAlive: true, maxSockets: 8192 });
let sira = 0;

function birIstek(kayit) {
  const i = sira++;
  const yol = (i * 9973) % 1000 < pay * 1000 ? `/odunc?uye=U-${i % 500}` : `/ara?kitap=K-${i % 9000}`;
  const bas = performance.now();
  return new Promise((coz) => {
    const r = request({ port, path: yol, agent }, (y) => {
      y.resume(); y.on("end", () => { kayit.push(performance.now() - bas); coz(); });
    });
    r.on("error", () => { kayit.push(performance.now() - bas); coz(); });
    r.end();
  });
}

if (Number.isInteger(port) === false || ["0", "1"].includes(yapi) === false) {
  console.log("kullanim: node kutuphane/olc.mjs <nokta> <yapi 0|1> <hedef hiz> <odunc payi> <sure>");
} else {
  await birIstek([]);                              // isinma istegi
  const kayit = [], ucus = [];
  let borc = 0, sonT = 0;
  const bas = performance.now();
  while (performance.now() - bas < sure * 1000) {
    const t = (performance.now() - bas) / 1000;
    borc += hiz * (t - sonT); sonT = t;
    while (borc >= 1) { borc -= 1; ucus.push(birIstek(kayit)); }
    await new Promise((c) => setTimeout(c, 5));
  }
  await Promise.all(ucus);
  const gecen = (performance.now() - bas) / 1000;
  console.log(JSON.stringify({ yapi: yapi === "0" ? "dizinsiz" : "dizinli", hiz, pay,
    ulasilan: kayit.length / gecen, ms: kayit.map((x) => Math.round(x * 1000) / 1000) }));
}
agent.destroy();

Alt örnek taraması, kaydedilen gecikmelerden yerine koyarak örnek çeker; üreteç doğrusal eşleniktir ve tohumu görünürdür.

// kutuphane/analiz.mjs — kayitli gecikmeler uzerinde olcut secimi, ornek sayisi taramasi ve
// verim-gecikme okumasi. Arguman: <jsonl>
import { readFileSync } from "node:fs";

const ESIK = 20, TOHUM = 20260731, ALT = 200;      // IO1 esigi, gorunur tohum, alt ornek sayisi
const s = readFileSync(process.argv[2], "utf8").trim().split("\n").map((x) => JSON.parse(x));
const bul = (y, h, p) => s.find((r) => r.yapi === y && r.hiz === h && r.pay === p);
const v = (x, n = 2) => x.toFixed(n).replace(".", ",");
const g = (x, n) => String(x).padStart(n);
const ort = (d) => d.reduce((a, b) => a + b, 0) / d.length;
const yuz = (d, p) => { const t = [...d].sort((a, b) => a - b);
  return t[Math.min(t.length - 1, Math.floor((p / 100) * t.length))]; };

const A = bul("dizinsiz", 1700, 0.25), B = bul("dizinli", 1700, 0.25);
const OLCUT = [["ortalama", (d) => ort(d)], ["p50", (d) => yuz(d, 50)], ["p90", (d) => yuz(d, 90)],
  ["p95", (d) => yuz(d, 95)], ["p99", (d) => yuz(d, 99)], ["p99,9", (d) => yuz(d, 99.9)],
  ["en buyuk", (d) => Math.max(...d)]];
console.log(`ayni iki kosum, yedi olcut; esik ${ESIK} ms (IO1), ornek ${A.ms.length} / ${B.ms.length}`);
console.log("olcut     dizinsiz(ms)  karar   dizinli(ms)  karar   dizinsiz/dizinli");
for (const [ad, f] of OLCUT) {
  const a = f(A.ms), b = f(B.ms);
  console.log(ad.padEnd(10) + g(v(a), 12) + g(a <= ESIK ? "gecti" : "kaldi", 7) +
    g(v(b), 13) + g(b <= ESIK ? "gecti" : "kaldi", 7) + g(v(a / b), 18));
}

let t = TOHUM;
const rast = () => { t = (1103515245 * t + 12345) % 2147483648; return t / 2147483648; };
const altOrnek = (d, n) => Array.from({ length: n }, () => d[Math.floor(rast() * d.length)]);
console.log(`\nornek sayisi taramasi: ${ALT} alt ornek, tohum ${TOHUM}; tam ornekte p99 ` +
  `dizinsiz ${v(yuz(A.ms, 99))} ms, dizinli ${v(yuz(B.ms, 99))} ms`);
console.log("ornek  p99 en kucuk  p99 en buyuk  yayilim kat  yanlis gecme/200  yanlis kalma/200");
for (const n of [20, 50, 100, 500, 2000]) {
  const a = Array.from({ length: ALT }, () => yuz(altOrnek(A.ms, n), 99));
  const b = Array.from({ length: ALT }, () => yuz(altOrnek(B.ms, n), 99));
  console.log(g(n, 5) + g(v(Math.min(...a)), 13) + g(v(Math.max(...a)), 14) +
    g(v(Math.max(...a) / Math.min(...a)), 13) + g(a.filter((x) => x <= ESIK).length, 18) +
    g(b.filter((x) => x > ESIK).length, 18));
}

console.log("\nverim-gecikme: dizinsiz yapi, ayni karisim, dort hedef hiz");
console.log("hedef hiz  ulasilan/s  ulasilan/hedef  p95(ms)  karar");
for (const h of [900, 1300, 1700, 2100]) {
  const r = bul("dizinsiz", h, 0.25);
  console.log(g(h, 9) + g(v(r.ulasilan), 12) + g(v(r.ulasilan / h, 3), 16) +
    g(v(yuz(r.ms, 95)), 9) + g(yuz(r.ms, 95) <= ESIK ? "gecti" : "kaldi", 7));
}
# olc.sh — bes kosum: ayni senaryo iki yapida (5 s), arti dizinsiz yapida uc ek hedef hiz (2 s).
rm -f sonuc.jsonl
for Y in 0 1; do
  node kutuphane/sunucu.mjs 8853 $Y & SUNUCU=$!
  sleep 1.2
  node kutuphane/olc.mjs 8853 $Y 1700 0.25 5 >> sonuc.jsonl
  kill $SUNUCU
  wait $SUNUCU 2>/dev/null
done
for H in 900 1300 2100; do
  node kutuphane/sunucu.mjs 8853 0 & SUNUCU=$!
  sleep 1.2
  node kutuphane/olc.mjs 8853 0 $H 0.25 2 >> sonuc.jsonl
  kill $SUNUCU
  wait $SUNUCU 2>/dev/null
done
node kutuphane/analiz.mjs sonuc.jsonl
ayni iki kosum, yedi olcut; esik 20 ms (IO1), ornek 8494 / 8492
olcut     dizinsiz(ms)  karar   dizinli(ms)  karar   dizinsiz/dizinli
ortalama          9,04  gecti         1,87  gecti              4,84
p50               8,66  gecti         1,64  gecti              5,28
p90              16,09  gecti         3,02  gecti              5,33
p95              18,33  gecti         3,54  gecti              5,18
p99              25,35  kaldi         7,77  gecti              3,26
p99,9            98,38  kaldi        15,58  gecti              6,31
en buyuk        103,88  kaldi        16,05  gecti              6,47

ornek sayisi taramasi: 200 alt ornek, tohum 20260731; tam ornekte p99 dizinsiz 25,35 ms, dizinli 7,77 ms
ornek  p99 en kucuk  p99 en buyuk  yayilim kat  yanlis gecme/200  yanlis kalma/200
   20        13,03         38,18         2,93                86                 0
   50        17,62         38,18         2,17                15                 0
  100        21,68         38,18         1,76                 0                 0
  500        27,54         34,50         1,25                 0                 0
 2000        27,54         27,54         1,00                 0                 0

verim-gecikme: dizinsiz yapi, ayni karisim, dort hedef hiz
hedef hiz  ulasilan/s  ulasilan/hedef  p95(ms)  karar
      900      896,85           0,996    11,31  gecti
     1300     1291,61           0,994    13,89  gecti
     1700     1695,88           0,998    18,33  gecti
     2100     1470,45           0,700  1381,94  kaldi

Ortalamanın Gizlediği

Birinci tablo tek bir koşumdan yedi sayı çıkarıyor ve yedisi aynı yapı için iki ayrı karar veriyor. Bu koşumda ortalama 9,04, ortanca 8,66, p90 16,09 ve p95 18,33 eşiğin altında; p99 25,35, p99,9 98,38 ve en büyük 103,88 üstünde. Kod aynı, koşum aynı, örnek aynı; değişen tek şey dağılımdan hangi noktanın okunduğu. Sayılar ölçüm sınıfındadır ve bu makineye bağlıdır; kesme noktasının hangi ölçüt çiftinin arasına düştüğü koşumdan koşuma oynar — başka bir koşumda p95 de eşiğin üstüne çıkabilir. Koşumdan bağımsız olan, sıranın kendisidir: kuyruğa doğru gidildikçe okunan değer büyür ve dizinsiz yapının sağlam yapıya oranı artar.

Ortalamanın niçin geçtiği son sütunda görünüyor. Bu koşumda isteklerin dörtte biri ödünç yoluna gidiyor ve tam tarama yapıyor; kalan dörtte üçü ucuz aramalar. Ortalama iki kümeyi birbirine karıştırır ve pahalı olanın ağırlığını dörtte bire indirir. Kuyruk yüzdelikleri karıştırmaz: p99 tanımı gereği yalnız en yavaş yüzde birde durur ve orada dizinsiz yapı sağlam yapının bu koşumda 3,26 katıdır, p99,9’da 6,31 katı. Katın tam değeri koşuma bağlıdır; koşumdan bağımsız olan, oranın kuyruğa doğru gidildikçe büyümesidir.

Yakalanan kusur sınıfının adı kuyruk gecikmesidir. Sağlam yapı yedi ölçütün yedisinde de geçiyor, dolayısıyla bu koşumda p99’un yanlış kalma üretmediği de görülüyor: ölçütü sıkılaştırmak sağlam yapıyı kırmızıya döndürmedi.

Bir Yüzdelik Kaç Örneğe Dayanır

İkinci tablo aynı gecikmelerden alt örnekler çekiyor ve her boyda p99’u yeniden hesaplıyor. Tam örnekte p99 25,35 milisaniye; yirmi örneklik alt örneklerde aynı sayı 13,03 ile 38,18 arasında geziniyor, yani 2,93 kat yayılım. Kararı bozan yay budur: iki yüz alt örneğin 86’sında p99 eşiğin altında çıkıyor ve koşum yanlış geçme veriyor. Elli örnekte bu sayı 15’e, yüz örnekte sıfıra iniyor.

Sıfırın yüz örnekte gelmesi rastlantı değil, IO5’in kendisidir: p99’un dışladığı pay yüzde bir olduğu için yüz örnekten azında o paya tek bir istek bile düşmeyebilir ve okunan değer dağılımın daha alçak bir yerinden gelir. Beş yüz ve iki bin örnekte yayılım 1,25 ve 1,00 kata iniyor. Yanlış kalma sütunu bütün boylarda sıfır: sağlam yapının p99’u eşikten o kadar uzak ki alt örnekleme onu eşiğin üstüne çıkaramıyor. Örnek sayısı yalnız yanlış geçme tarafını bozuyor — az örnek, hep iyimser bir sonuç üretir.

Buradan çıkan kural koşum süresine bakar: hedef hız 1.700 istek/s ise p99 için gereken yüz örnek 0,06 saniyede toplanır, ama tepe saatin dışında 10 istek/s ile koşan bir ortamda aynı sayı 10 saniye ister. p99,9 için bu süre bin katına çıkar.

Verim Okunmadan Gecikme Okunmaz

Üçüncü tablo aynı yapıyı dört hedef hızda koşturuyor. İlk üç satırda ulaşılan hız hedefin 0,994 ile 0,998’i arasında; p95 sırasıyla 11,31, 13,89 ve 18,33 milisaniye. Bu üç sayı karşılaştırılabilir, çünkü üçü de istenen yükün altında ölçüldü.

Dördüncü satır karşılaştırılabilir değildir. Hedef 2.100 istek/s, ulaşılan 1.470,45 — yani hedefin 0,700’ü. p95 1.381,94 milisaniye görünüyor ama bu sayı 2.100 istek/s’in p95’i değil, sistemin kabul edebildiği 1.470 istek/s’in p95’idir; saniyede 630 istek hiç karşılanmadı. Yalnız gecikme sütununu taşıyan bir rapor bu satırı “yavaş” diye okur, oysa söylediği şey başkadır: sistem istenen yüke ulaşamadı. Gecikme ile verim tek bir çift olarak okunur; ulaşılan hız raporlanmayan bir gecikme sayısı hangi yüke ait olduğunu söylemez.

Bedel ve Kaçan Sınıf

Bedelin koşuma bağlı yanı ortama bağlıdır: beş koşum bu makinede yarım dakikanın altında sürdü. Koşumdan bağımsız yanı sayılır — iki analiz koşumu 8.494 ve 8.492 gecikme değeri biriktirdi, alt örnek taraması beş boyda iki yüzer kez, yani 1.000 yeniden hesap yaptı ve toplam 536.000 örnek çekti. Kaçan sınıf da ölçüldü: ortalama ve ortanca bu kusuru yedi ölçütün dördünde geçirdi, yani bir ölçüt kümesinin sınırı da yanlış geçme sayısıyla yazılır.

Özet

  • Ölçüt bir seçimdir: aynı koşumda ortalama 9,04 ve p50 8,66 eşiği geçerken p99 25,35 ve p99,9 98,38 kalıyor.
  • Ortalama pahalı ile ucuz isteği karıştırır; kuyruk yüzdeliği karıştırmaz ve iki yapıyı p99’da 3,26, p99,9’da 6,31 kat ayırır.
  • Yüzdelik örnek sayısına duyarlıdır: yirmi örnekte p99 tahmini 2,93 kat yayılıyor ve 200 alt örneğin 86’sı yanlış geçme veriyor; yüz örnekte bu sayı sıfırlanıyor.
  • IO5 bunu bir alt sınıra bağlar: bir yüzdelik en az 1/(1p)1/(1-p) örnek ister, yani p99 için 100, p99,9 için 1.000. Az örnek yalnız iyimser yönde yanılır.
  • Gecikme verimle birlikte okunur: 2.100 istek/s hedefinde ulaşılan hız 0,700’e düştüğü için 1.381,94 milisaniyelik p95 istenen yükün değil, kabul edilen yükün sayısıdır.

Sonraki Adım

Ölçüt seçildi ve eşik kırmızıya döndü. Rapor artık şunu söylüyor: p99 25,35 milisaniye, eşik 20, kaldı. Bu cümle bir kusuru bildirir ama nerede olduğunu söylemez. Aynı sayı uygulama katmanındaki bir hesaptan, veritabanındaki bir taramadan ya da yanıtın büyüklüğünden doğmuş olabilir ve üçünün düzeltmesi birbirinden başkadır. Sonraki ders bu atfetmeyi ele alır: bir yük testi sonucu hangi ölçümle bir katmana bağlanır, aynı belirtiyi veren iki katman nasıl ayrılır ve atfetme ne zaman yanlıştı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