İçeriğe geç
academia.sh

Ders 03 / 18

Sorgu ve Toplulaştırma

Aynı soru dört toplulaştırmayla yanıtlanır ve yanıtın değiştiği gösterilir: ortalama gizler, yüzdelik gösterir, toplam yanıltır, sayı ile oran farklı servisi işaret eder. Önceden toplulaştırmanın geri getirilemez biçimde kaybettiği ayrıntı ile kazandırdığı sorgu maliyeti aynı tabloda sayılır.

İçindekiler

Bir önceki ders seri kümesinin hangi ayrımı sakladığını ölçtü. Ama hiçbir soru serinin kendisiyle yanıtlanmaz: bir seri yüz binlerce sayıdır ve yanıt tek bir sayı olmak zorundadır. Araya toplulaştırma girer — ortalama, yüzdelik, en büyük, toplam — ve bu ders o adımın kararsız olduğunu gösterir: aynı veriden, aynı soruya, farklı toplulaştırmalarla farklı yanıtlar çıkar.

İkinci ölçü önceden toplulaştırmadır. Ham ölçümü saklamak yerine dakikalık özet saklamak sorgu maliyetini iki mertebe düşürür ve karşılığında bir ayrıntıyı geri getirilemez biçimde siler. Bu dersin ölçüsü, o iki tarafı aynı tabloda saymaktır.

Ölçüm yine bölgesel ölçüm ağı üzerindedir ve kurgudur; gerçek trafik koşturulmaz, sayılan şey node ile üretilmiş bir modeldir.

OG13. Dört servisin istek hızı ve gecikme profili kurgudur ve önceden yazılıdır. OG14. Ölçümler kendi üretecimizle üretilir, tohum 20260902; bir saatlik pencerede 90.000 istek. OG15. Faturalama servisinin isteklerinin yüzde altısı kilit bekler ve 950–1100 ms ek gecikme alır; öteki üç servisin dağılımı düzgündür. Doğrulayıcıya ayrıca 90 saniye süren kurgu bir bozulma eklenir (+900 ms) ve olaysız profil değerleri eşik tabanı sayılır. OG16. Önceden toplulaştırma dakika başına dört alan tutar: adet, toplam, en büyük ve dakikanın p95 değeri. OG17. Özetten hesaplanan genel p95, dakika p95’lerinin istek sayısıyla ağırlıklı ortalamasıdır — yüzdeliklerin birleştirilebileceği varsayımı bilerek uygulanır. OG18. Süre ölçümü ortama bağlıdır; çıktıya yalnız bant denetiminin sonucu basılır, ham ölçüm basılmaz.

Ham Ölçüm Kümesi

// olcum.mjs — bir saatlik istek olcumleri kendi uretecimizle uretilir; profiller kurgudur.
export const uretec = (t) => () => (t = (t * 1103515245 + 12345) % 2147483648) / 2147483648;
export const PENCERE = 3600;
export const PROFIL = {                              // servis: [istek/sn, taban, yayilim, kuyruk payi, kuyruk ms, hata orani]
  "okuma-toplayici": [12, 150, 110, 0, 0, 0.012],
  dogrulayici: [8, 90, 40, 0, 0, 0.006],
  faturalama: [3, 40, 30, 0.06, 950, 0.035],
  "is-emri": [2, 200, 40, 0, 0, 0.009],
};
export const olcumler = (tohum = 20260902) => {
  const rast = uretec(tohum), o = {};
  for (const [servis, [hiz, taban, yayilim, pay, kuyruk, hata]] of Object.entries(PROFIL)) {
    const d = [];
    for (let sn = 0; sn < PENCERE; sn++)
      for (let i = 0; i < hiz; i++) {
        const kuyruklu = rast() < pay;
        d.push({ sn, ms: Math.round(taban + rast() * yayilim + (kuyruklu ? kuyruk + rast() * 150 : 0)),
          hata: rast() < hata });
      }
    o[servis] = d;
  }
  return o;
};
export const yuzdelik = (a, p) => {
  const s = [...a].sort((x, y) => x - y);
  return s[Math.min(s.length - 1, Math.ceil(p * s.length) - 1)];
};
export const enBuyukAd = (o) => Object.entries(o).sort((a, b) => b[1] - a[1])[0][0];

