İçeriğe geç
academia.sh

Ders 01 / 16

Meşgul Veritabanı

Aynı belirtiyi veren iki karşıt nedenin bir ölçümle ayrılması: depo katmanı doluyken uygulama katmanının boşta kalması, azaltmayan işin depoda bırakılması ile azaltan işin uygulamaya taşınmasının aynı belirtiyi vermesi, sınır oranının düzeltmenin yönünü seçmesi ve tek bir sayının hangi durumda yanlış planı önerdiği.

İçindekiler

Önceki kurs yirmi kalıbı iki ayrı günün sayısıyla savundu, ama hepsi aynı varsayımın üstünde durdu: arıza fark edilmiş olacak. Devre kesici bir eşiğin aşıldığını, devralma bir kopyanın düştüğünü bilecekti. Farkın nasıl edileceği tasarlanmadı. Bu kurs oradan başlar ve arızadan değil, arızasız günün kendisinden: bir sistem hiç bozulmadan da kötüleşir, kötüleşmenin adı konmadan da düzeltilemez.

Bu dersin ve ardından gelen dokuzun yürüttüğü yol üç adımdır. Belirti bir sayıdır; “yavaş” bir belirti değildir. Ayırt etme, aynı belirtiyi veren en az iki nedeni birbirinden ayıran bir ölçümdür; ölçüm yoksa yapılan şey tahmindir ve bu kursta tahmin diye adlandırılır. Düzeltme, kalıbı giderir ve karşılığında neyin büyüdüğünü sayar. Üçü tamamlanmadan bir karşıt kalıp (antipattern) tanınmış olmaz. Ve hiçbir karşıt kalıp her ölçekte yanlış değildir; her ders aynı biçimin doğru olduğu koşulu bir sayıyla sınırlar.

Belirti: Bir Katman Dolu, Öteki Boş

Belirti gün sonu ücretlendirme akışında görünüyor. K01’in kabaca büyüklük hesabı bu işi otuz günün 12.000.000 kaydını dört saatlik pencerede taramak olarak yazmıştı: saniyede 833,33 kayıt. İş pencereye sığmıyor. Aynı anda iki doluluk okuması var ve ikisi ters yönde: depo katmanının işlemci doluluğu tepe yapıyor, uygulama düğümlerinin doluluğu düşük kalıyor. Doluluk M19/K01 temel-nitelikler/01’de tanımlandı, burada yeniden tanımlanmaz.

İlk refleks bir tahmindir ve tahmin olduğu için adı konur: “depo yetmiyor, daha büyük makine gerekiyor.” Bu cümlenin tehlikesi yanlış olması değil, yönsüz olmasıdır. Aşağıdaki iki neden aynı belirtiyi verir ve düzeltmeleri birbirinin tersidir; yön seçilmeden yapılan her müdahalenin yarısı belirtiyi kötüleştirir.

Neden A — azaltmayan iş depoda. Depo, sonuç kayıt sayısını hiç değiştirmeyen bir iş yapıyor: satır başına dize üretimi, biçimlendirme, sunum için kod çözümü. Girdi kaydı ile çıktı kaydı eşit; harcanan işlemci işi sınırdan geçen veriyi bir bayt bile azaltmıyor.

Neden B — azaltan iş uygulamada. Uygulama ham kayıtları çekip toplamayı kendi yapıyor. Depo satır başına hiç hesap yapmıyor, ama on iki milyon kaydı okuyup seri hâle getirip göndermek zorunda. Depo yine dolu, işin cinsi başka.

Meşgul veritabanı (busy database) bu iki nedenin ortak adıdır: hesabın yanlış katmanda yapılması. Adın tek başına bir işe yaramadığı buradan görünüyor — ad iki nedeni de kapsıyor, düzeltme ise nedeni bilmeyi gerektiriyor.

Dört Plan, Tek Düzenek

Ayırt etme için aynı gün sonu işi dört planla koşuluyor. İki istek var: satıcının kalem dökümü (her gönderi bir satır) ve fatura satırı (satıcı-gün başına bir satır, K01’in V13 = 100 kalem oranı). Her istek iki katman seçimiyle yazılıyor.

Ölçüm gerçek bir motorda yapılıyor ve model ölçeği M19/K04’ün ölçeğidir: 20 satıcı, 30 gün, günde 100 gönderi. Sayılan üç şey var — sınırdan geçen kayıt, iki katmanda yapılan satır işi (satır başına çalıştırılan hesap sayısı, motora kayıtlı bir sayaç işleviyle sayılıyor) ve sınırdan geçen bayt. Süre ölçülmüyor: süre bu makineye bağlıdır, sayılan üç nicelik değildir.

