Ders 13 / 18
Önbelleksiz Tasarım Karşıt Kalıbı
Önbelleğin gerekli olup olmadığının erişim örüntüsünden karara bağlanması: takip sorgusunda önbelleksiz tasarımın depoya 513,89 istek/s bindirmesi, dönem taramasında önbelleğin isabetsiz kalıp istek başına 2,00 ek dokunuş getirmesi, sıcak yazmada 5000 kat tekrarın 0,3001 isabete düşmesi ve toplu taramanın aynı önbellekten geçirilmesinin takip isabetini 0,4263 düşürüp depo erişimini 85.252 artırması.
İçindekiler
Önceki dersin son sayısı rahatsız edici bir soru bıraktı: isabet oranı 0,8286 iken depo, önbellek hiç yokmuş gibi 513,89 istek/s taşıyordu. Buraya kadar her ders bir önbelleğin var olduğunu varsaydı ve yalnız onun nasıl düzenleneceğini tartıştı. Bu ders varsayımı kaldırır ve soruyu ters yönden sorar: önbellek gerçekten gerekli mi.
Sorunun iki yanlış yanıtı vardır ve ikisi de aynı kusurdan doğar. Birincisi önbelleksiz tasarım karşıt kalıbı: erişim örüntüsü tekrarla dolu olduğu hâlde her okumanın depoya gitmesi. İkincisi bunun aynası: tekrar barındırmayan bir yola önbellek konması. İkisi de erişim örüntüsünün ölçülmemesinin sonucudur, bu yüzden bu ders örüntüyü ölçer.
Dört Örüntü
Model süreç içidir: aynı önbellek dört ayrı akışla koşturulur, sayaçlar açıktır. Akışların hepsi K01’in üç akışından türetilmiştir — takip sorgusu, durum olayı yazması ve gün sonu ücretlendirme taraması — ve hızları K01’in hesabından gelir: tepe okuma 416,67 istek/s, toplu tarama 833,33 kayıt/s. Pencere yine 480 saniyedir.
// onbellek/oruntu.mjs — ayni onbellek dort erisim oruntusuyle kosturulur. Onbellek ve // depo birer modul; sayaclar aciktir. Oruntuler K01'in uc akisindan turetilmistir. export const K01 = { tepeOkuma: (2_000_000 * 6 / 86_400) * 3, // 416,67 istek/s tepeYazma: (400_000 * 7 / 86_400) * 3, // 97,22 istek/s topluTarama: (30 * 400_000) / (4 * 3600), // 833,33 kayit/s }; export const PENCERE = 480; // saniye function rastgeleUretec(tohum) { return () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648); } // Takip sorgusu: bir gonderinin sorgulari `obek` uzunlugunda gelir. export function takipAkisi(istek, calismaKumesi = 1000, obek = 10) { const rast = rastgeleUretec(20260730); let sonraki = 1; const etkin = Array.from({ length: calismaKumesi }, () => ({ no: sonraki++, kalan: obek })); const cikti = []; for (let i = 0; i < istek; i++) { const j = Math.floor(rast() * etkin.length); cikti.push(["oku", `takip:${etkin[j].no}`]); if ((etkin[j].kalan -= 1) === 0) etkin[j] = { no: sonraki++, kalan: obek }; } return cikti; } // Ucretlendirme taramasi: her satici-gun kaydi bir kez okunur, sirayla. export function taramaAkisi(kayit) { return Array.from({ length: kayit }, (_, i) => ["oku", `donem:${i + 1}`]); } // Sicak yazma: kucuk bir anahtar uzayina okumadan cok yazma duser. export function sicakYazmaAkisi(islem, anahtar = 40, yazmaPayi = 0.7) { const rast = rastgeleUretec(4242); return Array.from({ length: islem }, () => { const a = `bolge:${Math.floor(rast() * anahtar) + 1}`; return [rast() < yazmaPayi ? "yaz" : "oku", a]; }); } // Iki akisi sirayla ic ice gecirir: her `araN` takip istegi arasina `sokN` tarama istegi. export function karistir(a, b, araN, sokN) { const cikti = []; let i = 0, j = 0; while (i < a.length || j < b.length) { for (let k = 0; k < araN && i < a.length; k++) cikti.push(a[i++]); for (let k = 0; k < sokN && j < b.length; k++) cikti.push(b[j++]); } return cikti; } // atla: bu ad alanindaki anahtarlar onbellege hic ugramaz. export function kosum(islemler, { kapasite, atla = null }) { const m = new Map(); const s = { istek: 0, okuma: 0, isabet: 0, depoOkuma: 0, depoYazma: 0, dokunus: 0, takipIstek: 0, takipIsabet: 0 }; for (const [tur, anahtar] of islemler) { s.istek += 1; const takip = anahtar.startsWith("takip:"); if (takip) s.takipIstek += 1; if (atla !== null && anahtar.startsWith(atla)) { if (tur === "oku") s.depoOkuma += 1; else s.depoYazma += 1; s.dokunus += 1; continue; } if (tur === "yaz") { s.depoYazma += 1; s.dokunus += 2; m.delete(anahtar); continue; } s.okuma += 1; s.dokunus += 1; if (m.has(anahtar)) { s.isabet += 1; if (takip) s.takipIsabet += 1; const d = m.get(anahtar); m.delete(anahtar); m.set(anahtar, d); continue; } s.depoOkuma += 1; s.dokunus += 2; m.set(anahtar, 1); if (m.size > kapasite) m.delete(m.keys().next().value); } return s; }
// onbellek/karar.mjs — dort oruntude onbellegin kazandirdigi ve kaybettirdigi import { K01, PENCERE, takipAkisi, taramaAkisi, sicakYazmaAkisi, karistir, kosum } from "./oruntu.mjs"; const KAPASITE = 2000; const OKUMA = Math.round(K01.tepeOkuma * PENCERE); const TARAMA = Math.round(K01.topluTarama * PENCERE); const takip = takipAkisi(OKUMA); const tarama = taramaAkisi(TARAMA); const sicak = sicakYazmaAkisi(OKUMA); console.log(`pencere ${PENCERE} s: takip sorgusu ${OKUMA}, donem taramasi ${TARAMA} kayit, kapasite ${KAPASITE}`); console.log(`(K01: tepe okuma ${K01.tepeOkuma.toFixed(2)}/s, toplu tarama ${K01.topluTarama.toFixed(2)} kayit/s)\n`); const ORUNTU = [ ["takip sorgusu", takip], ["donem taramasi", tarama], ["sicak yazma", sicak], ["takip + tarama", karistir(takip, tarama, 1, 2)], ]; console.log("oruntu istek okuma tekil anahtar tekrar isabet depo erisimi onbelleksiz fark ek dokunus/istek"); console.log("--------------- ------ ------ -------------- ------- ------- ------------- ----------- ----- -----------------"); for (const [ad, akis] of ORUNTU) { const r = kosum(akis, { kapasite: KAPASITE }); const tekil = new Set(akis.map(([, a]) => a)).size; const depo = r.depoOkuma + r.depoYazma; console.log(`${ad.padEnd(15)} ${String(r.istek).padStart(6)} ${String(r.okuma).padStart(6)} ${String(tekil).padStart(14)} ` + `${(r.istek / tekil).toFixed(2).padStart(7)} ${(r.isabet / r.okuma).toFixed(4).padStart(7)} ` + `${String(depo).padStart(13)} ${String(r.istek).padStart(11)} ${(depo - r.istek).toString().padStart(5)} ` + `${((r.dokunus - r.istek) / r.istek).toFixed(2).padStart(17)}`); } console.log("\ntoplu tarama ayni onbellekten gecerse takip sorgusuna ne olur:"); const birlikte = karistir(takip, tarama, 1, 2); const gecen = kosum(birlikte, { kapasite: KAPASITE }); const atlayan = kosum(birlikte, { kapasite: KAPASITE, atla: "donem:" }); const yalniz = kosum(takip, { kapasite: KAPASITE }); const satir = (ad, r) => { const depo = r.depoOkuma + r.depoYazma; const takipS = K01.tepeOkuma * (1 - r.takipIsabet / r.takipIstek); console.log(`${ad.padEnd(28)} ${(r.takipIsabet / r.takipIstek).toFixed(4).padStart(14)} ` + `${String(depo).padStart(13)} ${(depo / PENCERE).toFixed(2).padStart(15)} ${(takipS + K01.tepeYazma).toFixed(2).padStart(16)}`); }; console.log("durum takip isabeti depo erisimi depo erisimi/s depoya ulasan/s"); console.log("---------------------------- --------------- ------------- --------------- ----------------"); satir("tarama yok", yalniz); satir("tarama onbellekten geciyor", gecen); satir("tarama onbellegi atliyor", atlayan); console.log(`\ntaramanin onbellekten gecmesinin bedeli = ${(gecen.depoOkuma + gecen.depoYazma) - (atlayan.depoOkuma + atlayan.depoYazma)} ek depo erisimi`); console.log(`takip isabetindeki kayip = ${((atlayan.takipIsabet / atlayan.takipIstek) - (gecen.takipIsabet / gecen.takipIstek)).toFixed(4)}`);
pencere 480 s: takip sorgusu 200000, donem taramasi 400000 kayit, kapasite 2000 (K01: tepe okuma 416.67/s, toplu tarama 833.33 kayit/s) oruntu istek okuma tekil anahtar tekrar isabet depo erisimi onbelleksiz fark ek dokunus/istek --------------- ------ ------ -------------- ------- ------- ------------- ----------- ----- ----------------- takip sorgusu 200000 200000 20477 9.77 0.8976 20483 200000 -179517 0.20 donem taramasi 400000 400000 400000 1.00 0.0000 400000 400000 0 2.00 sicak yazma 200000 60078 40 5000.00 0.3001 181968 200000 -18032 1.12 takip + tarama 600000 600000 420477 1.43 0.1571 505735 600000 -94265 1.69 toplu tarama ayni onbellekten gecerse takip sorgusuna ne olur: durum takip isabeti depo erisimi depo erisimi/s depoya ulasan/s ---------------------------- --------------- ------------- --------------- ---------------- tarama yok 0.8976 20483 42.67 139.90 tarama onbellekten geciyor 0.4713 505735 1053.61 317.50 tarama onbellegi atliyor 0.8976 420483 876.01 139.90 taramanin onbellekten gecmesinin bedeli = 85252 ek depo erisimi takip isabetindeki kayip = 0.4263
Bütün sayılar hesap sınıfındadır: belirlenimli akışlar üzerinde sayılmışlardır.
Karşıt Kalıbın Bedeli
İlk satır karşıt kalıbı tanımlıyor. Takip sorgusunda tekrar oranı 9,77: her takip numarası
pencerede ortalama on kez sorulur. Önbelleksiz tasarımda depo 200.000 erişim görür, önbellekli
tasarımda 20.483 — fark 179.517 erişim, yani onda dokuzu gereksizdi. K01’in hesabında bu,
depoya ulasan istek/s satırının 513,89 ile 139,90 arasındaki farkıdır: 3,67 kat.
Önbelleksiz tasarım, deponun bu katsayıyla boyutlandırılmasını gerektirir ve ondan kaçınmanın
bedeli istek başına 0,20 ek dokunuştur.
İkinci satır aynanın öteki yüzü. Dönem taraması 400.000 satıcı-gün kaydını okur ve hepsi farklıdır; tekrar oranı tam olarak 1,00, isabet 0,0000. Önbellekli ve önbelleksiz depo erişimi eşit — fark sıfır. Kazanılan hiçbir şey yokken istek başına 2,00 ek dokunuş ödenir: bir yoklama ve bir yerleştirme. Bu örüntüde önbelleksiz tasarım karşıt kalıp değil, doğru tasarımdır.
Üçüncü satır tekrarın tek başına yetmediğini gösteriyor. Sıcak yazma örüntüsünde kırk anahtar 200.000 işlem görüyor; tekrar oranı 5000, bu tablodaki en yüksek değer. Buna karşılık isabet yalnız 0,3001. Neden, işlemlerin 140.000’inin yazma olmasıdır: her yazma girdiyi düşürür ve sonraki okuma ıska alır. Yüksek tekrar oranı bir önbelleği haklı çıkarmaz; haklı çıkaran şey, tekrarın iki yazma arasına sığmasıdır.
Bir Örüntü Başka Bir Örüntüyü Bozar
Dördüncü satır ile ikinci tablo aynı olguyu iki açıdan gösteriyor. Takip sorgusu ile dönem taraması aynı önbellekten geçtiğinde birleşik isabet 0,1571’e, takip sorgusunun kendi isabeti 0,8976’dan 0,4713’e iniyor. Taramanın 400.000 farklı anahtarı, kapasitesi 2000 olan önbelleğe sürekli girip çıkıyor ve girerken takip girdilerini dışarı itiyor.
Sayı burada işareti değiştiriyor. Tarama önbelleği atladığında toplam depo erişimi 420.483,
önbellekten geçtiğinde 505.735: aradaki 85.252 erişim, önbelleğin eklediği yüktür.
depoya ulasan istek/s 139,90’dan 317,50’ye çıkıyor. Bir önbellek, yanlış örüntüye açıldığında
koruduğu şeyi bozar.
Çözüm önbelleği kaldırmak değil, tarama akışını ondan geçirmemektir. Son satır bunu ölçüyor:
donem: ad alanı atlandığında takip isabeti hiç düşmeden 0,8976’da kalıyor ve depoya ulasan istek/s 139,90’a geri dönüyor. Tarama yine 400.000 kayıt okuyor — o okumalar zaten kaçınılmazdı
— ama artık kimsenin çalışma kümesini tahliye etmiyor. K01’in gün sonu işini ayrı bir dört
saatlik pencereye koymasının gerekçesi bu tabloda görünür hâle geliyor.
Karar Kuralı
Dört ölçüm birlikte okunduğunda önbellek kararı dört koşula iner ve dördü de sayılabilir.
Tekrar oranı birden büyük olmalı. Pencerede istek sayısının tekil anahtar sayısına oranı 1,00 ise kazanılacak hiçbir şey yoktur; dönem taraması bu koşulda kalır.
Tekrar iki yazma arasına sığmalı. Sıcak yazma örüntüsünde tekrar 5000 ama okuma başına düşen yazma oranı yüksek olduğu için isabet 0,3001’de kalıyor. Ölçülecek nicelik tekrar değil, bir girdinin ömrü boyunca aldığı okuma sayısıdır.
Çalışma kümesi kapasiteye sığmalı. Bu konunun ilk dersi kapasitesi 2000 olan bir önbelleğin 1000 gönderilik çalışma kümesini karşıladığını ölçmüştü; kapasite altına inildiğinde katmanlar artıklarını birbirine sızdırır.
Aynı önbelleği paylaşan başka bir örüntü çalışma kümesini tahliye etmemeli. Bu koşul ötekilerin hepsi sağlansa bile tek başına bozulabilir ve bozulduğunda önbellek net zarara geçer: 85.252 ek depo erişimi.
Dördü sağlanmıyorsa önbelleksiz tasarım karşıt kalıp değildir; ölçülmeden verilmiş bir önbellek kararı ise her durumda öyledir.
Özet
- Takip sorgusunda tekrar oranı 9,77 ve önbelleksiz tasarım depoya 200.000 erişim bindiriyor;
önbellekli tasarımda 20.483. K01’in
depoya ulasan istek/ssatırında bu 513,89 ile 139,90 arasındaki 3,67 katlık farktır. - Dönem taramasında tekrar oranı 1,00, isabet 0,0000 ve depo erişimi iki tasarımda da 400.000; kazanç sıfırken istek başına 2,00 ek dokunuş ödenir.
- Sıcak yazma örüntüsünde tekrar oranı 5000 olduğu hâlde isabet 0,3001: yüksek tekrar bir önbelleği haklı çıkarmaz, tekrarın iki yazma arasına sığması çıkarır.
- Tarama aynı önbellekten geçtiğinde takip isabeti 0,8976’dan 0,4713’e iniyor ve toplam depo erişimi 420.483’ten 505.735’e çıkıyor; önbellek 85.252 erişim ekliyor.
- Karar dört koşula iner: tekrar oranı birden büyük, tekrar iki yazma arasına sığıyor, çalışma kümesi kapasiteye sığıyor ve başka bir örüntü onu tahliye etmiyor.
Sonraki Adım
Bu derste önbelleğin gerekli olduğu örüntüler ayrıldı, ama bir soru hep açık kaldı. Önbellekten dönen değer, deponun o andaki değeriyle aynı olmak zorunda değil; ikisinin ayrıldığı süre bu konunun ikinci dersinde bir kez ölçüldü — geçersizleştirme kapatıldığında 105.794 bayat okuma — ve dördüncü dersinde bir parametreye bağlandı: beş saniyelik üst sınır süresi keyfi seçilmişti ve seçimin gerekçesi yazılmamıştı. Sıradaki ders o gerekçeyi kurar. Bayatlığın ne kadarının kabul edilebilir olduğu bir mühendislik tercihi değil, verinin kendisinden çıkan bir sayıdır: bir gönderinin durumu ne sıklıkla değişiyorsa, o değişimden daha kısa bir pencere bayat yanıt üretmez. Sonraki ders pencereyi bu ilişkiden hesaplar, saniyede kaç bayat yanıt kabul edildiğini sayar ve aynı bayatlık bütçesiyle isabetin nereye kadar çıkabileceğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.