İçeriğe geç
academia.sh

Ders 20 / 22

Tipik Kullanım Senaryoları

Katalog önbelleği, oturum kaydı, hız sınırı ve sıralama tablosunun bellek içi deponun yapılarına oturtulması: her senaryonun hangi yapıyla kurulduğu, giriş başına kaç bayt tuttuğu ve o baytın karşılığında hangi erişimi satın aldığı.

İçindekiler

Önceki ders yayımla–abone ol yolunu ölçtü ve bir sınırla bitti: o yol hiçbir şey saklamadığı için 49 bayt tutuyordu, karşılığında kopan abonenin 700 olayı geri gelmiyordu. Bu, bu kursun tekrar eden sorusunun tek bir örneğiydi. Aynı soru deponun günlük işlerinin hepsinde sorulur.

Bu ders kütüphane sisteminin dört tipik işini alır — katalog önbelleği, oturum kaydı, hız sınırı, popüler kitap sıralaması — ve her birini tek bir soruya indirger: hangi yapıyla kurulur ve kaç bayt tutar. Önbellek stratejisinin ne olacağı, hız sınırının hangi algoritmayla işleyeceği burada tartışılmaz; o seçimler başka kurslarda ölçüldü. Burada ölçülen şey yapının kendisidir.

Ortak Maliyet Modeli

Dört hesap da aynı sayma kuralını kullanır (BK13): her giriş için 16 bayt işaretçi/uzunluk payı, karma yapıda her alan için 8 bayt ek pay, sıralı kümede her üye için 24 bayt dizin düğümü payı, her sayısal değer için 8 bayt. Anahtarların ve dizgi değerlerinin baytı gerçek uzunluktan okunur. Bu model bir ürünün iç yerleşimi değil, kendi deposunun sayma sözleşmesidir; önemli olan iki seçeneğin aynı kuralla sayılmasıdır.

Katalog Önbelleği ve Oturum Kaydı

İlk iki senaryo aynı yapıyı, karma yapıyı, iki farklı nedenle seçer. Katalog önbelleğinde soru kaydın bütün hâlinde mi yoksa alan alan mı tutulacağıdır. Oturum kaydında soru, kayıtların ne kadar süreyle tutulacağıdır: oturum verisi süre sonuyla birlikte kurulur ve bellek, açılan oturum sayısıyla değil aynı anda canlı oturum sayısıyla ölçeklenir.

// senaryo-katalog-oturum.mjs — katalog onbellegi ve oturum kaydi: hangi yapi, kac bayt
const EK_GIRIS = 16, EK_ALAN = 8, SAYI = 8;        // kendi maliyet modelim (giris/alan/sayi payi)
const b = (s) => Buffer.byteLength(String(s));
const alanBayt = (ad, deger) =>                    // karma yapida bir alanin maliyeti
  b(ad) + (typeof deger === "number" ? SAYI : b(deger)) + EK_ALAN;

function kitaplar(adet) {
  let tohum = 20250731;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  const etiketler = ["roman", "tarih", "cocuk", "bilim"];
  return Array.from({ length: adet }, (_, i) => ({
    kitapId: i + 1, baslik: `Kitap ${i + 1}`,
    yazar: `Yazar ${Math.floor(rast() * 120) + 1}`,
    sube: Math.floor(rast() * 3) + 1, rafta: Math.floor(rast() * 5),
    etiket: etiketler[Math.floor(rast() * 4)],
  }));
}

// --- Senaryo 1: katalog onbellegi. Ayni kayit iki yapiyla tutulur.
const kayitlar = kitaplar(400);
let dizgiBayt = 0, karmaBayt = 0, dizgiYazma = 0, karmaYazma = 0;
for (const k of kayitlar) {
  const anahtar = `kitap:${k.kitapId}`;
  const govde = JSON.stringify(k);
  dizgiBayt += b(anahtar) + b(govde) + EK_GIRIS;   // tek dizgi: govde butun hâlinde
  karmaBayt += b(anahtar) + EK_GIRIS +             // karma yapi: alan alan
    Object.entries(k).reduce((t, [ad, d]) => t + alanBayt(ad, d), 0);
}
for (let i = 0; i < 1000; i += 1) {                // 1000 raf guncellemesi: yalniz `rafta` degisir
  const k = kayitlar[i % kayitlar.length];
  dizgiYazma += b(JSON.stringify(k));              // dizgide butun govde yeniden yazilir
  karmaYazma += SAYI;                              // karmada yalniz o alanin degeri
}
console.log("katalog onbellegi (400 kitap)");
console.log(`  dizgi  : tutulan=${dizgiBayt} bayt  giris basina=${Math.round(dizgiBayt / 400)}` +
  `  1000 guncellemede yazilan=${dizgiYazma} bayt`);
