İçeriğe geç
academia.sh

Ders 06 / 16

Tek Parça Kalıcılık

Bütün veri sınıflarının tek depoya yığılmasının belirtiden tanınması: aynı yavaşlamayı veren iki nedenin ayrılması, okuma yolunun hiçbir şey göstermemesi, dönem taramasının kendi sınıfının 801 katı kaydın üstünden geçmesi, bir yazmanın kendi sorgusuna hizmet etmeyen iki dizini de bakması ve ayırmanın karşılığında yazma yolunun iki depoya uzaması.

İçindekiler

Buraya kadarki beş kalıp tek bir deponun ve tek bir sürecin içindeki israfı ölçtü: yanlış katmanda yapılan hesap, ana iş parçacığına yığılan iş, çok sayıda küçük çağrı, ihtiyaçtan fazlasının okunması ve pahalı istemcinin yeniden yaratılması. Hepsi deponun tek olduğunu veri olarak aldı. Bu ders o varsayımı sorar.

Belirti şudur: gün sonu ücretlendirme işi dört saatlik penceresini doldurmuyor, ama aynı pencerede depoya inen yazma yolunun istek başına yaptığı iş ölçülebilir biçimde büyümüş durumda. K01’in hesabında tepe yazma 97,22 istek/s ve bu sayı değişmedi; değişen, bir yazmanın depoda ne kadar iş doğurduğu. Tek parça kalıcılık (monolithic persistence), bütün veri sınıflarının tek bir depoya yığılmasıdır. Bu addaki tek parça, Mimari Biçemler kursundaki tek parça (monolith) ile aynı sözcüktür ama ayrı şeydir: orada tek olan dağıtım birimi, burada tek olan veri deposudur.

Aynı Belirti, İki Neden

Yazma başına işin büyümesinin iki ayrı nedeni olabilir ve ikisi ölçülmeden ayrılamaz.

Birinci neden: kayıt büyüdü. Durum olayına yeni alanlar eklenmiştir; her yazma daha çok bayt taşır ve daha çok yapıyı günceller. Bu, dördüncü dersin ölçtüğü israfın yazma tarafındaki karşılığıdır.

İkinci neden: depo paylaşılıyor. Kayıt hiç değişmemiştir. Aynı tabloya, başka bir veri sınıfının sorgusu için bir dizin eklenmiştir ve o günden sonra her durum olayı yazması, kendi sorgusunun hiç kullanmadığı bir yapıyı da bakmak zorundadır.

Tahmin burada işe yaramaz; ikisi de yazma yolunu ağırlaştırır ve ikisi de aynı belirtiyi verir. Ayıran ölçüm şudur: bir erişim yolunun dokunduğu kaydı ve baktığı dizini sınıfına göre ayrıştırmak. Birinci nedende bir yazmanın işi büyür ama işin tamamı kendi sınıfına hizmet eder; ikinci nedende iş, kendi sınıfına hizmet etmeyen bir paya bölünür. Bu pay ölçülebilir.

İki Yerleşim

Aşağıdaki kurulum bir model değil, gerçek bir ölçümdür: aynı mantıksal veri iki yerleşime de girilir ve sayımlar deponun kendisinden okunur. Veri Katmanı Ölçekleme kursunun federasyon kararı burada yeniden anlatılmaz; buradaki soru, o kararın hangi belirtiden doğduğudur.

KK6 — ölçek. K01’in günlük hacminin binde biri, otuz günlük dönem üzerinden. Gerekçe: sınıflar arası oranlar K01’in varsayım tablosundan gelir (V3 günde 400.000 gönderi, V4 gönderi başına 7 olay, V13 fatura satırına 100 kalem); bölen yalnız koşum süresini kısaltır, oranları değiştirmez. Duyarlılığı aşağıda sınıf karışımı üzerinden verilir. Bu varsayım K01’in tablosuna eklenmez.

// kalicilik/depo.mjs — ayni mantiksal veri iki yerlesimde kurulur: butun siniflar tek
// tabloda ve her sinif kendi deposunda. node:sqlite ile gercek kurulur; sayimlar SQL'den
// okunur. Olcek K01'in gunluk hacminin binde biridir (KK6), donem 30 gundur.
import { DatabaseSync } from "node:sqlite";

export const KK6 = 1000;                 // olcek bolen
export const GUN = 30;                   // K01 V11: ucretlendirme donemi
export const K01 = { gonderi: 400_000, olayCarpani: 7, faturaKalemi: 100, tepeYazma: 97.22 };