Profiller birbirinden yalnız ortalamayla ayrılmaz. Okuma toplayıcı yüksek hacimli ve düzgün dağılımlıdır; faturalama düşük hacimli ve kuyrukludur — isteklerinin yüzde altısı bir saniyenin üstünde biter. Bu iki biçim, gerçek işletimde en sık karşılaşılan iki gecikme desenidir ve aralarındaki farkı hangi toplulaştırmanın gördüğü baştan belli değildir.

Aynı Soru, Dört Toplulaştırma

// sorgu.mjs — ayni soru farkli toplulastirmalarla yanitlanir; onceden toplulastirmanin bedeli sayilir.
import { PROFIL, PENCERE, olcumler, yuzdelik, enBuyukAd } from "./olcum.mjs";

const ham = olcumler();
const servisler = Object.keys(PROFIL);
const toplamIstek = servisler.reduce((t, s) => t + ham[s].length, 0);
console.log(`bir saatlik olcum (kurgu model): ${toplamIstek.toLocaleString("tr-TR")} istek, ${servisler.length} servis`);
console.log("\nS1 'hangi servis yavasladi' — toplulastirmaya gore yanit");
console.log("toplulastirma   okuma-toplayici  dogrulayici  faturalama  is-emri   yanit");
const OLCU = [["ortalama ms", (d) => d.reduce((t, x) => t + x.ms, 0) / d.length],
  ["p95 ms", (d) => yuzdelik(d.map((x) => x.ms), 0.95)],
  ["en buyuk ms", (d) => Math.max(...d.map((x) => x.ms))],
  ["toplam sn", (d) => d.reduce((t, x) => t + x.ms, 0) / 1000]];
for (const [ad, f] of OLCU) {
  const v = Object.fromEntries(servisler.map((s) => [s, f(ham[s])]));
  console.log(`${ad.padEnd(14)}` + servisler.map((s, i) =>
    v[s].toFixed(0).padStart([17, 13, 12, 9][i])).join("") + `   ${enBuyukAd(v)}`);
}

console.log("\nS10 'hangi hata kodu artti' — sayi mi oran mi");
const hataSayi = Object.fromEntries(servisler.map((s) => [s, ham[s].filter((x) => x.hata).length]));
const hataOran = Object.fromEntries(servisler.map((s) => [s, 100 * hataSayi[s] / ham[s].length]));
console.log("olcu            " + servisler.map((s, i) => s.padStart([17, 13, 12, 9][i])).join("") + "   yanit");
console.log("hata sayisi   " + servisler.map((s, i) => String(hataSayi[s]).padStart([19, 13, 12, 9][i])).join("") +
  `   ${enBuyukAd(hataSayi)}`);
console.log("hata orani %  " + servisler.map((s, i) => hataOran[s].toFixed(2).padStart([19, 13, 12, 9][i])).join("") +
  `   ${enBuyukAd(hataOran)}`);
bir saatlik olcum (kurgu model): 90.000 istek, 4 servis

S1 'hangi servis yavasladi' — toplulastirmaya gore yanit
toplulastirma   okuma-toplayici  dogrulayici  faturalama  is-emri   yanit
ortalama ms                 204          110         125      220   is-emri
p95 ms                      255          128        1042      238   faturalama
en buyuk ms                 260          130        1166      240   faturalama
toplam sn                  8834         3162        1350     1582   okuma-toplayici

S10 'hangi hata kodu artti' — sayi mi oran mi
olcu              okuma-toplayici  dogrulayici  faturalama  is-emri   yanit
hata sayisi                   523          168         428       66   okuma-toplayici
hata orani %                 1.21         0.58        3.96     0.92   faturalama