console.log(`  karma  : tutulan=${karmaBayt} bayt  giris basina=${Math.round(karmaBayt / 400)}` +
  `  1000 guncellemede yazilan=${karmaYazma} bayt`);

// --- Senaryo 2: oturum kaydi. Karma yapi + sure sonu; sure sonu etkin oturumu sinirlar.
const DAKIKA = 720, DK_GIRIS = 25, OMUR = 30;      // 12 saat, dakikada 25 giris, 30 dk sure sonu
const oturumAlanlari = { kullaniciId: 1, sube: 1, rol: "uye", sonErisim: 1 };
const oturumBayt = b("oturum:") + 32 + EK_GIRIS +  // anahtar: onek + 32 karakterlik kimlik
  Object.entries(oturumAlanlari).reduce((t, [ad, d]) => t + alanBayt(ad, d), 0);
const canli = new Map();                           // kimlik -> sonGecerlilik (dakika)
let zirveCanli = 0, acilan = 0;
for (let dk = 1; dk <= DAKIKA; dk += 1) {
  for (const [kimlik, son] of canli) if (son <= dk) canli.delete(kimlik);   // sure sonu temizler
  for (let i = 0; i < DK_GIRIS; i += 1) canli.set(`o${acilan++}`, dk + OMUR);
  zirveCanli = Math.max(zirveCanli, canli.size);
}
console.log("oturum kaydi (12 saat)");
console.log(`  giris basina=${oturumBayt} bayt  acilan oturum=${acilan}  zirve canli=${zirveCanli}`);
console.log(`  sure sonu var : tutulan=${zirveCanli * oturumBayt} bayt`);
console.log(`  sure sonu yok : tutulan=${acilan * oturumBayt} bayt` +
  `  (${(acilan / zirveCanli).toFixed(1)} kat)`);
katalog onbellegi (400 kitap)
  dizgi  : tutulan=46115 bayt  giris basina=115  1000 guncellemede yazilan=90454 bayt
  karma  : tutulan=60623 bayt  giris basina=152  1000 guncellemede yazilan=8000 bayt
oturum kaydi (12 saat)
  giris basina=141 bayt  acilan oturum=18000  zirve canli=750
  sure sonu var : tutulan=105750 bayt
  sure sonu yok : tutulan=2538000 bayt  (24.0 kat)

Katalog satırlarındaki takas nettir. Karma yapı 14.508 bayt fazla tutuyor (giriş başına 115 yerine 152), çünkü her alanın adı ve her alanın ek payı ayrı ayrı ödeniyor. Satın aldığı şey ikinci sütundadır: bin raf güncellemesinde dizgi biçimi 90.454 bayt yeniden yazarken karma yapı 8.000 bayt yazıyor. Kayıt bütün hâlinde tutulduğunda tek bir sayının değişmesi bütün gövdenin yeniden yazılması demektir; ayrıca gövdenin çözülüp yeniden kurulması gerekir.

Seçim, kaydın nasıl kullanıldığına bağlıdır. Kayıt her zaman bütün hâlinde okunuyor ve seyrek güncelleniyorsa dizgi biçimi daha az bayt tutar ve tek erişimde biter. Alanları ayrı ayrı okunuyor ya da güncelleniyorsa karma yapının fazladan 37 baytı, her güncellemede kazanılan 82 bayt yazmayla karşılanır.

Oturum satırlarındaki takas başka bir eksende. Giriş başına 141 bayt ile on iki saatte açılan 18.000 oturumun tamamı tutulsa 2.538.000 bayt gerekirdi. Süre sonu, aynı anda canlı olan oturumu 750’de tutuyor ve maliyeti 105.750 bayta indiriyor: yirmi dört kat. Burada bellek, kullanıcı sayısıyla değil oturum ömrüyle ölçeklenir. Kaybedilen şey de bellidir: süresi dolan oturumun verisi geri getirilemez, kullanıcı yeniden kimlik doğrular (BK14).

Hız Sınırı ve Sıralama Tablosu

Son iki senaryo aynı sorunun iki ucudur: bir sayının kendisi yeterli mi, yoksa sayıyı oluşturan tek tek girişler de gerekli mi. Hız sınırında sayaç yalnız pencerenin toplamını tutar; sıralı küme her isteğin anını ayrı bir üye olarak tutar. Sıralama tablosunda karma yapı yalnız ödünç sayılarını tutar; sıralı küme bunların üstüne sıralı bir dizin taşır.

// senaryo-hiz-siralama.mjs — hiz siniri ve siralama tablosu: hangi yapi, kac bayt, kac giris
const EK_GIRIS = 16, EK_ALAN = 8, EK_DIZIN = 24, SAYI = 8;   // ayni maliyet modeli
const b = (s) => Buffer.byteLength(String(s));