export const SAYI = {
  gonderi: (K01.gonderi / KK6) * GUN,
  olay: ((K01.gonderi * K01.olayCarpani) / KK6) * GUN,
  donem: (K01.gonderi / K01.faturaKalemi / KK6) * GUN,
};

// Veri bir kez uretilir, iki yerlesime de ayni satirlar girer.
export function satirlar() {
  const c = [];
  const gunluk = { gonderi: SAYI.gonderi / GUN, olay: SAYI.olay / GUN, donem: SAYI.donem / GUN };
  for (let g = 1; g <= GUN; g += 1) {
    for (let i = 1; i <= gunluk.gonderi; i += 1) {
      const no = `TK${g}-${i}`;
      c.push({ tur: "gonderi", anahtar: no, satici: null, gun: g });
      for (let k = 1; k <= K01.olayCarpani; k += 1) c.push({ tur: "olay", anahtar: no, satici: null, gun: g });
    }
    for (let s = 1; s <= gunluk.donem; s += 1) c.push({ tur: "donem", anahtar: null, satici: `S${s}`, gun: g });
  }
  return c;
}

// Tek parca yerlesim: tek tablo, butun siniflarin sorgularina hizmet eden dizinler.
export function tekParca(veri) {
  const d = new DatabaseSync(":memory:");
  d.exec(`CREATE TABLE kayit(id INTEGER PRIMARY KEY, tur TEXT, anahtar TEXT, satici TEXT, gun INTEGER, govde BLOB);
          CREATE INDEX ix_anahtar ON kayit(anahtar);
          CREATE INDEX ix_gun ON kayit(gun);
          CREATE INDEX ix_satici ON kayit(satici)`);
  const y = d.prepare("INSERT INTO kayit(tur,anahtar,satici,gun,govde) VALUES(?,?,?,?,?)");
  d.exec("BEGIN");
  for (const r of veri) y.run(r.tur, r.anahtar, r.satici, r.gun, null);
  d.exec("COMMIT");
  return d;
}

// Ayrilmis yerlesim: her sinif kendi deposunda, yalniz kendi sorgusunun dizini.
export function ayrilmis(veri) {
  const gonderi = new DatabaseSync(":memory:");
  const olay = new DatabaseSync(":memory:");
  const donem = new DatabaseSync(":memory:");
  gonderi.exec("CREATE TABLE gonderi(anahtar TEXT PRIMARY KEY, gun INTEGER, govde BLOB)");
  olay.exec(`CREATE TABLE olay(id INTEGER PRIMARY KEY, anahtar TEXT, gun INTEGER, govde BLOB);
             CREATE INDEX ix_olay_anahtar ON olay(anahtar)`);
  donem.exec("CREATE TABLE donem(satici TEXT, gun INTEGER, govde BLOB, PRIMARY KEY(satici,gun)) WITHOUT ROWID");
  const yg = gonderi.prepare("INSERT INTO gonderi VALUES(?,?,?)");
  const yo = olay.prepare("INSERT INTO olay(anahtar,gun,govde) VALUES(?,?,?)");
  const yd = donem.prepare("INSERT INTO donem VALUES(?,?,?)");
  for (const d of [gonderi, olay, donem]) d.exec("BEGIN");
  for (const r of veri) {
    if (r.tur === "gonderi") yg.run(r.anahtar, r.gun, null);
    else if (r.tur === "olay") yo.run(r.anahtar, r.gun, null);
    else yd.run(r.satici, r.gun, null);
  }
  for (const d of [gonderi, olay, donem]) d.exec("COMMIT");
  return { gonderi, olay, donem };
}

// Bir tabloya yazarken bakilan dizin sayisi SQL'den okunur (PRAGMA index_list).
export function dizinSayisi(d, tablo) {
  return d.prepare(`SELECT count(*) n FROM pragma_index_list(?)`).get(tablo).n;
}
// kalicilik/olcum.mjs — uc erisim yolunun iki yerlesimde dokundugu kayit ve bakilan dizin
import { KK6, GUN, K01, SAYI, satirlar, tekParca, ayrilmis, dizinSayisi } from "./depo.mjs";

const veri = satirlar();
const tek = tekParca(veri);
const ayri = ayrilmis(veri);
const sor = (d, s, ...p) => d.prepare(s).get(...p).n;

