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 ö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 ö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.