// --- Senaryo 3: hiz siniri. Sayac pencerenin toplamini, sirali kume tek tek anlari tutar.
const KULLANICI = 2000, SINIR = 60;                // dakikada 60 istek
let sayacBayt = 0, kumeBayt = 0;
for (let u = 1; u <= KULLANICI; u += 1) {
  sayacBayt += b(`hiz:${u}:2025073114`) + SAYI + EK_GIRIS;   // anahtarda pencere var
  let uye = b(`hiz:${u}`) + EK_GIRIS;
  for (let i = 0; i < SINIR; i += 1)               // her istek icin bir uye: zaman damgasi + sira
    uye += b(`1753963200000-${i}`) + SAYI + EK_DIZIN;
  kumeBayt += uye;
}
console.log(`hiz siniri (${KULLANICI} kullanici, dakikada ${SINIR} istek)`);
console.log(`  sayac       : tutulan=${sayacBayt} bayt  kullanici basina=${(sayacBayt / KULLANICI).toFixed(1)}` +
  `  giris=${KULLANICI}`);
console.log(`  sirali kume : tutulan=${kumeBayt} bayt  kullanici basina=${(kumeBayt / KULLANICI).toFixed(1)}` +
  `  giris=${KULLANICI * SINIR}  (${(kumeBayt / sayacBayt).toFixed(1)} kat)`);

// --- Senaryo 4: siralama tablosu. Ayni sayaclar; fark, sirali dizinin tutulup tutulmadigi.
function oduncSayilari(adet) {
  let tohum = 20250731;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  return Array.from({ length: adet }, (_, i) => [`Kitap ${i + 1}`, Math.floor(rast() * 900) + 1]);
}
const sayimlar = oduncSayilari(400);
let karmaBayt = b("populer") + EK_GIRIS, kumeSiraBayt = b("populer") + EK_GIRIS;
for (const [uye] of sayimlar) {
  karmaBayt += b(uye) + SAYI + EK_ALAN;            // yalniz ad ve sayi
  kumeSiraBayt += b(uye) + SAYI + EK_DIZIN;        // ustune sirali dizin dugumu
}

const sirali = [...sayimlar].sort((x, y) => y[1] - x[1] || (x[0] < y[0] ? -1 : 1));
let karmaDokunulan = 0, kumeDokunulan = 0;
const ilkOn = sirali.slice(0, 10);                 // kume: dizinin basindan on giris
kumeDokunulan += ilkOn.length;
karmaDokunulan += sayimlar.length;                 // karma: hepsi taranip siralanir

const hedefSkor = sayimlar[136][1];                // "Kitap 137" kacinci sirada
let karmaUstun = 0;                                // karmada yanit tam taramayla bulunur
for (const [, s] of sayimlar) if (s > hedefSkor) karmaUstun += 1;
karmaDokunulan += sayimlar.length;
let alt = 0, ust = sirali.length - 1, adim = 0;    // sirali dizinde ikili arama
while (alt <= ust) {
  const orta = (alt + ust) >> 1; adim += 1;
  if (sirali[orta][1] > hedefSkor) alt = orta + 1; else ust = orta - 1;
}
kumeDokunulan += adim;
console.log(`siralama tablosu (${sayimlar.length} kitap)`);
console.log(`  karma yapi  : tutulan=${karmaBayt} bayt  ilk on + sira icin dokunulan=${karmaDokunulan}`);
console.log(`  sirali kume : tutulan=${kumeSiraBayt} bayt  ilk on + sira icin dokunulan=${kumeDokunulan}` +
  `  (${(kumeSiraBayt / karmaBayt).toFixed(2)} kat bayt)`);
console.log(`  ilk uc: ${ilkOn.slice(0, 3).map(([a, s]) => `${a}=${s}`).join("  ")}`);
console.log(`  "Kitap 137" skoru=${hedefSkor}; daha yuksek skorlu kitap: karma=${karmaUstun}` +
  ` (400 giris) kume=${alt} (${adim} giris)`);
hiz siniri (2000 kullanici, dakikada 60 istek)
  sayac       : tutulan=84893 bayt  kullanici basina=42.4  giris=2000
  sirali kume : tutulan=5786893 bayt  kullanici basina=2893.4  giris=120000  (68.2 kat)
siralama tablosu (400 kitap)
  karma yapi  : tutulan=9915 bayt  ilk on + sira icin dokunulan=800
  sirali kume : tutulan=16315 bayt  ilk on + sira icin dokunulan=19  (1.65 kat bayt)
  ilk uc: Kitap 141=900  Kitap 211=900  Kitap 125=897
  "Kitap 137" skoru=533; daha yuksek skorlu kitap: karma=146 (400 giris) kume=146 (9 giris)