console.log(`olcek 1/${KK6}, donem ${GUN} gun -> gonderi ${SAYI.gonderi}, olay ${SAYI.olay}, donem ${SAYI.donem}`);
console.log(`tek parca deponun kayit sayisi = ${sor(tek, "SELECT count(*) n FROM kayit")}`);
console.log(`ayrilmis depolarin kayit sayisi = ${sor(ayri.gonderi, "SELECT count(*) n FROM gonderi")}` +
  ` / ${sor(ayri.olay, "SELECT count(*) n FROM olay")} / ${sor(ayri.donem, "SELECT count(*) n FROM donem")}\n`);

// S1 takip okumasi: bir takip numarasinin gonderi kaydi ve olaylari.
const NO = "TK15-100";
const s1Tek = sor(tek, "SELECT count(*) n FROM kayit WHERE anahtar=?", NO);
const s1Ayri = sor(ayri.gonderi, "SELECT count(*) n FROM gonderi WHERE anahtar=?", NO) +
  sor(ayri.olay, "SELECT count(*) n FROM olay WHERE anahtar=?", NO);

// S2 donem taramasi: 30 gunluk satici-gun kayitlari; paylasilan gun dizini butun siniflari gezer.
const s2Tek = sor(tek, "SELECT count(*) n FROM kayit WHERE gun BETWEEN 1 AND ?", GUN);
const s2Ayri = sor(ayri.donem, "SELECT count(*) n FROM donem WHERE gun BETWEEN 1 AND ?", GUN);

console.log("yol                     tek parca  ayrilmis  oran  gereken");
console.log("--------------------- ----------- --------- ----- --------");
const satir = (ad, a, b, gereken) => console.log(`${ad.padEnd(21)} ${String(a).padStart(11)} ${String(b).padStart(9)} ` +
  `${(a / b).toFixed(2).padStart(5)} ${String(gereken).padStart(8)}`);
satir("S1 takip okumasi", s1Tek, s1Ayri, 4);
satir("S2 donem taramasi", s2Tek, s2Ayri, SAYI.donem);

// S3 durum olayi yazmasi: bakilan dizin ve kendi sorgularina hizmet eden dizin.
const dTek = dizinSayisi(tek, "kayit");
const dAyri = dizinSayisi(ayri.olay, "olay");
console.log(`\nS3 durum olayi yazmasi: tek parcada ${dTek} dizin bakiliyor, ayrilmis depoda ${dAyri}`);
console.log(`bunlarin kendi siniflarinin sorgusuna hizmet edeni: 1 / ${dTek} = ${(1 / dTek).toFixed(2)}` +
  ` ve 1 / ${dAyri} = ${(1 / dAyri).toFixed(2)}`);
console.log(`K01 tepe yazma ${K01.tepeYazma} istek/s -> dizin bakimi ${(K01.tepeYazma * dTek).toFixed(2)}/s ve` +
  ` ${(K01.tepeYazma * dAyri).toFixed(2)}/s, fark ${(K01.tepeYazma * (dTek - dAyri)).toFixed(2)}/s`);

// Ayirmanin karsiligi: yazma yolu iki depoya uzuyor (olay kutugu + gonderinin son durumu).
const ayirmaYazma = 2, tekYazma = 1;
console.log(`\nayirmanin karsiligi: bir durum olayi ${tekYazma} depo yerine ${ayirmaYazma} depoya yaziliyor` +
  ` -> ${(K01.tepeYazma * ayirmaYazma).toFixed(2)} depo yazmasi/s (tek parcada ${(K01.tepeYazma * tekYazma).toFixed(2)})`);
console.log(`isletilen depo sayisi 1 -> 3; atomik olmayan yazma yolu ${K01.tepeYazma.toFixed(2)} istek/s`);

// Saklama kurali: tek kural butun siniflara uygulanir (K01 V12 = 730 gun, donem 30 gun yeter).
const [SAKLAMA, DONEM_GEREK] = [730, GUN];
const donemGunluk = SAYI.donem / GUN;
console.log(`\nsaklama: tek kural 730 gun -> donem kaydi ${SAKLAMA} gun tutuluyor, gereken ${DONEM_GEREK} gun`);
console.log(`olcekte tutulan donem kaydi ${donemGunluk * SAKLAMA} yerine ${donemGunluk * DONEM_GEREK}` +
  ` -> ${(SAKLAMA / DONEM_GEREK).toFixed(2)} kat fazla`);