// katman/plan.mjs — ayni gun sonu isinin dort plani: sinirdan gecen kayit, sinir orani,
// iki katmanda yapilan satir isi ve tasinan bayt. node:sqlite ile gercek bir motor kosar.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, TARIFE = 40, BOLGE = 12;   // M19/K04 model olcegi
const N = SATICI * GUN * GUNLUK, FATURA = SATICI * GUN;               // 60000 gonderi, 600 fatura satiri
const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INT, gun INT, durum INT,
  agirlik REAL, oran REAL, katsayi REAL, indirim REAL);`);
db.exec("BEGIN");
const ekle = db.prepare("INSERT INTO gonderi VALUES(?,?,?,?,?,?,?,?)");
for (let s = 0; s < SATICI; s += 1) for (let d = 0; d < GUN; d += 1) for (let k = 0; k < GUNLUK; k += 1) {
  const ti = (s * 7 + d + k) % TARIFE, bi = (s + k) % BOLGE;
  ekle.run(`TR-${s}-${d}-${k}`, s, d, k % 5, 1 + (k % 30),
    12 + (ti % 7) * 0.5, 1 + (bi % 5) * 0.1, (s % 9) * 0.01);
}
db.exec("COMMIT");

const TUTAR = "agirlik * oran * katsayi * (1 - indirim)";
const DURUM = "CASE durum WHEN 0 THEN 'kabul' WHEN 1 THEN 'aktarma' WHEN 2 THEN 'dagitimda' " +
  "WHEN 3 THEN 'teslim' ELSE 'bekliyor' END";
const AD = ["kabul", "aktarma", "dagitimda", "teslim", "bekliyor"];
const SQL = (i) => ({                                    // i: satir isini sayan sarmalayici
  D1: `SELECT no, ${i(`printf('%s / %02d. gun', no, gun)`)} AS baslik,
    ${i(`printf('%.2f', ${i(TUTAR)})`)} AS tutar, ${i(`printf('%.1f kg', agirlik)`)} AS agirlik,
    ${i(DURUM)} AS durum FROM gonderi`,
  D2: `SELECT no, gun, ${i(TUTAR)} AS tutar, agirlik, durum FROM gonderi`,
  F1: `SELECT satici, gun, count(*) AS kalem, sum(${i(TUTAR)}) AS tutar FROM gonderi GROUP BY satici, gun`,
  F2: `SELECT satici, gun, agirlik, oran, katsayi, indirim FROM gonderi`,
});
const UYGULAMA = {                                       // uygulama katmaninda yapilan satir isi
  D1: (r) => 0,
  D2: (r) => { let c = 0; for (const x of r) { c += 4;
    void [`${x.no} / ${String(x.gun).padStart(2, "0")}. gun`, x.tutar.toFixed(2),
      `${x.agirlik.toFixed(1)} kg`, AD[x.durum]]; } return c; },
  F1: (r) => { let c = 0; for (const x of r) { c += 2; void [x.tutar.toFixed(2), `${x.kalem} kalem`]; }
    return c; },
  F2: (r) => { const t = new Map(); let c = 0;
    for (const x of r) { c += 1; const k = `${x.satici}/${x.gun}`;
      const v = t.get(k) ?? { kalem: 0, tutar: 0 }; v.kalem += 1;
      v.tutar += x.agirlik * x.oran * x.katsayi * (1 - x.indirim); t.set(k, v); }
    for (const v of t.values()) { c += 2; void [v.tutar.toFixed(2), `${v.kalem} kalem`]; } return c; },
};
const GEREKLI = { D1: N, D2: N, F1: FATURA, F2: FATURA };   // istegin sonucundaki kayit sayisi

let sayac = 0;
db.function("i", (x) => { sayac += 1; return x; });
const olcu = {};
for (const [ad, sorgu] of Object.entries(SQL((e) => `i(${e})`))) {
  sayac = 0;
  const satir = db.prepare(sorgu).all();
  const depo = sayac;
  const bayt = Buffer.byteLength(JSON.stringify(satir.map((r) => Object.values(r))));
  olcu[ad] = { gecen: satir.length, gerekli: GEREKLI[ad], oran: satir.length / GEREKLI[ad],
    depo, uygulama: UYGULAMA[ad](satir), bayt };
}

const PLAN = ["D1", "D2", "F1", "F2"];
const ETIKET = { D1: "D1 dokum, bicim depoda", D2: "D2 dokum, bicim uygulamada",
  F1: "F1 fatura, toplama depoda", F2: "F2 fatura, toplama uygulamada" };
const say = (x) => x.toLocaleString("tr-TR");
console.log(`model: ${say(N)} gonderi, ${SATICI} satici, ${GUN} gun; dokum istegi ${say(N)} satir, ` +
  `fatura istegi ${FATURA} satir dondurur; taranan kayit dort planda da ${say(N)}`);
console.log(`\n${"plan".padEnd(28)}${"gecen kayit".padStart(13)}${"gerekli".padStart(10)}` +
  `${"sinir orani".padStart(13)}${"depoda satir isi".padStart(18)}${"uygulamada".padStart(12)}` +
  `${"gecen bayt".padStart(12)}${"kayit basina".padStart(14)}`);
for (const p of PLAN) {
  const o = olcu[p];
  console.log(ETIKET[p].padEnd(28) + say(o.gecen).padStart(13) + say(o.gerekli).padStart(10) +
    o.oran.toFixed(2).padStart(13) + say(o.depo).padStart(18) + say(o.uygulama).padStart(12) +
    say(o.bayt).padStart(12) + (o.bayt / o.gecen).toFixed(1).padStart(14));
}

const OLCEK = 12_000_000 / N, PENCERE = 4 * 3600;        // K01: 30 gunun 12.000.000 kaydi, 4 saat
console.log(`\nK01 olceginde (olcek ${OLCEK}, pencere ${PENCERE} s, tarama ` +
  `${(12e6 / PENCERE).toFixed(2)} kayit/s); sinir bandi F2 = K01'in 6 Mbit/s'i olarak olceklendi`);
console.log(`${"plan".padEnd(28)}${"depoda satir isi/s".padStart(20)}${"uygulamada/s".padStart(14)}` +
  `${"sinir bandi Mbit/s".padStart(20)}`);
for (const p of PLAN) {
  const o = olcu[p];
  console.log(ETIKET[p].padEnd(28) + ((o.depo * OLCEK) / PENCERE).toFixed(2).padStart(20) +
    ((o.uygulama * OLCEK) / PENCERE).toFixed(2).padStart(14) +
    ((6 * o.bayt) / olcu.F2.bayt).toFixed(3).padStart(20));
}

console.log(`\nKK1 (depoda bir satir isi uygulamadakinin k katina mal olur) ile plan cifti secimi`);
console.log(`${"plan cifti".padEnd(14)}${"k=2".padStart(12)}${"k=6".padStart(12)}${"k=12".padStart(12)}` +
  `${"gecen kayit".padStart(14)}${"sinir bandi Mbit/s".padStart(20)}`);
for (const [a, b] of [["D1", "F1"], ["D2", "F1"], ["D1", "F2"], ["D2", "F2"]]) {
  const depo = olcu[a].depo + olcu[b].depo, uyg = olcu[a].uygulama + olcu[b].uygulama;
  const bayt = olcu[a].bayt + olcu[b].bayt;
  console.log(`${a} + ${b}`.padEnd(14) + [2, 6, 12].map((k) => say(depo * k + uyg).padStart(12)).join("") +
    say(olcu[a].gecen + olcu[b].gecen).padStart(14) + ((6 * bayt) / olcu.F2.bayt).toFixed(3).padStart(20));
}
model: 60.000 gonderi, 20 satici, 30 gun; dokum istegi 60.000 satir, fatura istegi 600 satir dondurur; taranan kayit dort planda da 60.000

plan                          gecen kayit   gerekli  sinir orani  depoda satir isi  uygulamada  gecen bayt  kayit basina
D1 dokum, bicim depoda             60.000    60.000         1.00           300.000           0   3.991.401          66.5
D2 dokum, bicim uygulamada         60.000    60.000         1.00            60.000     240.000   1.988.049          33.1
F1 fatura, toplama depoda             600       600         1.00            60.000       1.200      13.407          22.3
F2 fatura, toplama uygulamada       60.000       600       100.00                 0      61.200   1.302.519          21.7

K01 olceginde (olcek 200, pencere 14400 s, tarama 833.33 kayit/s); sinir bandi F2 = K01'in 6 Mbit/s'i olarak olceklendi
plan                          depoda satir isi/s  uygulamada/s  sinir bandi Mbit/s
D1 dokum, bicim depoda                   4166.67          0.00              18.386
D2 dokum, bicim uygulamada                833.33       3333.33               9.158
F1 fatura, toplama depoda                 833.33         16.67               0.062
F2 fatura, toplama uygulamada                0.00        850.00               6.000

KK1 (depoda bir satir isi uygulamadakinin k katina mal olur) ile plan cifti secimi
plan cifti             k=2         k=6        k=12   gecen kayit  sinir bandi Mbit/s
D1 + F1            721.200   2.161.200   4.321.200        60.600              18.448
D2 + F1            481.200     961.200   1.681.200        60.600               9.220
D1 + F2            661.200   1.861.200   3.661.200       120.000              24.386
D2 + F2            421.200     661.200   1.021.200       120.000              15.158

Tablonun sayıları ölçüm sınıfındadır; K01 ölçeğine çevrilmiş satırlar hesap sınıfındadır. Sınır bandı sütununda mutlak bir bayt iddiası yoktur: modelin ham satırı K01’in 900 baytlık gönderi kaydından dardır, bu yüzden band F2 planı K01’in yazdığı 6 Mbit/s’e eşitlenerek ölçeklendi. Taşınabilir olan planların birbirine oranıdır.

Sınır Oranı Yönü Seçiyor

Üçüncü sütun tanının kendisidir. Sınır oranı, bir isteğin sınırdan geçirdiği kayıt sayısının o isteğin sonucunda gerçekten bulunan kayıt sayısına bölümüdür. Dört planın üçünde 1,00; F2’de 100,00.

Oran 1,00 olduğunda depo zaten azaltmıştır ve depo yine de doluysa suçlu azaltmayan iştir. D1 bunu tam olarak gösteriyor: sınır oranı 1,00 ama depoda satır işi 300.000, yani kayıt başına beş. D2 aynı sonucu üretiyor ve depodaki iş 60.000’e, kayıt başına bire iniyor. Fark biçimlendirme işidir ve o iş sınırdan geçen kaydı bir tane bile azaltmıyor — üstelik artırıyor: geçen bayt 3.991.401’den 1.988.049’a, kayıt başına 66,5 bayttan 33,1 bayta iniyor. Depoda üretilen dizeler, üretildikleri sayılardan geniştir. Düzeltmenin yönü yukarıdır.

Oran 1’in çok üstünde olduğunda azaltma yanlış katmandadır. F2’de depoda satır işi sıfır — depo hiç hesap yapmıyor — ama 60.000 kaydı okuyup seri hâle getirip gönderiyor, oysa isteğin sonucu 600 satır. F1 aynı sonucu 600 kayıtla veriyor ve sınır bandını 6,000’den 0,062 Mbit/s’e indiriyor: 96,8 kat. Düzeltmenin yönü aşağıdır.

İki neden de “depo dolu, uygulama boşta” belirtisini verir, ama biri işi yukarı, öteki aşağı taşımayı ister. Sınır oranı bu iki durumu tek bir sayıyla ayırır ve ayırdığı şey düzeltmenin yönüdür.

Tek Sayı Tanı Vermiyor

Yönü bulmak planı seçmeye yetmez. Son tablo bir varsayımla plan çiftlerini karşılaştırıyor.

KK1 — depoda yapılan bir satır işi, uygulamada yapılan bir satır işinin kk katına mal olur; temel değer k=6k = 6. Gerekçe: uygulama katmanı durumsuzdur ve düğüm eklenerek yatay ölçeklenir (M19/K01 temel-nitelikler/03), depo katmanında ise yazma tek noktadadır ve çekirdek eklemek makine değiştirmektir. İki katmandaki bir birim iş aynı fiyata alınmaz. Duyarlılık k=2k = 2 ve k=12k = 12 sütunlarında verildi.

Ağırlıklı satır işi ölçüsü üç kk değerinde de aynı çifti öneriyor: D2 + F2, 661.200 birimle (k = 6). Bu öneri yanlıştır. Aynı satırda sınırdan geçen kayıt 120.000, sınır bandı 15,158 Mbit/s; D2 + F1 çiftinde ise 60.600 kayıt ve 9,220 Mbit/s. Satır işi ölçüsü F2’yi ucuz sanıyor, çünkü F2’nin depoya yüklediği iş satır işi değil — okuma, seri hâle getirme ve taşımadır ve o sütunda hiç görünmüyor.

Kursun ilk kuralı budur: bir belirti tek bir sayıyla açıklanmaz. Depo iki ayrı para biriminde meşgul olur — satır başına hesap ve sınırdan geçen bayt — ve bir plan yalnız birine bakılarak seçilirse öteki büyür. Doğru seçim D2 + F1’dir: her iki istekte de gereksiz iş katmandan çıkarılmıştır.

Depoda Kalmanın Doğru Olduğu Koşul

Hesabın depoda yapılması bir yasak değildir; F1 tam olarak bunu yapıyor ve dört planın en ucuzudur. Koşul ölçülebilir: depoda yapılan iş, sınırdan geçen kaydı azalttığı ölçüde haklıdır. F1’de 60.000 satır işi, sınırdan geçen kaydı 60.000’den 600’e indiriyor — kayıt başına 100 birim azaltma, K01’in V13 = 100 oranının kendisi. D1’de 240.000 fazladan satır işi sınırdan geçen kaydı hiç azaltmıyor, baytı 2,01 katına çıkarıyor.

Koşul bir ölçekle sınırlıdır. Fatura satırı başına düşen kalem sayısı 100’den 1’e inseydi — yani her gönderi kendi fatura satırını doğursaydı — F1 ile F2 arasındaki 96,8 katlık band farkı kaybolur ve toplamayı depoda yapmanın hiçbir karşılığı kalmazdı. Aynı biçim, aynı sistemde, başka bir kırılım oranında yanlış tarafa geçer.

Düzeltmenin Karşılığında Büyüyen

D1’den D2’ye geçiş depodaki satır işini 300.000’den 60.000’e indirdi; uygulamadaki satır işi 0’dan 240.000’e çıktı. K01 ölçeğinde bu, depoda saniyede 4166,67 satır işinden 833,33’e inmek ve uygulamada 0’dan 3333,33’e çıkmaktır. KK1’in k=6k = 6 değerinde takas kârlıdır, k=1k = 1 olsaydı olmazdı — yani düzeltmenin gerekçesi katmanların ölçekleme asimetrisidir, işin kendisi değil.

İkinci karşılık koddadır ve sayılmaz ama görülür: biçimlendirme kuralı depodan çıkınca sorgunun her tüketicisi onu kendi tarafında tekrar yazmak zorunda kalır. Depoda tek bir yerde duran kural, uygulamada tüketici sayısı kadar yerde durur. Bu bedel D2’yi yanlış yapmaz; bedelin adının konmamış olması yanlış yapar.

Özet

  • Tanı üç adımdır: belirti bir sayıdır, ayırt etme aynı belirtiyi veren iki nedeni ayıran bir ölçümdür, düzeltme karşılığında büyüyeni sayar. Ölçümsüz yapılan şey tahmindir.
  • Meşgul veritabanının iki nedeni aynı belirtiyi verir: azaltmayan iş depoda (D1) ve azaltan iş uygulamada (F2). Düzeltmeleri birbirinin tersidir.
  • Sınır oranı yönü seçer: 1,00 ise iş yukarı taşınır (D1 → D2, geçen bayt 3.991.401 → 1.988.049), 1’in çok üstündeyse aşağı indirilir (F2 → F1, band 6,000 → 0,062 Mbit/s, 96,8 kat).
  • Tek sayı tanı vermez: ağırlıklı satır işi ölçüsü D2 + F2’yi öneriyor (661.200, k = 6), oysa o çift sınırdan 120.000 kayıt ve 15,158 Mbit/s geçiriyor; doğru seçim D2 + F1’dir.
  • Hesabın depoda kalması, sınırdan geçen kaydı azalttığı ölçüde haklıdır: F1’de 60.000 satır işi kaydı 100 kat azaltıyor. Kırılım oranı 1’e inseydi aynı seçim yanlışa dönerdi.
  • Düzeltmenin karşılığı uygulama katmanında büyüyor: satır işi 0 → 240.000 (K01 ölçeğinde saniyede 0 → 3333,33) ve biçimlendirme kuralı tek yerden tüketici sayısı kadar yere dağılıyor.

Sonraki Adım

Sınır oranı, işin hangi katmanda yapılacağını iki katman için çözdü: depo ve uygulama. Oysa zincirin bir ucu daha var ve orası hiç ölçülmedi. D2 planı biçimlendirmeyi depodan çıkardı ve uygulamaya koydu; aynı iş bir adım daha ileri gidip istemciye de konabilir — sunucu ham kaydı gönderir, tarayıcı biçimlendirir. Bu, aynı takasın üçüncü katmanıdır ve orada ölçünün adı değişir: uygulama katmanında iş düğüm eklenerek dağıtılır, istemcide dağıtılacak bir düğüm yoktur. Sonraki ders bu katmanı ele alır: sunucunun gönderdiği yükün istemcideki işi nasıl büyüttüğü, aynı yavaşlık belirtisinin sunucu kaynaklı mı istemci kaynaklı mı olduğunu ayıran ölçüm ve yükü küçültmenin karşılığında sunucuda büyüyen iş.

İ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