Hız sınırındaki fark altmış sekiz kattır ve nedeni tek cümleyle söylenebilir: sayaç kullanıcı başına bir giriş tutar, sıralı küme altmış giriş tutar. Kullanıcı başına 42,4 bayt yerine 2.893,4 bayt ödenir. Karşılığında satın alınan şey, pencerenin içindeki her isteğin anının bilinmesidir; sayaç bunu bilmez, yalnız kaç tane olduğunu bilir. Bu bilginin gerekli olup olmadığı sınırın nasıl işleyeceğine bağlıdır ve o karar bu dersin dışındadır; ölçülen şey fiyatıdır.

Sıralama tablosunda oran çok daha küçüktür: 1,65 kat, yani 6.400 bayt. Karşılığı son iki satırda görünür. İlk on kitabı ve bir kitabın sırasını bulmak karma yapıda 800 giriş dokunmayı gerektiriyor (iki kez tam tarama), sıralı kümede 19 giriş yetiyor (on giriş baştan okunuyor, sıra dokuz adımlık ikili aramayla bulunuyor). İki yol da aynı yanıtı veriyor: “Kitap 137”den daha çok ödünç verilen 146 kitap var.

Sıralı kümenin dizin payı bir kez ödenir ama her sorguda geri kazanılır. Katalog 400 değil 400.000 kitap olduğunda karma yapının tam taraması 800.000 girişe çıkarken sıralı kümenin işi 10 + 19 girişte kalır; fazladan ödenen bayt ise girişle doğru orantılı büyür.

Dört Senaryo Tek Tabloda

Senaryo Yapı Giriş Tutulan bayt Satın alınan Kaybedilen
Katalog önbelleği karma yapı 400 60.623 alan başına 8 baytlık yazma (dizgide 90 bayt) dizgiye göre 14.508 bayt
Oturum kaydı karma yapı + süre sonu 750 canlı 105.750 belleğin oturum ömrüyle sınırlanması süresi dolan oturumun verisi
Hız sınırı sayaç 2.000 84.893 kullanıcı başına tek giriş isteklerin tek tek anları
Sıralama tablosu sıralı küme 400 16.315 ilk on ve sıra sorgusunda 19 giriş karma yapıya göre 6.400 bayt

Tablo dört ayrı iş gibi görünüyor ama tek bir karar biçimini tekrarlıyor. Her satırda yapı, o iş için hangi bilginin tutulması gerektiği sorusuna verilen yanıttır. Bilgi ne kadar ayrıntılı tutulursa erişim o kadar ucuzlar ve bellek o kadar pahalılaşır. Karma yapı alanları ayırdığı için alan başına yazmayı, sıralı küme sırayı sakladığı için sıra sorgusunu ucuzlatır; sayaç ayrıntıyı attığı için ucuzdur ve attığı ayrıntı geri gelmez.

Özet

  • Aynı kaydın dizgi biçimi 46.115, karma biçimi 60.623 bayt tuttu; karma yapının 14.508 baytlık fazlası, bin güncellemede 90.454 yerine 8.000 bayt yazmayla karşılandı.
  • Oturum kaydında bellek kullanıcı sayısıyla değil oturum ömrüyle ölçeklenir: süre sonu 18.000 oturumu 750 canlı oturuma indirdi, 2.538.000 bayt yerine 105.750 bayt.
  • Hız sınırında sayaç kullanıcı başına 42,4 bayt, sıralı küme 2.893,4 bayt tutar; altmış sekiz katlık fark, tek tek istek anlarının saklanmasının fiyatıdır.
  • Sıralama tablosunda sıralı kümenin dizin payı 6.400 bayttır ve ilk on ile sıra sorgusunu 800 giriş yerine 19 girişe indirir; iki yol da aynı yanıtı verir.
  • Dört senaryoda da yapı seçimi tek bir soruya bakar: hangi bilgi tutulacak. Ayrıntı tutuldukça erişim ucuzlar, bellek pahalılaşır.

Sonraki Adım

Bu dersin bütün hesapları deponun beklendiği gibi çalıştığını varsaydı: her erişim sırası geldiğinde yapılıyor, hiçbir istek bir başkasını beklemiyor. Bellek içi depoların çoğu komutları tek bir iş parçacığında sırayla işler ve bu varsayım orada özel bir biçimde kırılır: 400.000 girişi tarayan tek bir komut, arkasında bekleyen bütün istekleri kendi süresi kadar geciktirir. Sonraki ders bu gecikmeyi ölçer, bekleyen istek sayısını sayar ve yavaş komut günlüğünün eşiğini tarayarak sorunlu komutun nasıl görünür kılındığını gösterir.

İ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