// KK6'nin duyarliligi: sinif karisimi degisirse S2'nin gecis carpani ne olur.
console.log(`\nsinif karisimi (K01 V13 = fatura satirina kalem) -> S2 gecis carpani`);
for (const kalem of [100, 20, 5, 1]) {
  const d = (K01.gonderi / kalem / KK6) * GUN;
  const toplam = SAYI.gonderi + SAYI.olay + d;
  console.log(`  kalem ${String(kalem).padStart(3)} -> donem kaydi ${String(d).padStart(6)},` +
    ` donem payi ${(d / toplam).toFixed(4)}, carpan ${(toplam / d).toFixed(2)}`);
}
olcek 1/1000, donem 30 gun -> gonderi 12000, olay 84000, donem 120
tek parca deponun kayit sayisi = 96120
ayrilmis depolarin kayit sayisi = 12000 / 84000 / 120

yol                     tek parca  ayrilmis  oran  gereken
--------------------- ----------- --------- ----- --------
S1 takip okumasi                8         8  1.00        4
S2 donem taramasi           96120       120 801.00      120

S3 durum olayi yazmasi: tek parcada 3 dizin bakiliyor, ayrilmis depoda 1
bunlarin kendi siniflarinin sorgusuna hizmet edeni: 1 / 3 = 0.33 ve 1 / 1 = 1.00
K01 tepe yazma 97.22 istek/s -> dizin bakimi 291.66/s ve 97.22/s, fark 194.44/s

ayirmanin karsiligi: bir durum olayi 1 depo yerine 2 depoya yaziliyor -> 194.44 depo yazmasi/s (tek parcada 97.22)
isletilen depo sayisi 1 -> 3; atomik olmayan yazma yolu 97.22 istek/s

saklama: tek kural 730 gun -> donem kaydi 730 gun tutuluyor, gereken 30 gun
olcekte tutulan donem kaydi 2920 yerine 120 -> 24.33 kat fazla

sinif karisimi (K01 V13 = fatura satirina kalem) -> S2 gecis carpani
  kalem 100 -> donem kaydi    120, donem payi 0.0012, carpan 801.00
  kalem  20 -> donem kaydi    600, donem payi 0.0062, carpan 161.00
  kalem   5 -> donem kaydi   2400, donem payi 0.0244, carpan 41.00
  kalem   1 -> donem kaydi  12000, donem payi 0.1111, carpan 9.00

Kayıt ve dizin sayıları ölçüm sınıfındadır: depodan okunmuşlardır. K01’e çevrilen satırlar hesap, ölçek ve sınıf karışımı varsayım sınıfındadır.

Okuma Yolu Hiçbir Şey Göstermiyor

Tablonun ilk satırı tanının en önemli parçasıdır. S1 takip okuması iki yerleşimde de 8 kayda dokunuyor; oran 1,00. Tek parça kalıcılık bu yolda görünmez. Bir tanıyı yalnız takip sorgusunun ölçümüyle kurmaya çalışan biri, deponun tek olmasının bir bedeli olmadığı sonucuna varır ve yanılır. İki yerleşim de gerekenden fazlasını okuyor (8 kayıt, gereken 4) — ama bu, bu dersin değil dördüncü dersin konusudur.

İkinci satır ayrımı veriyor. Dönem taraması ayrılmış depoda 120 kayda dokunuyor, yani tam olarak gereken kadarına. Tek parça depoda paylaşılan gun dizini bütün sınıfların satırlarını aynı sıraya dizdiği için tarama 96.120 kaydın üstünden geçiyor: kendi sınıfının 801 katı. Bu sayı deponun yavaş olmasından değil, taramanın kendi sınıfına ait olmayan kayıtları geçmek zorunda kalmasından geliyor.

Üçüncü ölçüm belirtiyi doğrudan karşılıyor. Tek parça depoda bir durum olayı yazması üç dizin bakar; ayrılmış depoda bir. Bu üç dizinin yalnız biri (ix_anahtar) olayın kendi sorgusuna hizmet eder, ikisi dönem sınıfı için oradadır: hizmet oranı 0,33. K01’in 97,22 istek/s tepe yazmasında bu, saniyede 291,66 dizin bakımı demektir; ayrılmış yerleşimde 97,22. Aradaki 194,44 bakım, hiçbir sorgunun kullanmadığı iştir.

Sayının işaret ettiği neden birincisi değil ikincisidir: kayıt hiç büyümemiştir, büyüyen yalnız kendi sınıfına hizmet etmeyen paydır. Ayrımı yapan ölçüm budur.

Tek Deponun Doğru Olduğu Koşul

Tek depo her ölçekte yanlış değildir. Son tablo sınırı sayıyla veriyor: geçiş çarpanı sınıf karışımına bağlıdır. Dönem kayıtları toplamın 0,0012’siyken çarpan 801, 0,0244’üyken 41, 0,1111’iyken 9’a iniyor. Yani tek depo, hiçbir sınıfın payı ötekilerin yanında kaybolmadığı sürece savunulabilir bir seçimdir; çarpan bire yaklaştıkça ayırmanın kazandıracağı bir şey kalmaz.