Tek bir soru, dört farklı yanıt üretecek üç ayrı toplulaştırma. Sayılar aynı 90.000 isteğe aittir ve hiçbiri yanlış değildir.

Ortalama gizler. Faturalamanın ortalaması 125 milisaniyedir ve dört servisin en düşük ikincisidir. Kuyrukta bekleyen yüzde altılık dilim ortalamayı yalnız yetmiş milisaniye yukarı çeker, çünkü ortalama uç değerleri hacme böler. Ortalamaya bakan bir gösterge faturalamayı hiç göstermez.

Yüzdelik gösterir. Aynı servisin p95 değeri 1042 milisaniyedir — ötekilerin dört katı. Kuyruk p95’te saklanamaz, çünkü yüzde altılık dilim yüzde beşlik eşiğin üstündedir. Bir istekten yirmisi saniyeyi aşıyorsa bu ancak yüzdelikte görünür.

Toplam yanıltır. Toplam süreye bakıldığında yanıt okuma toplayıcıdır: 8834 saniye. Ama bu sayının söylediği şey gecikme değil, hacimdir — okuma toplayıcı saatte 43.200 istek alır, faturalama 10.800. Toplam, hızı ölçmek isterken iş yükünü ölçer. Kapasite sorusunda doğru, gecikme sorusunda yanıltıcıdır.

Hata tablosu aynı ayrımı ikinci kez kurar. Hata sayısı okuma toplayıcıyı işaret eder (523), hata oranı faturalamayı (%3,96). İkisi de doğrudur ve iki farklı soruyu yanıtlar: “en çok hangi servis hata üretiyor” ile “hangi servis en bozuk”. Nöbetteki kişiye yanlış olanı gösterildiğinde inceleme, hata oranı üç kat düşük olan servisin günlüklerinde başlar.

Sorgu Penceresi Aynı Şeyi Yapar

Toplulaştırma yalnız hangi işlevin uygulandığı değil, hangi aralığa uygulandığıdır. Aynı ortalama bir dakikaya ve bir saate uygulandığında farklı şeyler ölçer. Aşağıdaki blok doğrulayıcıya kısa bir bozulma yerleştirir ve aynı iki toplulaştırmayı dört pencerede sorar.

// pencere.mjs — sorgu penceresi degistiginde ayni soru baska yanit verir.
import { PROFIL, PENCERE, olcumler, yuzdelik } from "./olcum.mjs";

const ham = olcumler();
const OLAY = { servis: "dogrulayici", bas: 1500, sure: 90, ek: 900 };   // kurgu bozulma
for (const x of ham[OLAY.servis])
  if (x.sn >= OLAY.bas && x.sn < OLAY.bas + OLAY.sure) x.ms += OLAY.ek;
const d = ham[OLAY.servis];
const taban = { ort: 110, p95: 128 };                    // olaysiz profilin degerleri
console.log(`${OLAY.servis} servisinde ${OLAY.sure} sn suren +${OLAY.ek} ms bozulma` +
  ` (saatin %${(100 * OLAY.sure / PENCERE).toFixed(1)}'i); esik taban degerinin iki kati`);
console.log("sorgu penceresi  pencere  en yuksek ort ms  esigi asan  en yuksek p95 ms  esigi asan  fark etme");
for (const w of [60, 300, 900, 3600]) {
  const say = PENCERE / w, ort = [], p95 = [];
  for (let i = 0; i < say; i++) {
    const g = d.filter((x) => x.sn >= i * w && x.sn < (i + 1) * w).map((x) => x.ms);
    ort.push(g.reduce((a, b) => a + b, 0) / g.length); p95.push(yuzdelik(g, 0.95));
  }
  console.log(`${String(`${w / 60} dk`).padEnd(15)}${String(say).padStart(8)}` +
    `${Math.max(...ort).toFixed(0).padStart(18)}${String(`${ort.filter((v) => v > 2 * taban.ort).length}/${say}`).padStart(12)}` +
    `${String(Math.max(...p95)).padStart(18)}${String(`${p95.filter((v) => v > 2 * taban.p95).length}/${say}`).padStart(12)}` +
    `${String(`${w / 60} dk`).padStart(11)}`);
}
console.log(`\ncozunurluk basina kayit (bir saat, dort servis): ` +
  [60, 300, 900, 3600].map((w) => `${w / 60} dk ${4 * (PENCERE / w) * 4}`).join("   "));
