Ders 16 / 20
Kenar Depolama
Kenar anahtar–değer deposu okuma için hızlı, yazma için yavaştır ve tutarlılığı gecikmelidir. Yayılma penceresi taranıp her değerde bayat okuma sayılır, nesne deposuna erişimin tur sayısı ve çıkış ücreti kurgu birimle karşılaştırılır, deponun boyut sınırı bir kısıt olarak çıkarılır.
İçindekiler
Önceki dersin dört işi de durumsuzdu: yönlendirme bir tabloya, kimlik doğrulama bir imzaya, A/B ayrımı bir bölme kuralına bakıyordu. Uçta yapılamayan üç işin ortak yanı ise durum istemeleriydi. Kurgudaki bölgesel ölçüm ağında şube uçlarının sık ihtiyaç duyduğu şeyler bellidir: şubenin tarife tablosu, yapılandırması, bir oturumun geçerlilik özeti. Bunlar büyük değildir ve çoğunlukla okunur.
Uçta bir depo vardır ve adı kenar anahtar–değer deposudur. Bu ders onu tek bir ölçüyle tartmaz, çünkü tek bir sayı yanıltır: aynı depo okuma yönünde bölgedeki her şeyden hızlı, yazma yönünde bölgedeki her şeyden yavaştır. Aradaki fark bir gecikme kalemi değil, bir tutarlılık penceresidir.
KE13. Yazma merkezde uygulanır; yazan uç yeni değeri hemen görür, öteki uçlar yayılma penceresi boyunca eski değeri döndürür. KE14. Pencere sabittir ve sonunda bütün uçlar yeni değeri görür; kısmi yayılma modellenmez. KE15. Olay akışı kendi üretecimizle üretilir ve tohum görünürdür.
Okuma Ucuz, Yazma Pahalı
Okuma yolunun uzunluğu değerin uçta olup olmamasına bağlıdır. Değer uçta duruyorsa okuma hiç ağa çıkmaz. Yazma yolunun uzunluğu ise değişmez: yazma her zaman merkeze gider, çünkü sıralamayı belirleyen tek nokta orasıdır.
// olcum-agi/yayilma.mjs — kenar anahtar-deger deposunda okuma/yazma asimetrisi // ve yayilma penceresinde bayat okuma sayimi. // MODEL: olay akisi kendi uretecimizle uretilir; tohum gorunurdur. const TOHUM = 20_260_801; let s = TOHUM % 2_147_483_647; const rnd = () => (s = (s * 48_271) % 2_147_483_647) / 2_147_483_647; const UC = 6, ANAHTAR = 40, OLAY = 20_000, YAZMA_YUZDE = 8; const YOL = { "okuma (uctaki kopya)": { tur: 0, ms: 2 }, "okuma (kopya yoksa)": { tur: 1, ms: 62 }, "yazma (merkeze)": { tur: 1, ms: 64 }, }; console.log("okuma ve yazma ayni depoda ayni sey degil (model girdisi)"); for (const [ad, v] of Object.entries(YOL)) console.log(" " + ad.padEnd(24) + `${v.tur} ek tur ${String(v.ms).padStart(3)} ms`); console.log(" yazma / okuma gecikme orani: " + (YOL["yazma (merkeze)"].ms / YOL["okuma (uctaki kopya)"].ms).toFixed(0) + "x"); const olay = []; let t = 0; for (let i = 0; i < OLAY; i++) { t += 1 + Math.floor(rnd() * 60); const uc = Math.floor(rnd() * UC), anahtar = Math.floor(rnd() * ANAHTAR); olay.push({ t, uc, anahtar, yaz: rnd() * 100 < YAZMA_YUZDE }); } const yazma = olay.filter((o) => o.yaz).length; console.log(`\n${OLAY} olay, ${UC} uc, ${ANAHTAR} anahtar, tohum ${TOHUM}`); console.log(` yazma ${yazma} okuma ${OLAY - yazma} akis suresi ${(t / 1000).toFixed(1)} sn`); console.log("\nyayilma penceresi tarandiginda bayat okuma"); console.log("pencere".padEnd(12) + "pencere ici okuma".padEnd(20) + "bayat okuma".padEnd(14) + "okuma orani".padEnd(14) + "anahtar basina"); for (const pencere of [1_000, 5_000, 10_000, 30_000, 60_000]) { const son = new Array(ANAHTAR).fill(null); let ici = 0, bayat = 0, okuma = 0; for (const o of olay) { if (o.yaz) { son[o.anahtar] = o; continue; } okuma++; const y = son[o.anahtar]; if (!y || o.t - y.t >= pencere) continue; ici++; if (y.uc !== o.uc) bayat++; } console.log(`${pencere} ms`.padEnd(12) + String(ici).padEnd(20) + String(bayat).padEnd(14) + `%${((bayat / okuma) * 100).toFixed(2)}`.padEnd(14) + (bayat / ANAHTAR).toFixed(1)); } console.log(" pencere 0 ms olsaydi bayat okuma 0 olurdu; bedeli her okumanin merkeze gitmesidir");
okuma ve yazma ayni depoda ayni sey degil (model girdisi) okuma (uctaki kopya) 0 ek tur 2 ms okuma (kopya yoksa) 1 ek tur 62 ms yazma (merkeze) 1 ek tur 64 ms yazma / okuma gecikme orani: 32x 20000 olay, 6 uc, 40 anahtar, tohum 20260801 yazma 1582 okuma 18418 akis suresi 609.8 sn yayilma penceresi tarandiginda bayat okuma pencere pencere ici okuma bayat okuma okuma orani anahtar basina 1000 ms 1154 957 %5.20 23.9 5000 ms 5019 4147 %22.52 103.7 10000 ms 8690 7230 %39.26 180.8 30000 ms 15466 12857 %69.81 321.4 60000 ms 17572 14621 %79.38 365.5 pencere 0 ms olsaydi bayat okuma 0 olurdu; bedeli her okumanin merkeze gitmesidir
Asimetri ilk üç satırda okunur. Uçtaki kopyadan okuma 0 ek tur ve 2 ms, kopya yoksa okuma 1 tur ve 62 ms, yazma her durumda 1 tur ve 64 ms’dir. Yazmanın okumaya oranı 32 kattır. Bu oran deponun ne için kurulduğunu söyler: değer bir kez yazılıp çok kez okunuyorsa depo yerindedir, her istekte bir kez yazılıyorsa depo yanlış yerdedir.
Bayat Okumanın Sayısı
Tutarlılığın gecikmeli olması bir sıfat değildir; kaç okumanın eski değeri gördüğüyle ölçülür. Aşağıdaki tarama pencereyi beş değerde tutar ve her değerde bayat okumayı sayar. Bayat sayılmanın koşulu iki tanedir: okuma son yazmadan sonra pencere dolmadan gelmiş olacak ve okuyan uç yazan uçtan farklı olacak.
Akışın kendisi de bir girdidir ve okunması gerekir: 20.000 olayın 1.582’si yazma, 18.418’i okumadır ve 609,8 saniyeye yayılır. Yani karışım deponun tasarlandığı yöne uygundur — on bir okumaya bir yazma düşer. Bayat okuma sayısının bu kadar yüksek çıkması bu yüzden yazmanın çok olmasından değil, kırk anahtarın altı uca dağılmasından gelir: her yazma, öteki beş uçtaki okumaları pencere boyunca eski değere bağlar.
Sayılar tek yönde ve hızlı büyür. 1.000 ms’lik pencerede bayat okuma 957, okumaların %5,20’si. 5.000 ms’de 4.147 ve %22,52. 10.000 ms’de 7.230 ve %39,26. 30.000 ms’de 12.857 ve %69,81. 60.000 ms’de 14.621 ve %79,38. Pencere altmış kat büyürken bayat okuma on beş kat artar; büyüme doğrusal değildir, çünkü pencere genişledikçe okumaların çoğu zaten bir yazmanın gölgesinde kalır ve doyuma yaklaşır. Anahtar başına düşen bayat okuma 23,9’dan 365,5’e çıkar.
Kırk anahtarın her biri kurgudaki bir şubenin tarife tablosu sayılırsa, 30.000 ms’lik bir pencerede anahtar başına 321,4 okuma eski tarifeyi görür. Bu sayının kabul edilebilir olup olmadığı okumanın ne için yapıldığına bağlıdır: bir ekranda tarife göstermek için okunuyorsa katlanılır, bir faturaya tutar yazmak için okunuyorsa katlanılmaz. Aynı depo aynı pencereyle iki iş için iki ayrı yanıt verir.
Devredilen karar burada çoğaltmadır: değerin hangi uçlara, ne zaman kopyalanacağını uygulama seçmez. Karşılığında gelen kısıt yayılma penceresidir ve bir sayıdır. Kısıtın etrafından dolaşmanın yolu son satırda yazılıdır: pencere sıfıra indirilirse bayat okuma sıfır olur, ama o zaman her okuma merkeze gider — okuma 2 ms’den 62 ms’ye, yani otuz bir kat yukarı çıkar. Depoyu kullanmanın sebebi tam olarak o 2 ms olduğuna göre, bu dolaşma yolu deponun kendisini iptal eder.
Nesne Deposuna Uzanmak
Depoya sığmayan şeyler için uç, nesne deposuna uzanır. Uzanmanın iki ekseni vardır ve ters yönde çalışırlar: tur sayısı ve çıkış ücreti.
KE16. Ücretler kurgu birimdir; gerçek bir para birimi ya da fiyat değildir. KE17. Nesne boyutu 250 KB sabittir ve uçta tutulan kopyanın isabet oranı %70 girdidir. KE18. Değer sınırı 25 KB, anahtar sınırı 512 bayttır; parçalama parçaların sırayla okunmasını varsayar.
// olcum-agi/nesne.mjs — kenardan nesne deposuna erisimin turu ve cikis ucreti, // ardindan kenar deposunun boyut siniri. MODEL: ucretler kurgu birimdir, // gercek bir para birimi ya da fiyat degildir. const ISTEK = 10_000, NESNE_KB = 250, GB = 1_048_576; // KB cinsinden 1 GB const UCRET = { "nesne->bolge": 0, "bolge->uc": 0, "nesne->uc": 2, "uc->istemci": 1 }; const YOL = { "bolge uzerinden": { tur: 2, ms: 80, bacak: ["nesne->bolge", "bolge->uc", "uc->istemci"] }, "uctan dogrudan": { tur: 1, ms: 45, bacak: ["nesne->uc", "uc->istemci"] }, "ucta kopya": { tur: 0, ms: 2, bacak: ["uc->istemci"] }, }; const hacimGB = (n) => (n * NESNE_KB) / GB; const ucret = (yol, n) => YOL[yol].bacak.reduce((t, b) => t + UCRET[b], 0) * hacimGB(n); console.log(`${ISTEK} nesne istegi, nesne ${NESNE_KB} KB, ucretler kurgu birimdir`); console.log("yol".padEnd(18) + "ag turu".padEnd(10) + "gecikme".padEnd(10) + "cikis kalemi".padEnd(14) + "birim/GB".padEnd(11) + "kume ucreti"); for (const [ad, v] of Object.entries(YOL)) { const birimGB = v.bacak.reduce((t, b) => t + UCRET[b], 0); console.log(ad.padEnd(18) + String(v.tur).padEnd(10) + `${v.ms} ms`.padEnd(10) + String(v.bacak.length).padEnd(14) + String(birimGB).padEnd(11) + `${ucret(ad, ISTEK).toFixed(2)} birim`); } console.log(`kume hacmi ${hacimGB(ISTEK).toFixed(2)} GB ` + `bacak ucreti: ` + Object.entries(UCRET).map(([b, u]) => `${b} ${u}`).join(", ")); const ISABET = 70; const kopya = Math.round((ISTEK * ISABET) / 100), dusen = ISTEK - kopya; const karmaMs = (kopya * YOL["ucta kopya"].ms + dusen * YOL["bolge uzerinden"].ms) / ISTEK; const karmaUcret = ucret("ucta kopya", kopya) + ucret("bolge uzerinden", dusen); console.log(`\nkarma: %${ISABET} ucta kopya, kalani bolge uzerinden`); console.log(" " + `${kopya} + ${dusen} istek`.padEnd(22) + `${karmaMs.toFixed(1)} ms/istek ${karmaUcret.toFixed(2)} birim ` + `dogrudan yola gore ${(ucret("uctan dogrudan", ISTEK) / karmaUcret).toFixed(2)}x ucuz`); // Kenar anahtar-deger deposunun siniri: anahtar 512 bayt, deger 25 KB. const DEGER_KB = 25, ANAHTAR_BAYT = 512; const KAYIT = { "sube yapilandirmasi": 2, "tarife tablosu": 18, "oturum ozeti": 0.4, "gunluk okuma ozeti": 140, "aylik fatura belgesi": 900, "sayac ham gunlugu": 12_000, }; console.log(`\nkenar deposu siniri: deger ${DEGER_KB} KB, anahtar ${ANAHTAR_BAYT} bayt`); console.log("kayit".padEnd(24) + "boyut".padEnd(12) + "sigar".padEnd(9) + "dolasma"); let sigan = 0; for (const [ad, kb] of Object.entries(KAYIT)) { const olur = kb <= DEGER_KB; if (olur) sigan++; const parca = Math.ceil(kb / DEGER_KB); console.log(ad.padEnd(24) + `${kb} KB`.padEnd(12) + (olur ? "evet" : "hayir").padEnd(9) + (olur ? "-" : `${parca} parca, okuma basina +${parca - 1} okuma ya da nesne deposu (+2 tur)`)); } console.log(` ${sigan}/${Object.keys(KAYIT).length} kayit depoya sigar`);
10000 nesne istegi, nesne 250 KB, ucretler kurgu birimdir yol ag turu gecikme cikis kalemi birim/GB kume ucreti bolge uzerinden 2 80 ms 3 1 2.38 birim uctan dogrudan 1 45 ms 2 3 7.15 birim ucta kopya 0 2 ms 1 1 2.38 birim kume hacmi 2.38 GB bacak ucreti: nesne->bolge 0, bolge->uc 0, nesne->uc 2, uc->istemci 1 karma: %70 ucta kopya, kalani bolge uzerinden 7000 + 3000 istek 25.4 ms/istek 2.38 birim dogrudan yola gore 3.00x ucuz kenar deposu siniri: deger 25 KB, anahtar 512 bayt kayit boyut sigar dolasma sube yapilandirmasi 2 KB evet - tarife tablosu 18 KB evet - oturum ozeti 0.4 KB evet - gunluk okuma ozeti 140 KB hayir 6 parca, okuma basina +5 okuma ya da nesne deposu (+2 tur) aylik fatura belgesi 900 KB hayir 36 parca, okuma basina +35 okuma ya da nesne deposu (+2 tur) sayac ham gunlugu 12000 KB hayir 480 parca, okuma basina +479 okuma ya da nesne deposu (+2 tur) 3/6 kayit depoya sigar
Üç yol arasındaki ilişki ilk bakışta beklenenin tersidir. Uçtan doğrudan nesne deposuna gitmek en az turlu yollardan biridir — 1 tur, 45 ms — ama küme ücreti 7,15 birimdir, bölge üzerinden gitmenin üç katı. Nedeni çıkış kalemidir: doğrudan yolda nesne, deponun ağından çıkıp uca gelir ve bu bacak GB başına 2 birim yazar. Bölge üzerinden gidildiğinde nesne aynı ağın içinde kalır; ücretlenen tek bacak uçtan istemciye olandır. Tur sayısını azaltmak burada ücreti üç katına çıkarır.
Uçta kopya tutmak iki eksende de kazanır: 0 tur, 2 ms ve 2,38 birim. Ama bu satır bir varsayımla gelir — kopyanın orada olması. Karma satır gerçek durumu verir: %70 isabetle ortalama 25,4 ms/istek ve 2,38 birim; doğrudan yola göre 3,00 kat ucuz. Kopya oranı düştükçe gecikme 80 ms’ye doğru kayar, ücret ise değişmez. Yani uçta kopya tutmanın kazandırdığı şey ücret değil, gecikmedir; ücreti belirleyen tek şey trafiğin hangi bacaklardan geçtiğidir.
Depoya Ne Sığar
Son tablo deponun sınırını bir kısıt olarak sayar. Değer sınırı 25 KB’dır ve kurgudaki altı kayıt türünün 3’ü sığar: şube yapılandırması 2 KB, tarife tablosu 18 KB, oturum özeti 0,4 KB. Üçü sığmaz: günlük okuma özeti 140 KB, aylık fatura belgesi 900 KB, sayaç ham günlüğü 12.000 KB.
Sığmayan bir değeri depoya sokmanın yolu parçalamaktır ve bedeli okuma tarafında toplanır. 140 KB’lık özet 6 parça eder; her tam okuma +5 okuma demektir. Fatura belgesi 36 parça ve +35 okuma, ham günlük 480 parça ve +479 okuma. Parçalama küçük bir aşımda makul, büyük bir aşımda saçmadır: 25 KB sınırının altı yüz katına yaklaşan bir değer için depo doğru araç değildir. Alternatif satırda yazılıdır — nesne deposuna koymak ve +2 tur ödemek. Karar noktası tek okumada kaç parça gerektiğidir; parça sayısı ikiyi üçü geçtiği anda nesne deposunun iki turu daha ucuza gelir.
Anahtar sınırının 512 bayt olması ayrı bir tasarım kısıtıdır ve tabloda görünmez. Anahtarın kısa olması, birleşik anahtarların — şube, sayaç, dönem üçlüsünün — tek bir dizgeye sığdırılmasını gerektirir; bu da deponun aralık sorgusu yapamamasıyla birleşince veriyi anahtar tasarımına gömmeye zorlar. Depo bir veritabanı değildir; anahtarla erişilen küçük ve çok okunan değerler içindir.
Özet
- Aynı depo iki yönde iki ayrı şeydir: uçtaki kopyadan okuma 0 tur / 2 ms, yazma 1 tur / 64 ms. Yazmanın okumaya oranı 32 kattır; depo bir kez yazılıp çok okunan değerler içindir.
- Tutarlılık gecikmelidir ve bedeli sayılır: 1.000 / 5.000 / 10.000 / 30.000 / 60.000 ms’lik pencerelerde bayat okuma 957, 4.147, 7.230, 12.857 ve 14.621; okuma oranı %5,20’den %79,38’e çıkar, anahtar başına 23,9’dan 365,5’e.
- Pencereyi sıfırlamak bayat okumayı sıfırlar ama okumayı 2 ms’den 62 ms’ye taşır — deponun kullanılma sebebini iptal eder.
- Nesne deposuna üç yol vardır: bölge üzerinden 2 tur / 2,38 birim, uçtan doğrudan 1 tur / 7,15 birim, uçta kopya 0 tur / 2,38 birim. Tur azaltmak ücreti üç katına çıkarabilir; %70 isabetli karma 25,4 ms/istek ve 2,38 birim eder.
- Değer sınırı 25 KB’dır ve altı kayıt türünün 3’ü sığar. Sığmayanlar 6, 36 ve 480 parça eder; parça sayısı birkaçı geçtiğinde nesne deposunun +2 turu daha ucuza gelir.
Sonraki Adım
Bu dersin deposu bir şeyi hiç çözmedi: iki uç aynı anahtarı aynı anda değiştirmek isterse ne olur? Yayılma penceresi eski değeri okutuyordu; iki yazma yarışırsa biri diğerinin üzerine yazar ve arada bir güncelleme kaybolur. Sayaç artırmak, bir odaya katılanları saymak, bir kaynağı tek bir işleme kilitlemek — üçü de “oku, değiştir, yaz” adımını gerektirir ve üçü de bu depoda güvenli değildir. Uçta eşgüdümlü durum tutmanın yolu, aynı anahtar için tek bir yazarın var olmasıdır. Sonraki ders tek yazarlı bir nesne modelini koşturur, eşzamanlı isteklerde kayıp güncellemeyi sayar, ve karşılığında gelen iki kısıtı — tek örneğin darboğazı ile nesnenin coğrafi olarak sabitlenmesi — ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.