İkinci koşul dizin tarafındadır. Üç dizinin hepsi aynı sınıfa hizmet etseydi hizmet oranı 1,00 olurdu ve yazma tarafında ödenen hiçbir bedel bulunmazdı. Bedel dizin sayısından değil, başka sınıf için konmuş dizin sayısından doğar.

Üçüncü koşul saklamadır. K01’in V12 varsayımı bütün kayıtları 730 gün tutar, oysa dönem kaydının işlevi otuz günlük dönem kapandığında biter. Tek depo tek bir saklama kuralı taşıdığı için dönem kayıtları 24,33 kat fazla tutuluyor. Sınıfların saklama süreleri eşitse bu koşul da düşer.

Üçü birden sağlanıyorsa — paylar birbirine yakın, dizinler ortak, saklama tek — tek depo yalnız doğru değil, ucuzdur da.

Ayırmanın Karşılığında Büyüyen

Ayırma bedava değildir ve bedeli yazma yolundadır. Tek depoda bir durum olayı tek bir yazmadır: olay kütüğüne eklenir ve gönderinin son durumu aynı işlemde güncellenir. Sınıflar ayrıldığında aynı olay iki depoya yazılır; K01’in tepe yazmasında bu, saniyede 97,22 yerine 194,44 depo yazması demektir. Daha ağır olan şey sayı değil: iki yazma artık tek bir işlem değildir. Aradaki tutarlılık, Dayanıklılık ve Güvenilirlik kursunun telafi edici işlem ve etkisiz işlem derslerinde kurulan düzene devredilir — saniyede 97,22 isteklik bir yazma yolu, atomikliğini kaybeder.

İkinci karşılık işletme yüzeyidir: bir depo yerine üç depo yedeklenir, sürümlenir, izlenir ve devralınır. Bu, bir sonraki konunun sayacağı yükün doğrudan girdisidir.

Özet

  • Belirti yazma başına işin büyümesidir; iki nedeni vardır — kayıt büyümüştür ya da depo başka bir sınıfla paylaşılmaktadır. Ayıran ölçüm, işin kendi sınıfına hizmet eden payıdır.
  • Takip okuması iki yerleşimde de 8 kayda dokunuyor (oran 1,00): tek parça kalıcılık okuma yolunda görünmez ve yalnız o yolla tanı konamaz.
  • Dönem taraması ayrılmış depoda 120, tek parça depoda 96.120 kayıttan geçiyor — kendi sınıfının 801 katı; çarpanın kaynağı sınıf karışımıdır.
  • Bir durum olayı yazması tek parça depoda 3 dizin bakar ve yalnız 1’i kendi sorgusuna hizmet eder (0,33); K01’in 97,22 istek/s yazmasında saniyede 194,44 bakım hiçbir sorguya yaramaz.
  • Tek depo üç koşul sağlandığında doğrudur: sınıf payları birbirine yakın (çarpan 801 değil 9), dizinler ortak (hizmet oranı 1,00) ve saklama tek (dönem kaydı 24,33 kat fazla tutulmuyor).
  • Ayırmanın karşılığı yazma yolunun uzamasıdır: saniyede 97,22 yerine 194,44 depo yazması ve atomikliğini kaybeden bir yazma yolu; işletilen depo sayısı 1’den 3’e çıkar.

Sonraki Adım

Bu ders deponun yapısını sorguladı ve bir yazmanın kendi sınıfına hizmet etmeyen payını saydı. Bir sonraki soru okuma tarafındadır ve önce yanıtlanmış gibi görünür: Veri Katmanı Ölçekleme kursunun önbellek konusu, önbelleğin gerekmediği yeri ölçmüştü — tekrar oranı 1,00 olan bir taramaya önbellek koymanın istek başına 2,00 ek dokunuşa mal olduğunu gösterdi. Sonraki ders o ölçümün aynadaki yüzüdür: önbelleğin gerektiği hâlde konmadığı yer. Ama ölçülecek şey aynı değildir. Orada yinelenen bir okuma sayıldı; burada yinelenen bir hesap sayılır ve bu ikisi aynı akışta ayrışabilir: okuma tekrarı düşük bir akışın hesap tekrarı çok yüksek olabilir. Sonraki ders bu ayrımı ölçer, hesabın tekrar oranını hesap anahtarı üzerinden sayar ve anahtarı eksik seçmenin neye mal olduğunu 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