dogrulayici servisinde 90 sn suren +900 ms bozulma (saatin %2.5'i); esik taban degerinin iki kati
sorgu penceresi  pencere  en yuksek ort ms  esigi asan  en yuksek p95 ms  esigi asan  fark etme
1 dk                 60              1009        2/60              1028        2/60       1 dk
5 dk                 12               380        1/12              1023        1/12       5 dk
15 dk                 4               200         0/4              1008         1/4      15 dk
60 dk                 1               132         0/1               129         0/1      60 dk

cozunurluk basina kayit (bir saat, dort servis): 1 dk 960   5 dk 192   15 dk 64   60 dk 16

Bozulma saatin yüzde iki buçuğudur ve dört pencerede dört farklı görünür. Bir dakikalık pencerede hem ortalama hem yüzdelik bin milisaniyenin üstüne çıkar ve iki pencere eşiği aşar. Beş dakikada ortalama 380’e iner ama hâlâ eşiğin üstündedir. On beş dakikada ortalama ölür: en yüksek değer 200 milisaniyedir ve iki yüz yirmilik eşiğin altında kalır — hiçbir pencere işaretlenmez. Aynı pencerede yüzdelik hâlâ 1008 milisaniye görür ve bir pencere işaretler. Bir saatte ikisi de kaybolur: ortalama 132, yüzdelik 129 milisaniye — olayın hiçbir izi yoktur.

Kayıp sayısı ile bedel aynı satırdadır. Bir dakikalık çözünürlük saatte 960, bir saatlik çözünürlük 16 kayıt tutar — altmış kat fark. Fark etme süresi de pencerenin kendisidir: bir pencere kapanmadan sorgulanamaz, dolayısıyla on beş dakikalık çözünürlük en iyi durumda on beş dakikalık bir gecikme demektir. Bozulma doksan saniye sürüyorsa o on beş dakika olayın tamamının bitmesinden sonrasına düşer.

Önceden Toplulaştırmanın İki Yüzü

Ham ölçümü istek başına saklamak pahalıdır. Yaygın karşılık dakikalık özet tutmaktır: her dakika için adet, toplam, en büyük ve dakikanın p95 değeri. Aşağıdaki blok özeti gerçekten üretir ve aynı soruları iki depoya sorar.

// onagg.mjs — onceden toplulastirmanin kaybettigi ayrinti ve kazandirdigi sorgu maliyeti.
import { PROFIL, olcumler, yuzdelik } from "./olcum.mjs";

const ham = olcumler(), servisler = Object.keys(PROFIL);
const ozet = {};                                     // dakikalik ozet: adet, toplam, en buyuk, dakika p95
for (const s of servisler) {
  ozet[s] = Array.from({ length: 60 }, (_, d) => {
    const g = ham[s].filter((x) => Math.floor(x.sn / 60) === d).map((x) => x.ms);
    return { adet: g.length, toplam: g.reduce((a, b) => a + b, 0), enBuyuk: Math.max(...g), p95: yuzdelik(g, 0.95) };
  });
}
const hamHepsi = servisler.flatMap((s) => ham[s].map((x) => x.ms));
const hamKayit = hamHepsi.length, ozetKayit = servisler.length * 60 * 4;
const hamP95 = yuzdelik(hamHepsi, 0.95);
const ozetP95 = servisler.reduce((t, s) => t + ozet[s].reduce((a, d) => a + d.p95 * d.adet, 0), 0) / hamKayit;
const hamOrt = hamHepsi.reduce((a, b) => a + b, 0) / hamKayit;
const ozetOrt = servisler.reduce((t, s) => t + ozet[s].reduce((a, d) => a + d.toplam, 0), 0) / hamKayit;
const sapma = (o, h) => `%${(100 * Math.abs(o - h) / h).toFixed(1)}`;

console.log(`ham ${hamKayit.toLocaleString("tr-TR")} kayit, dakikalik ozet ${ozetKayit} kayit` +
  ` (${(hamKayit / ozetKayit).toFixed(0)} kat kucuk)`);
console.log("\nsoru                        ham yanit    ozetten yanit   sapma");
const SATIR = [["genel ortalama ms", hamOrt.toFixed(1), ozetOrt.toFixed(1), sapma(ozetOrt, hamOrt)],
  ["genel p95 ms", String(hamP95), ozetP95.toFixed(1), sapma(ozetP95, hamP95)],
  ["en buyuk ms", String(Math.max(...hamHepsi)),
    String(Math.max(...servisler.flatMap((s) => ozet[s].map((d) => d.enBuyuk)))), "%0.0"],
  ["1 sn ustu istek", String(hamHepsi.filter((x) => x > 1000).length), "hesaplanamaz", "-"]];
for (const [a, h, o, s] of SATIR)
  console.log(`${a.padEnd(26)}${h.padStart(11)}${o.padStart(16)}${s.padStart(8)}`);

// sorgu maliyeti: ayni p95 sorgusu iki depoda TEKRAR kez kosturulur
const TEKRAR = 40;
const beklenenOran = hamKayit / ozetKayit;
const olc = (f) => { const b = process.hrtime.bigint(); for (let i = 0; i < TEKRAR; i++) f(); return Number(process.hrtime.bigint() - b); };
const hamSure = olc(() => servisler.map((s) => yuzdelik(ham[s].map((x) => x.ms), 0.95)));
const ozetSure = olc(() => servisler.map((s) => yuzdelik(ozet[s].map((d) => d.p95), 0.95)));
const olculenOran = hamSure / ozetSure;
console.log(`\np95 sorgusu ${TEKRAR} kez kosturuldu`);
console.log(`  taranan kayit          ham ${(hamKayit * TEKRAR).toLocaleString("tr-TR")}` +
  `   ozet ${(ozetKayit * TEKRAR).toLocaleString("tr-TR")}   beklenen oran ${beklenenOran.toFixed(0)} kat`);
console.log(`  olculen sure orani 5 ile 20000 kat arasinda mi: ${olculenOran > 5 && olculenOran < 20000 ? "evet" : "hayir"}`);
ham 90.000 kayit, dakikalik ozet 960 kayit (94 kat kucuk)

soru                        ham yanit    ozetten yanit   sapma
genel ortalama ms               165.9           165.9    %0.0
genel p95 ms                      251           288.1   %14.8
en buyuk ms                      1166            1166    %0.0
1 sn ustu istek                   729    hesaplanamaz       -

p95 sorgusu 40 kez kosturuldu
  taranan kayit          ham 3.600.000   ozet 38.400   beklenen oran 94 kat
  olculen sure orani 5 ile 20000 kat arasinda mi: evet

Tablo üç ayrı davranışı yan yana koyar. Ortalama birebir korunur: sapma sıfırdır, çünkü ortalama toplam ve adetten yeniden kurulabilir — dakikalık toplamların toplamı saatin toplamıdır. En büyük değer de korunur, aynı nedenle: en büyüklerin en büyüğü gerçek en büyüktür.

Yüzdelik korunmaz. Genel p95 hamda 251, özetten 288 milisaniyedir; sapma yüzde 14,8. Nedeni tek cümledir: yüzdeliklerin ortalaması yüzdelik değildir. Dakikalık p95 değerleri her dakikanın kendi dağılımının üst dilimidir; bunları istek sayısıyla ağırlıklandırıp toplamak, saatin dağılımının üst dilimini vermez. Yön de rastlantı değildir — bu veri kümesinde özet yukarı sapar, çünkü faturalamanın kuyruğu her dakikanın p95’ini yukarı çeker ve o dakikalar toplamda seyrelmez. Başka bir veri kümesinde aynı yöntem aşağı sapabilir; kestirilebilir olan tek şey sapmanın varlığıdır.

Son satır tümüyle kayıptır. “Kaç istek bir saniyeyi aştı” sorusunun hamda yanıtı 729’dur; özette yanıt yoktur ve yaklaşık bir yanıt da yoktur, çünkü eşiğin üstündeki istek sayısı özetin dört alanının hiçbirinde durmaz. Ayrıntı silinmiştir ve geri getirilemez. Bunu görünür kılan şey de yoktur: özet üzerinde bu sorgu yazıldığında depo hata vermez, yalnız sorgunun yazılamayacağı anlaşılır.

Kazanç tarafı da aynı tablodadır. Aynı p95 sorgusu ham depoda 3,6 milyon kayıt tarar, özet depoda 38.400 — 94 kat az. Süre ölçümü ortama bağlıdır ve buraya basılmaz; ölçülen oranın beklenen mertebeyle uyumlu olup olmadığı bant denetiminden geçirilir ve sonuç evet yazılır.

Karar iki sayının çarpışmasındadır: doksan dört kat ucuz sorgu, yüzde on beş sapmış bir yüzdelik ve tümüyle kaybolmuş bir eşik sorusu. Özet, ortalama ve en büyük soran gösterge tabloları için bedavaya yakındır; yüzdelik ve eşik soran uyarı kuralları için yanlış bir tabandır.

Özet

  • Aynı soru dört toplulaştırmayla üç farklı yanıt verir: ortalama is-emri, p95 ve en büyük faturalama, toplam okuma-toplayici. Hiçbiri yanlış değildir; her biri başka bir şeyi ölçer.
  • Ortalama uç dilimi hacme böldüğü için gizler (faturalama 125 ms), yüzdelik gösterir (1042 ms); toplam gecikme yerine hacmi ölçer.
  • Hata sayısı ile hata oranı farklı servisi işaret eder: 523 hata okuma-toplayici, %3,96 oran faturalama.
  • Sorgu penceresi de bir toplulaştırmadır: 90 saniyelik bir bozulma 1 dakikalık pencerede hem ortalamada hem yüzdelikte görünür, 15 dakikada yalnız yüzdelikte, 60 dakikada hiçbirinde. Çözünürlük 960 kayıttan 16 kayda inerken fark etme süresi 1 dakikadan 60 dakikaya çıkar.
  • Önceden toplulaştırma ortalamayı ve en büyüğü birebir korur (%0,0), p95’i %14,8 saptırır, eşik üstü istek sayısını tümüyle siler — 729 istek özette hesaplanamaz.
  • Aynı sorgu ham depoda 3,6 milyon, özet depoda 38.400 kayıt tarar: 94 kat ucuz. Kazanç ile kayıp aynı kararın iki yüzüdür.

Sonraki Adım

Buraya kadar her yanıt bir sayıydı: kaç milisaniye, kaç istek, kaç hata. Ama tablodaki hiçbir sayı neden sorusunu yanıtlamaz. Faturalamanın p95’inin 1042 milisaniye olduğu bilinir; hangi kilidin, hangi sorgunun, hangi bölgenin okumasında beklendiği bilinmez. O bilgi ölçütte değil, sürecin kendi yazdığı metinde durur. Bir sonraki ders günlük akışını ele alır ve o metnin toplama hattından geçerken kaç satırının kaybolduğunu katman katman sayar.

İ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