Ders 01 / 22
Anahtar–Değer Temelleri
Bellek içi deponun sözleşmesi ve bedeli: ayarlama, okuma ve süre sonunun kendi yazılan bir depo üzerinde kurulması, giriş başına tutulan baytın anahtar–değer–üstveri olarak sayılması, süre sonu olmayan ile sabit ve kayan süre sonu politikalarının zirve bellek ve yeniden giriş sayısı düzleminde karşılaştırılması, anahtar ve değer biçiminin tutulan bayta etkisi ve üstverinin giriş sayısıyla ölçeklenmesi.
İçindekiler
İlişkisel Olmayan Veri Modelleri kursu, ele aldığı bütün depoların ortak bir alışkanlığıyla kapandı: veri er ya da geç diske yazıldı, dayanıklılık modelin verili bir parçasıydı. Bu kurs o alışkanlığı bırakan depoları konu ediyor. Bellek içi depoda verinin tamamı süreç belleğinde durur, disk isteğe bağlı bir ayardır ve asıl kısıt kapasite değil bellek bütçesidir.
Bu kursun her dersi aynı üç soruyu sorar: bir yapı bellekte kaç bayt tutuyor, karşılığında ne satın alıyor, aynı işi daha az bellekle yapan seçenek ne kaybediyor. “Hızlıdır” bu soruların hiçbirinin yanıtı değildir; bellek içi deponun hızlı olması konu değil varsayımdır. Bu ders sözleşmenin kendisiyle başlıyor: bir anahtar ayarlanır, okunur ve bir ömrü olur.
Sözleşme ve Giriş Başına Bayt
Depo üç işlem tanır. Ayarlama bir anahtara bir değer bağlar ve isteğe bağlı bir ömür verir. Okuma anahtarı verir, değeri alır; ömrü dolmuş bir giriş yokmuş gibi davranır. Ömür uzatma son erim damgasını ileri taşır. Anahtar–değer deposunun temel sözleşmesi burada da geçerlidir: depo değerin içini görmez. Değişen tek şey, kaydın nerede durduğudur — ve bu tek değişiklik maliyet hesabını baştan yazar, çünkü diskte yer bir kapasite kalemiyken bellekte yer bir bütçedir.
Ölçüm için kütüphanenin oturum kaydı kullanılır. BY1: katalog 20.000 üye taşır, gün 12 saattir
(43.200 saniye), her üye gün içinde 1–5 kez sisteme dokunur ve dokunuşlar arası 60–2.459 saniyedir;
dokunuş zamanları tohumu görünür bir doğrusal eşlenik üreticiden gelir. BY2: giriş başına
üstveri 56 bayt sayılır — karma kovası göstergesi 8, giriş yapısının göstergeleri 16, uzunluk
alanları 16, son erim damgası 8, hizalama 8. Tutulan bayt her zaman anahtar + değer + 56
biçiminde hesaplanır. BY3: canlı giriş 300 saniyede bir örneklenir, zirve bu örneklerin en
büyüğüdür; süresi geçmiş girişin bellekten ne zaman silindiği ayrı bir sorudur, burada yalnız
süresi geçmemiş giriş sayılır.
// bellek/depo.mjs — kendi yazilan bellek ici depo: anahtar alani, sure sonu ve tutulan // baytin sayilmasi. Giris basina ustveri BY2'de bildirilir; canli giris 300 sn'de bir sayilir. const USTVERI = 56; class BellekIciDepo { #tablo = new Map(); okuma = 0; iska = 0; ayarla(anahtar, deger, simdi, omur) { const s = JSON.stringify(deger); this.#tablo.set(anahtar, { s, sonErim: omur === null ? null : simdi + omur }); } oku(anahtar, simdi) { // suresi gecmis giris yok sayilir this.okuma += 1; const g = this.#tablo.get(anahtar); if (g === undefined || (g.sonErim !== null && g.sonErim <= simdi)) { this.iska += 1; return undefined; } return JSON.parse(g.s); } omruUzat(anahtar, simdi, omur) { const g = this.#tablo.get(anahtar); if (g) g.sonErim = simdi + omur; } canli(simdi) { // giris basina: anahtar + deger + ustveri let n = 0, b = 0; for (const [a, g] of this.#tablo) if (g.sonErim === null || g.sonErim > simdi) { n += 1; b += Buffer.byteLength(a) + Buffer.byteLength(g.s) + USTVERI; } return { n, b }; } } // --- olay uretimi: 12 saatlik gun, 20.000 uye, dokunuslar tohumlu ureticten --- const UYE = 20_000, GUN = 43_200, OMUR = 1800, ORNEK = 300, TOHUM = 20240115; let cekirdek = TOHUM; const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648; const SUBE = ["merkez", "sahil", "universite", "cocuk", "arsiv"]; const olay = []; for (let u = 1; u <= UYE; u += 1) { let t = Math.floor(rast() * (GUN - 7200)); const kez = 1 + Math.floor(rast() * 5); for (let k = 0; k < kez && t < GUN; k += 1) { olay.push([t, u]); t += 60 + Math.floor(rast() * 2400); } } olay.sort((x, y) => x[0] - y[0] || x[1] - y[1]); const anahtar = (u) => `oturum:${u}:${(u * 2654435761) % 4294967296}`; const oturum = (u) => ({ uye: u, sube: SUBE[u % 5], rol: u % 97 === 0 ? "gorevli" : "okur" }); function kosum(ad, omur, kayan) { const d = new BellekIciDepo(); let zirveN = 0, zirveB = 0, yeni = 0, sonraki = ORNEK; for (const [t, u] of olay) { while (t >= sonraki) { const c = d.canli(sonraki); if (c.n > zirveN) { zirveN = c.n; zirveB = c.b; } sonraki += ORNEK; } const a = anahtar(u); if (d.oku(a, t) === undefined) { d.ayarla(a, oturum(u), t, omur); yeni += 1; } else if (kayan) d.omruUzat(a, t, omur); } const son = d.canli(GUN); if (son.n > zirveN) { zirveN = son.n; zirveB = son.b; } return [ad, zirveN, zirveB, son.n, son.b, d.iska, yeni]; } console.log(`tohum ${TOHUM}; ${UYE} uye, ${olay.length} dokunus, gun ${GUN} sn, omur ${OMUR} sn`); console.log(`ornek oturum degeri ${Buffer.byteLength(JSON.stringify(oturum(4242)))} bayt, ` + `anahtar ${Buffer.byteLength(anahtar(4242))} bayt, ustveri ${USTVERI} bayt`); const satir = [kosum("sure sonu yok", null, false), kosum("sabit sure sonu", OMUR, false), kosum("kayan sure sonu", OMUR, true)]; console.log(`\n${"politika".padEnd(16)}${"zirve giris".padStart(12)}${"zirve bayt".padStart(11)}` + `${"gun sonu giris".padStart(15)}${"gun sonu bayt".padStart(14)}${"iska".padStart(6)}${"fazladan giris".padStart(15)}`); for (const [a, zn, zb, sn, sb, i] of satir) console.log(a.padEnd(16) + String(zn).padStart(12) + String(zb).padStart(11) + String(sn).padStart(15) + String(sb).padStart(14) + String(i).padStart(6) + String(i - UYE).padStart(15));
tohum 20240115; 20000 uye, 59337 dokunus, gun 43200 sn, omur 1800 sn ornek oturum degeri 45 bayt, anahtar 22 bayt, ustveri 56 bayt politika zirve giris zirve bayt gun sonu giris gun sonu bayt iska fazladan giris sure sonu yok 20000 2397230 20000 2397230 20000 0 sabit sure sonu 2193 262936 28 3363 36951 16951 kayan sure sonu 2658 318688 29 3479 31182 11182
Süre Sonu Bir Bellek Politikasıdır
Üç koşum aynı 59.337 dokunuşu işliyor; ayrım yalnız ömür ayarındadır. Süre sonu verilmediğinde depo günün sonunda 20.000 giriş ve 2.397.230 bayt taşıyor ve bu sayı hiç küçülmüyor: her üye bir kez giriş yaptığında kaydı süreç yaşadıkça durur. Buna karşılık her dokunuş kaydı yerinde bulur; 20.000 ıska yalnız günün ilk dokunuşlarıdır ve kaçınılmazdır — kimse ilk kez giriş yapmadan oturum bulamaz. Fazladan yeniden giriş sıfırdır.
Sabit süre sonu aynı işi 2.193 giriş ve 262.936 bayt zirvesiyle yapıyor: dokuz kat az bellek. Satın alınan şey açıkça görülüyor — bellek artık üye sayısıyla değil eşzamanlı etkin üye sayısıyla ölçekleniyor, gün sonunda geriye 28 giriş kalıyor. Kaybedilen şey de sayılı: 16.951 fazladan yeniden giriş. Kaydı düşen üye kimliğini yeniden doğrulamak zorunda kalıyor.
Kayan süre sonu ikisinin arasındadır ve satın alma oranını görünür kılar. Zirve 318.688 bayta çıkıyor, sabit politikaya göre 55.752 bayt fazla; karşılığında fazladan yeniden giriş 16.951’den 11.182’ye iniyor. Aradaki 5.769 girişin her biri 9,7 bayt zirve belleğe mal oluyor. Ders bu sayıyı öne alır: politika seçimi bir üslup tercihi değil, bayt cinsinden fiyatı olan bir alımdır. Aynı hesap gün sonu belleğinde bambaşka durur — süre sonu olmayan politika 2,4 MB’ı kalıcı olarak tutarken, iki süre sonu politikası 3,4 KB civarında kapanır; fark 700 kattır.
Anahtarın ve Değerin Biçimi
Giriş sayısı tek kaldıraç değildir. Aynı oturum farklı anahtar ve değer biçimlerinde tutulabilir ve her biçim tutulan baytı doğrudan değiştirir. İkinci düzenek aynı üyeyi altı biçimde sayar ve sonunda yapısal sayımı ortama bağlı bir ölçümle karşılaştırır.
// bellek/gosterim.mjs — ayni oturumun anahtar ve deger gosterimleri: bicim basina // tutulan bayt. USTVERI ayni sabittir; yapisal sayim kosumdan bagimsizdir. const USTVERI = 56, GIRIS = 20_000, SUBE = ["merkez", "sahil", "universite", "cocuk", "arsiv"]; const oturum = (u) => ({ uye: u, sube: SUBE[u % 5], rol: u % 97 === 0 ? "gorevli" : "okur" }); const uye = 4242, jeton = (uye * 2654435761) % 4294967296, o = oturum(uye); const bicim = [ ["oturum:jeton:4242:...", `oturum:jeton:${uye}:${jeton}`], ["oturum:4242:...", `oturum:${uye}:${jeton}`], ["o:...", `o:${jeton}`], ]; const deger = [["kendini betimleyen", JSON.stringify(o)], ["konumsal", `${o.uye}|${o.sube}|${o.rol}`]]; console.log(`${"anahtar bicimi".padEnd(23)}${"anahtar".padStart(8)}${"deger bicimi".padStart(20)}` + `${"deger".padStart(7)}${"giris".padStart(7)}${`${GIRIS} giriste`.padStart(14)}`); for (const [ad, a] of bicim) for (const [dad, d] of deger) { const g = Buffer.byteLength(a) + Buffer.byteLength(d) + USTVERI; console.log(ad.padEnd(23) + String(Buffer.byteLength(a)).padStart(8) + dad.padStart(20) + String(Buffer.byteLength(d)).padStart(7) + String(g).padStart(7) + String(g * GIRIS).padStart(14)); } // yeni bir alan (sube_kodu) ikinci siraya eklenirse ESKI girisler nasil okunur const j = JSON.parse(deger[0][1]), [, k1, k2, k3] = deger[1][1].split("|"); console.log(`\neski giris, yeni sira (uye|sube_kodu|sube|rol) ile okunur:`); console.log(` kendini betimleyen -> sube=${j.sube} rol=${j.rol} sube_kodu=${j.sube_kodu}`); console.log(` konumsal -> sube=${k2} rol=${k3} sube_kodu=${k1}`); const once = process.memoryUsage().heapUsed; // ORTAMA BAGLI const tablo = new Map(); for (let i = 1; i <= GIRIS; i += 1) tablo.set(`oturum:${i}:${(i * 2654435761) % 4294967296}`, { s: JSON.stringify(oturum(i)), sonErim: 1800 }); const sonra = process.memoryUsage().heapUsed; const yapisal = [...tablo].reduce((t, [a, g]) => t + Buffer.byteLength(a) + Buffer.byteLength(g.s) + USTVERI, 0); console.log(`\n${GIRIS} girisin yapisal sayimi ${yapisal} bayt (kosumdan bagimsiz); ` + `gercek yigin farki bunun ${((sonra - once) / yapisal).toFixed(1)} kati (ORTAMA BAGLI, kosumdan kosuma degisir)`);
anahtar bicimi anahtar deger bicimi deger giris 20000 giriste oturum:jeton:4242:... 28 kendini betimleyen 45 129 2580000 oturum:jeton:4242:... 28 konumsal 20 104 2080000 oturum:4242:... 22 kendini betimleyen 45 123 2460000 oturum:4242:... 22 konumsal 20 98 1960000 o:... 12 kendini betimleyen 45 113 2260000 o:... 12 konumsal 20 88 1760000 eski giris, yeni sira (uye|sube_kodu|sube|rol) ile okunur: kendini betimleyen -> sube=universite rol=okur sube_kodu=undefined konumsal -> sube=okur rol=undefined sube_kodu=universite 20000 girisin yapisal sayimi 2397230 bayt (kosumdan bagimsiz); gercek yigin farki bunun 2.5 kati (ORTAMA BAGLI, kosumdan kosuma degisir)
En pahalı biçimle en ucuzu arasında 20.000 girişte 820.000 bayt vardır — %32’lik bir fark, tek satır kod değişikliğiyle. Anahtar adının 28 baytdan 12 bayta inmesi 320.000 bayt, değerin alan adlarından arındırılması 500.000 bayt kazandırır.
İki kayıp da somuttur. Kısa anahtar, anahtar alanının okunabilirliğini götürür: oturum: öneki
olmadan aynı alanda hangi girişin oturum, hangisinin önbellek kaydı olduğu ayırt edilemez ve iki
farklı iş aynı adı üretirse çakışma sessizce olur. Konumsal değerin kaybı daha serttir. Çıktının
ortasındaki üç satır bunu gösteriyor: sube_kodu alanı sonradan ikinci sıraya eklendiğinde,
kendini betimleyen değer eski girişleri hâlâ doğru çözüyor (sube=universite, rol=okur) ve
yalnız yeni alanı boş buluyor. Konumsal değer aynı girişi yanlış çözüyor: şube alanına okur,
şube koduna universite düşüyor. Kazanılan 500.000 bayt, verinin kendini anlatma yeteneğiyle
ödenmiştir.
Son satır ölçünün sınırını çizer. Yapısal sayım 2.397.230 bayt verir ve ilk düzeneğin gün sonu sayısıyla birebir aynıdır; gerçek yığın kullanımı ise bunun 2,5 katıdır. Bu ikinci sayı ortama bağlıdır ve koşumdan koşuma değişir — yapısal sayımın bir alt sınır olduğunu söyler, o kadar; kurs boyunca karar verdiren ölçü birincisidir. İkisi arasındaki oran şunu da söyler: 56 baytlık üstveri en ucuz biçimde girişin %64’ünü kaplar. Girişler küçüldükçe biçim iyileştirmesinin getirisi azalır; asıl kaldıraç girişin biçimi değil, giriş sayısıdır — yani süre sonu politikasıdır.
Özet
- Bellek içi depo üç işlem tanır: ayarlama, okuma, ömür uzatma. Değer depo için saydam değildir; değişen tek şey kaydın diskte değil bellekte durmasıdır ve bu, yeri bir kapasite kalemi olmaktan çıkarıp bütçeye çevirir.
- Tutulan bayt
anahtar + değer + 56olarak sayılır. 20.000 oturum kaydı 2.397.230 bayt tutar; giriş başına ortalama 120 bayttır. - Süre sonu bir bellek politikasıdır: sabit süre sonu zirveyi 20.000 girişten 2.193 girişe ve 2.397.230 bayttan 262.936 bayta indirir, karşılığında 16.951 fazladan yeniden giriş yazdırır.
- Kayan süre sonu 55.752 bayt fazla tutup 5.769 yeniden girişi önler — önlenen giriş başına 9,7 bayt. Politika seçimi bayt cinsinden fiyatı olan bir alımdır.
- Anahtar ve değer biçimi 20.000 girişte 820.000 bayt fark ettirir; konumsal değer bu kazancı alan sırasını depo dışında tutarak alır ve şema değiştiğinde eski girişleri sessizce yanlış çözer.
- Yapısal sayım koşumdan bağımsızdır ve karar verdiren ölçüdür; gerçek yığın kullanımı (yapısal sayımın 2,5 katı) ortama bağlıdır ve yalnız yapısal sayımın bir alt sınır olduğunu gösterir.
Sonraki Adım
Bu dersteki her yazma bütün değeri baştan yazdı: oturum kaydı okundu, değiştirildi, geri kondu. Kütüphanenin sayması gereken şeyler bu kalıba uymuyor. Bir kitabın kaç kez ödünç alındığı, bir şubenin gün içinde kaç ödünç verdiği, bir üyenin gecikme sayısı — hepsi tek bir sayıdır ve her istekte bir artar. Aynı sayacı iki istek aynı anda okuyup geri yazdığında ne olduğu bu derste hiç sorulmadı. Sonraki ders bunu ölçüyor: oku-değiştir-yaz ile deponun kendi artırmasının eşzamanlı istekte ürettiği fark, kaybolan güncelleme sayısı ve tek bir sayacın 200.000 kitaplık katalogda kaç bayta mal olduğu.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.