İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat