Ders 12 / 14
Ölçüt Toplama Sistemi
Tek tek hiçbir kaydın anlamlı olmadığı vaka: zaman serisi yazma yolunun örnek hızıyla bütçelenmesi, saklama basamaklarının toplam bayta etkisi ve seyreltmenin hangi soruyu bozmadan, hangisini yüzde yüz yanlış yanıtladığının gerçek bir motorda ölçülmesi.
İçindekiler
Önceki iki vakada üretilen her bayt saklanmaya değerdi: bir nesne yazıldığı gibi tutuldu, bir video parçası üretildikten sonra defalarca okundu. Bu vaka o varsayımı kaldırır. Burada kayıtlar saniyede yüz binlerce üretilir, tek tek hiçbiri anlamlı değildir ve hepsini süresiz saklamak ne mümkündür ne de gereklidir.
Sistem bir ölçüt toplama sistemidir: düğümler düzenli aralıklarla sayısal örnek gönderir, örnekler zaman serisi olarak yazılır ve sorular hep bir zaman aralığı üzerinden sorulur. Ölçülecek karar seyreltme (downsampling) ile toplulaştırmanın (rollup) ikili etkisidir: saklanan bayt ve taranan satır düşer, ama toplulaştırılmış veriden okunan yanıt ham veriden okunana eşit değildir. Bu fark soru türüne göre değişir ve tasarım o farkı bilerek seçer.
Hangi ölçütün toplanmaya değer olduğu bu vakanın konusu değil; o soru Başarım Karşıt Kalıpları ve İzleme kursunun Enstrümantasyon Tasarımı dersinde kuruldu. Burada ölçüt kümesi verilidir ve tasarım onu taşıma işidir.
Kısıtlar
İşlevsel gereksinim: örnek kabul etme, seri ve zaman aralığına göre okuma, seri üzerinde ortalama / en büyük / yüzdelik / eşik aşımı sorularını yanıtlama ve saklama süresi dolan veriyi düşürme.
İşlevsel olmayan gereksinim, sayıyla: yazma yolu tepe 480.000 örnek/s’yi kayıpsız kabul eder; toplam saklanan bayt 40 TB’ı geçmez; altı saatlik bir gösterge tablosu sorgusu seri başına 400’den az satır tarar; son 7 gün içinde yüzdelik ve eşik aşımı soruları hatasız yanıtlanır.
Kapsam daraltması: uyarı kuralları, gösterge tablosu arayüzü, izleme kategorileri ve dağıtık iz toplama tasarlanmaz — bunlar Başarım Karşıt Kalıpları ve İzleme kursunun konusudur.
Varsayımlar
| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| OT1 | izlenen düğüm | 20.000 | işletilen süreçlerin toplamı |
| OT2 | düğüm başına seri | 120 | süreç, kaynak ve uç başına birkaç ölçüt |
| OT3 | örnekleme aralığı | 10 s | ajanın gönderme sıklığı |
| OT4 | ham örnek kaydı | 26 bayt | seri kimliği, zaman ve değer |
| OT5 | özet kaydı | 34 bayt | kova, adet, toplam ve en büyük |
| OT6 | tepe çarpanı | 2 | ölçüt akışı düzenlidir, tepesi sığdır |
| OT7 | ham saklama | 7 gün | olay incelemesi bir haftaya bakar |
| OT8 | bir dakikalık özet saklama | 90 gün | çeyrek dönem karşılaştırması |
| OT9 | beş dakikalık özet saklama | 730 gün | kapasite eğilimi iki yıl geriye gider |
| OT10 | gösterge tablosu aralığı | 6 saat | bir vardiyanın penceresi |
OT6 bu vakanın ayırt edici varsayımıdır: tepe çarpanı önceki vakalarda 3’tü, burada 2. Ölçüt akışı kullanıcı davranışına değil ajanın zamanlayıcısına bağlıdır, bu yüzden tepesi sığdır.
Ölçek
// olcut-olcek.mjs — OT tablosundan cikan olcek hesabi ve saklama basamaklarinin toplami const OT = { dugum: 20_000, seriBasinaDugum: 120, aralikSn: 10, hamBayt: 26, ozetBayt: 34, tepe: 2, sorguAralikSaat: 6, hamGun: 7, dakikaGun: 90, besDakikaGun: 730 }; const seri = OT.dugum * OT.seriBasinaDugum; const ornekSn = seri / OT.aralikSn; const gunlukHam = ornekSn * 86_400 * OT.hamBayt; const ozetGunluk = (tanecikSn) => (seri * (86_400 / tanecikSn)) * OT.ozetBayt; for (const [ad, d] of [ ["seri sayisi", seri], ["ortalama ornek/s", ornekSn], ["tepe ornek/s", ornekSn * OT.tepe], ["ham gunluk GB", gunlukHam / 1e9], ["1 dk ozet gunluk GB", ozetGunluk(60) / 1e9], ["5 dk ozet gunluk GB", ozetGunluk(300) / 1e9], ]) console.log(ad.padEnd(24) + d.toFixed(2).padStart(12)); const basamak = [["ham", gunlukHam, OT.hamGun], ["1 dk ozet", ozetGunluk(60), OT.dakikaGun], ["5 dk ozet", ozetGunluk(300), OT.besDakikaGun]]; let toplam = 0; console.log(`\n${"basamak".padEnd(12)}${"gun".padStart(6)}${"saklanan TB".padStart(14)}`); for (const [ad, gun, sure] of basamak) { toplam += (gun * sure) / 1e12; console.log(ad.padEnd(12) + String(sure).padStart(6) + ((gun * sure) / 1e12).toFixed(2).padStart(14)); } const hepsiHam = (gunlukHam * OT.besDakikaGun) / 1e12; console.log(`toplam ${toplam.toFixed(2)} TB; hepsi ham ve ${OT.besDakikaGun} gun tutulsaydi ` + `${hepsiHam.toFixed(2)} TB (x${(hepsiHam / toplam).toFixed(2)})`); console.log(`${OT.sorguAralikSaat} saatlik sorgu seri basina okunan satir: ham ` + `${(OT.sorguAralikSaat * 3600) / OT.aralikSn}, 1 dk ${OT.sorguAralikSaat * 60}, ` + `5 dk ${(OT.sorguAralikSaat * 3600) / 300}`);
seri sayisi 2400000.00 ortalama ornek/s 240000.00 tepe ornek/s 480000.00 ham gunluk GB 539.14 1 dk ozet gunluk GB 117.50 5 dk ozet gunluk GB 23.50 basamak gun saklanan TB ham 7 3.77 1 dk ozet 90 10.58 5 dk ozet 730 17.16 toplam 31.50 TB; hepsi ham ve 730 gun tutulsaydi 393.57 TB (x12.49) 6 saatlik sorgu seri basina okunan satir: ham 2160, 1 dk 360, 5 dk 72
Bu sayılar hesap sınıfındadır. Üçü tasarımı belirliyor. Yazma yolu saniyede 240.000, tepede 480.000 örnek görüyor; bu, bir isteğin bir kayda karşılık geldiği önceki vakalardan iki mertebe yüksektir ve yazma yolunun her adımının örnek başına maliyetini belirleyici yapar. Saklama basamakları toplamı 31,50 TB ile 40 TB kısıtının altında; aynı veri ham tutulsaydı 393,57 TB, yani 12,49 katı olurdu ve kısıt çiğnenirdi. Altı saatlik sorgu bir dakikalık özetten 360 satır okuyor, ham basamaktan 2160; 400 satır kısıtını yalnız özet basamakları karşılıyor.
Seyreltmenin Bedeli
Yukarıdaki tablo yalnız kazancı gösteriyor. Bedel yanıtın kendisindedir ve gerçek bir motorda
ölçülür. Aşağıdaki model bir küme kurmaz; tek bir serinin altı saatini node:sqlite içinde üretir,
iki tanecikte toplulaştırır ve aynı dört soruyu üç biçime birden sorar.
// seyreltme.mjs — node:sqlite ile gercek bir motorda tek serinin ham ve toplulastirilmis // bicimleri. Uretec kendi yazilmistir, tohum gorunur; olculen sey saklanan bayt, taranan satir // ve ayni dort sorunun iki bicimden aldigi yanitin farkidir. Model oldugu yazilir. import { DatabaseSync } from "node:sqlite"; const SURE = 21_600, TOHUM = 20260801, ESIK = 200; // 6 saat, saniyede bir ornek let durum = TOHUM; const rast = () => { durum = (durum * 1103515245 + 12345) % 2147483648; return durum / 2147483648; }; const uret = (t) => { // gunluk dalga + gurultu + ani sicrama const taban = 100 + 40 * Math.sin((2 * Math.PI * t) / 86_400) + (rast() - 0.5) * 30; return taban + (rast() < 0.002 ? 300 : 0); }; const db = new DatabaseSync(":memory:"); db.exec(`CREATE TABLE ham(t INTEGER PRIMARY KEY, deger REAL); CREATE TABLE ozet60(kova INTEGER PRIMARY KEY, adet INTEGER, toplam REAL, enBuyuk REAL); CREATE TABLE ozet300(kova INTEGER PRIMARY KEY, adet INTEGER, toplam REAL, enBuyuk REAL);`); db.exec("BEGIN"); const ekle = db.prepare("INSERT INTO ham VALUES(?,?)"); for (let t = 0; t < SURE; t += 1) ekle.run(t, uret(t)); db.exec("COMMIT"); for (const g of [60, 300]) db.exec(`INSERT INTO ozet${g} SELECT t / ${g}, count(*), sum(deger), max(deger) FROM ham GROUP BY t / ${g}`); const yuzdelik = (a, p) => { const s = [...a].sort((x, y) => x - y); return s[Math.floor(s.length * p)]; }; const hamDeger = db.prepare("SELECT deger FROM ham").all().map((r) => r.deger); const bayt = (t) => db.prepare("SELECT sum(pgsize) AS b FROM dbstat WHERE name = ?").get(t).b; const satir = (t) => db.prepare(`SELECT count(*) AS n FROM ${t}`).get().n; const HAM = { ort: hamDeger.reduce((a, b) => a + b) / SURE, enBuyuk: Math.max(...hamDeger), p95: yuzdelik(hamDeger, 0.95), asan: hamDeger.filter((d) => d > ESIK).length }; console.log(`model: ${SURE} ornek (saniyede bir), tohum ${TOHUM}, esik ${ESIK}\n`); console.log("bicim".padEnd(10) + "satir".padStart(7) + "bayt".padStart(9) + "bayt/satir".padStart(12) + "ortalama".padStart(10) + "en buyuk".padStart(10) + "p95".padStart(9) + "esik ustu sn".padStart(14)); const yaz = (ad, n, b, o, e, p, a) => console.log(ad.padEnd(10) + String(n).padStart(7) + String(b).padStart(9) + (b / n).toFixed(1).padStart(12) + o.toFixed(2).padStart(10) + e.toFixed(2).padStart(10) + p.toFixed(2).padStart(9) + String(a).padStart(14)); yaz("ham", satir("ham"), bayt("ham"), HAM.ort, HAM.enBuyuk, HAM.p95, HAM.asan); const OZET = {}; for (const g of [60, 300]) { const k = db.prepare(`SELECT adet, toplam, enBuyuk FROM ozet${g}`).all(); const kovaOrt = k.map((r) => r.toplam / r.adet); const o = { ort: k.reduce((a, r) => a + r.toplam, 0) / SURE, enBuyuk: Math.max(...k.map((r) => r.enBuyuk)), p95: yuzdelik(kovaOrt, 0.95), asan: kovaOrt.filter((d) => d > ESIK).length * g }; OZET[g] = o; yaz(`${g} sn ozet`, satir(`ozet${g}`), bayt(`ozet${g}`), o.ort, o.enBuyuk, o.p95, o.asan); } console.log(`\nham yanittan sapma (yuzde):`); for (const g of [60, 300]) { const s = (a, b) => (((a - b) / b) * 100).toFixed(2); console.log(` ${g} sn ozet -> ortalama ${s(OZET[g].ort, HAM.ort)}, en buyuk ` + `${s(OZET[g].enBuyuk, HAM.enBuyuk)}, p95 ${s(OZET[g].p95, HAM.p95)}, ` + `esik ustu sure ${s(OZET[g].asan, HAM.asan)}`); } for (const g of [60, 300]) { // ayni soru enBuyuk sutunuyla const n = db.prepare(`SELECT count(*) AS n FROM ozet${g} WHERE enBuyuk > ?`).get(ESIK).n; console.log(` ${g} sn ozet, esik ustu sure enBuyuk ile: ${n * g} sn (ham ${HAM.asan} sn, ` + `x${((n * g) / HAM.asan).toFixed(1)})`); } console.log(`taranan satir orani: 60 sn ozet 1/${SURE / satir("ozet60")}, ` + `300 sn ozet 1/${SURE / satir("ozet300")}; bayt orani 1/` + `${(bayt("ham") / bayt("ozet60")).toFixed(1)} ve 1/${(bayt("ham") / bayt("ozet300")).toFixed(1)}`);
model: 21600 ornek (saniyede bir), tohum 20260801, esik 200 bicim satir bayt bayt/satir ortalama en buyuk p95 esik ustu sn ham 21600 356352 16.5 125.84 450.17 148.84 41 60 sn ozet 360 16384 45.5 125.84 450.17 140.74 0 300 sn ozet 72 4096 56.9 125.84 450.17 139.88 0 ham yanittan sapma (yuzde): 60 sn ozet -> ortalama 0.00, en buyuk 0.00, p95 -5.44, esik ustu sure -100.00 300 sn ozet -> ortalama 0.00, en buyuk 0.00, p95 -6.02, esik ustu sure -100.00 60 sn ozet, esik ustu sure enBuyuk ile: 2400 sn (ham 41 sn, x58.5) 300 sn ozet, esik ustu sure enBuyuk ile: 9600 sn (ham 41 sn, x234.1) taranan satir orani: 60 sn ozet 1/60, 300 sn ozet 1/300; bayt orani 1/21.8 ve 1/87.0
Bu sayılar ölçüm sınıfındadır; değerler bu koşumun tohumuna bağlıdır, sapmaların işareti ve mertebesi bağlı değildir. Bayt sütunu sayfa taneciğine bağlı olduğu için az satırlı tabloda satır başına bayt büyük görünüyor; kıyas için toplam bayt oranına bakılır.
Dört soru üç ayrı davranış gösteriyor. Ortalama ve en büyük hiç bozulmuyor: sapma her iki tanecikte de yüzde 0,00, çünkü özet satırı toplamı, adedi ve en büyüğü taşıyor — bu üçünden ortalama ile en büyük tam olarak yeniden kurulabiliyor. Yüzdelik bozuluyor ama az: p95 bir dakikalık tanecikte yüzde 5,44, beş dakikalıkta yüzde 6,02 düşük çıkıyor, çünkü kova ortalaması kova içindeki dağılımı düzleştiriyor. Eşik aşımı tamamen kayboluyor: kova ortalamasıyla bakıldığında 41 saniyelik aşım 0 saniye görünüyor, yani yüzde 100 hata.
Aynı soruyu en büyük sütunuyla sormak da işe yaramıyor, ters yöne kaçırıyor: bir dakikalık tanecikte 2400 saniye, beş dakikalıkta 9600 saniye çıkıyor — ham yanıtın 58,5 ve 234,1 katı, çünkü kovanın herhangi bir saniyesindeki aşım bütün kovayı aşmış sayıyor. Bir özet satırı, taşıdığı sütunların yanıtladığı soruları yanıtlar; ötekiler için alt sınır ile üst sınır arasında bir belirsizlik bırakır.
Tasarım
Yazma yolu bir ileti aracısı ve rekabet eden tüketiciler üzerine kurulur (Uygulama Katmanı ve Servis Etkileşimi kursunun Kuyruklar ve İş Akışları konusu); aynı konudaki kuyruk tabanlı yük dengeleme tepedeki 480.000 örnek/s’yi yazıcıların gördüğü sabit bir hıza düzleştirir. Ajan tarafında geri basınç (Dayanıklılık ve Güvenilirlik kursunun Arıza Yalıtımı konusu) tampon doluluğuyla ölçülür ve tampon dolduğunda en eski örnek atılır.
Depo bir geniş sütun deposudur (Veri Katmanı Ölçekleme kursunun Veri Dağıtımı konusu, Depo Türleri): satır anahtarı seri kimliği, sütunlar zaman sıralıdır. Parçalama anahtarı seri kimliğidir, düğüm adı değil — düğüm adına göre parçalansaydı 120 serisi tek parçaya düşen bir düğüm sıcak parça yaratırdı. Bölümleme zamana göredir (aynı konu, Bölümleme Stratejileri) ve saklama politikası bölüm düşürmeyle uygulanır: 7 günden eski ham bölüm tek işlemde silinir, satır satır değil.
Toplulaştırma iki somutlaştırılmış görünümdür (aynı konu): tazeleme artımlıdır ve yalnız kapanan kovayı yazar. Parametreler taneciklerdir — 60 saniye ve 300 saniye — ve gerekçeleri OT8 ile OT9’un saklama süreleridir. Kapasite tahmini için okunan basamak beş dakikalıktır; zaman serisinden kaynak tahminine geçişin yöntemi Başarım Karşıt Kalıpları ve İzleme kursunun Kapasite Planlama dersinde kuruldu ve burada tekrarlanmaz, yalnız girdisi bu basamaktan gelir.
Bilerek kullanılmayan kalıp: tekillik anahtarı defteri. Dayanıklılık ve Güvenilirlik kursunun Etkisiz İşlemler dersindeki defter buraya konmaz, çünkü örnek zaten (seri, zaman) çiftiyle adreslenir ve yinelenen yazma aynı hücrenin üstüne aynı değeri yazar; ayrı bir defter örnek başına 26 baytlık kaydın yanına aynı mertebede bir kayıt daha ekler ve saniyede 240.000 kez ödenirdi. İkincisi federasyondur (Veri Dağıtımı konusu): bölünecek ayrı bir işlevsel alan yok, bütün veri tek biçimde ve tek erişim örüntüsüne sahip.
Elenen Alternatifler
Her şeyi ham tutmak yüzdelik ve eşik sorularını her tarihte hatasız yanıtlar. 393,57 TB ister, 40 TB kısıtının on katı; ayrıca altı saatlik sorgu seri başına 2160 satır tarar ve 400 satır kısıtını da çiğner. İki kısıtla birden eleniyor.
Her şeyi beş dakikalık özet tutmak en ucuzudur: toplam 17,16 TB ve sorgu başına 72 satır. Eşik aşımı sorusunu yüzde 100 hatayla (0 saniye) ya da 234,1 kat şişirerek (9600 saniye) yanıtlar, yüzdelikte 6,02 puan sapar. “Son 7 gün içinde hatasız” kısıtıyla eleniyor.
Seçilen üç basamaklı düzen 31,50 TB’da kalıyor, sorgu başına 360 satır tarıyor ve son yedi günde ham veri durduğu için hassas soruları hatasız yanıtlıyor. Hangi kısıt değişirse alternatif kazanır: eşik aşımı ve yüzdelik soruları hiç sorulmuyorsa ham basamağın varlık nedeni kalmaz, 3,77 TB’lik kalem düşer ve yazma yolu doğrudan özete yazabilir; tersine, olay incelemesi bir haftadan eskiye bakıyorsa ham saklama uzatılır ve toplam hızla 40 TB’ı zorlar.
Arıza Davranışı ve Feda Edilen
İleti aracısı düştüğünde ajanlar tamponlarında biriktirir; tampon dolduğunda en eski örneği atmak zarif bozulmadır (Arıza Yalıtımı konusu), çünkü seri kesintisiz kalır ama tanecik geçici olarak kabalaşır — ortalama ve en büyük sorusu yanıtlanmaya devam eder, eşik aşımı yanıtlanamaz. İşin tersi de olur: toplulaştırma işi geri kalırsa gösterge tablosu ham basamaktan okur ve seri başına taranan satır 360’tan 2160’a, yani altı kat artar. Bir parça düğümü düştüğünde yalnız o parçadaki serilerin okuması etkilenir; sorular seri bazında sorulduğu için bulaşma kapsamı bir parçadır.
Feda edilen: yedi günden eski veride yüzdelik ve eşik aşımı soruları güvenilir yanıt almaz. Bir ay önceki bir dalgalanmanın kaç saniye sürdüğü bu tasarımda sorulamaz; alınabilecek yanıt ya 0 ya da gerçeğin 58,5 katıdır.
Özet
- Ölçüt akışı örnek başına maliyeti belirleyici yapar: 2.400.000 seri, saniyede 240.000 örnek, tepede 480.000; tepe çarpanı önceki vakalardaki 3 değil 2, çünkü akış ajanın zamanlayıcısına bağlıdır.
- Saklama basamakları toplamı 31,50 TB; aynı veri ham tutulsaydı 393,57 TB, yani 12,49 katı.
- Toplulaştırma taranan satırı 60 ve 300 kat, saklanan baytı 21,8 ve 87,0 kat düşürüyor.
- Sapma soru türüne bağlıdır: ortalama ve en büyük yüzde 0,00, p95 yüzde 5,44 ile 6,02, eşik aşımı süresi yüzde 100 — aynı soru en büyük sütunuyla sorulduğunda 58,5 ve 234,1 kat şişiyor.
- Bu yüzden ham basamak bir başarım kalemi değil, bir yanıtlanabilirlik kalemidir: yedi günlük ham veri 3,77 TB’a mal olur ve karşılığında hassas soruların yanıtlanabildiği pencereyi tanımlar.
Sonraki Adım
Bu vakada bir kaydı bulmak kolaydı: seri kimliği ile zaman aralığı verildiğinde okunacak satırlar tek bir yerde ve sıralı duruyordu. Sonraki vaka bu kolaylığı kaldırır. Orada kayıtlar iki boyutlu bir düzlemde durur ve soru “şu noktaya yakın olanlar” biçiminde gelir; yakınlığın sıralı bir anahtarı yoktur, çünkü iki boyutu tek bir eksende sıralamanın her yolu bazı komşuları birbirinden uzağa düşürür. Soru şuna dönüşür: böyle bir sorguyu yanıtlamak için kaç aday kayda bakmak gerekir ve bu sayı seçilen hücre boyuna nasıl bağlıdır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.