Ders 08 / 10
Veri Ambarı ve Dönüştürme Hatları
Çözümleme yükünün işletim yükünden ayrılması: beş raporun işletim sistemine günde 163 sorgu ve 5.354.308 okunan kayıt bindirmesi, ambarın bu yükü tazelik gecikmesi karşılığında devralması, sekiz adımlık dönüştürme hattının bir çevrimde 523.341 kayıt taşıması ve kaynak şemasındaki tek bir alan değişiminin bir adımı kırıp dört adımı durdurarak beş raporun beşini birden düşürmesi.
İçindekiler
Önceki ders aynı varlığı birden çok sistemin yazdığı durumu ele aldı: bir kaynak sistem seçildi ve kayıtlar arasındaki tutarsızlık sayıldı. Orada bütün taraflar veriyi yazıyordu. Bu derste hiç yazmayan bir taraf var. Bölgesel kütüphane ağının yönetim birimi yalnız soru sorar: hangi şube doluyor, hangi eser gecikmede, üyelik nasıl akıyor. Soru veriyi değiştirmez ama bedelsiz de değildir; her soru bir sorguya, her sorgu okunan kayda dönüşür ve o kayıtlar ödünç işlemlerini yürüten sistemin diskinde durur.
Bu ders çözümleme yükünün işletim yükünden ayrılmasını ölçer. Ağ, sahipleri ve bütçeleri ayrı olan bir kurgudur.
Soru Soran Taraf Sayılabilir Bir Yük Getirir
Modelde dört kaynak vardır ve dördünün sahibi ayrıdır: ödünç servisi, üyelik sistemi, dışarıdan alınmış katalog ve şube sistemleri. Yönetim biriminin beş sorusu bunları okur.
TM10: kayıt sayıları, koşum sıklıkları ve tazelik gereksinimleri kurgudur ve hiçbir gerçek kurumdan alınmamıştır. TM11: bir raporun bir varlığa dokunması bir sorgudur; olay varlığı pencereye göre, boyut varlığı tam taranır. TM14: okuduğu alanı bulamayan adım kırılır; girdisi sağlam olmayan adım durur.
// ambar/model.mjs — bolgesel kutuphane aginin kurgu modeli ve donusturme hatti. Kayitlar // gercekten uretilir; sonraki bloklarin sayilari bu dizileri tarayarak olculur. export const TOHUM = 7391, GUN = 180, SON = GUN * 24 - 1; export const SAHIP = { odunc: "odunc servisi (iceride)", uye: "uyelik sistemi", eser: "katalog (disaridan alinmis)", sube: "sube sistemleri" }; export function uret(t = TOHUM) { let s = t; const r = () => (s = (s * 48271) % 2147483647) / 2147483647, n = (k) => Math.floor(r() * k); const dizi = (k, f) => Array.from({ length: k }, (_, i) => f(i)); return { sube: dizi(12, (i) => ({ id: i, bolge: `b${i % 4}` })), eser: dizi(128_000, (i) => ({ id: i, tur: ["kitap", "sureli", "isitsel"][n(3)], degisim: n(SON) })), uye: dizi(46_000, (i) => ({ id: i, kayit_gun: n(GUN), sube_id: n(12), degisim: n(SON) })), odunc: dizi(240_000, (i) => ({ id: i, uye_id: n(46_000), eser_id: n(128_000), sube_id: r() < 0.008 ? null : n(12), gun: n(GUN), saat: n(24) })), }; } // Raporlar: varlik -> pencere (gun) ya da tam tarama; gunluk kosum; kabul edilen en buyuk // gecikme (saat); ambardaki dayanak; kirilmayi belirleyen alan. export const RAPORLAR = { doluluk: { okur: { odunc: 1, sube: "tam" }, kosum: 48, tazelik: 1, dayanak: "ozet-sube", alan: "odunc.sube_id" }, "gecikmis-eser": { okur: { odunc: 30, eser: "tam" }, kosum: 24, tazelik: 4, dayanak: "zengin", alan: "eser.tur" }, "yeni-uye": { okur: { uye: "tam" }, kosum: 12, tazelik: 14, dayanak: "ozet-uye", alan: "uye.kayit_gun" }, etkinlik: { okur: { odunc: 90, uye: "tam" }, kosum: 2, tazelik: 36, dayanak: "ozet-uye", alan: "uye.kayit_gun" }, devir: { okur: { odunc: 180, eser: "tam", sube: "tam" }, kosum: 1, tazelik: 72, dayanak: "zengin", alan: "eser.tur" }, }; // Tek kosumda okunan kayit ve acilan sorgu. export function raporOku(veri, r) { let okunan = 0, sorgu = 0; for (const [varlik, pencere] of Object.entries(r.okur)) { sorgu += 1; if (pencere === "tam") { okunan += veri[varlik].length; continue; } for (const k of veri[varlik]) if (k.gun >= GUN - pencere) okunan += 1; } return { okunan, sorgu }; } // Bir cevrimde cekilen dilim: olaydan donem icindeki kayit, boyuttan ya tamami ya da // degisim damgasi doneme dusen kayit (TM13). export function cek(veri, donem, boyut) { const esik = SON - donem; const degisen = (d) => boyut === "tam" ? d : d.filter((k) => k.degisim > esik); const dilim = { odunc: veri.odunc.filter((k) => k.gun * 24 + k.saat > esik), uye: degisen(veri.uye), eser: degisen(veri.eser), sube: veri.sube }; return { dilim, okunan: Object.values(dilim).reduce((t, d) => t + d.length, 0), sorgu: 4 }; } // Hattin sekiz adimi; her adim okudugu alani bildirir. export const ADIMLAR = [ { ad: "cek-odunc", kaynak: "odunc", girdi: [], okur: ["uye_id", "eser_id", "sube_id", "gun"] }, { ad: "cek-uye", kaynak: "uye", girdi: [], okur: ["kayit_gun", "sube_id"] }, { ad: "cek-eser", kaynak: "eser", girdi: [], okur: ["tur"] }, { ad: "cek-sube", kaynak: "sube", girdi: [], okur: ["bolge"] }, { ad: "temiz", girdi: ["cek-odunc"], okur: ["sube_id", "gun"] }, { ad: "zengin", girdi: ["temiz", "cek-eser", "cek-sube"], okur: ["eser_id", "tur", "bolge"] }, { ad: "ozet-sube", girdi: ["zengin"], okur: ["bolge", "gun"] }, { ad: "ozet-uye", girdi: ["temiz", "cek-uye"], okur: ["uye_id", "kayit_gun"] }, ]; const grupla = (d, f) => [...new Set(d.map(f))].map((a) => ({ a })); const ISLEM = { temiz: ([o]) => o.filter((k) => k.sube_id !== null), zengin: ([o, e, s]) => { const t = new Map(e.map((k) => [k.id, k.tur])), b = new Map(s.map((k) => [k.id, k.bolge])); return o.map((k) => ({ ...k, tur: t.get(k.eser_id) ?? "yok", bolge: b.get(k.sube_id) ?? "yok" })); }, "ozet-sube": ([o]) => grupla(o, (k) => `${k.bolge}|${k.gun}`), "ozet-uye": ([o]) => grupla(o, (k) => `${k.uye_id}|${Math.floor(k.gun / 30)}`), }; // Hatti kostur: hata firlatan adim "kirik", girdisi saglam olmayan adim "durdu" olur. export function kostur(dilim) { const cikti = new Map(), durum = new Map(), sayim = new Map(); let tasinan = 0; for (const a of ADIMLAR) { if (a.girdi.some((g) => durum.get(g) !== "tamam")) { durum.set(a.ad, "durdu"); continue; } const girdiler = a.kaynak ? [dilim[a.kaynak]] : a.girdi.map((g) => cikti.get(g)); const ornek = girdiler.filter((d) => d.length > 0).map((d) => d[0]); try { for (const alan of a.okur) if (ornek.every((k) => (alan in k) === false)) throw new Error(`eksik alan ${alan}`); const sonuc = a.kaynak ? girdiler[0] : ISLEM[a.ad](girdiler); const giren = girdiler.reduce((t, d) => t + d.length, 0); cikti.set(a.ad, sonuc); durum.set(a.ad, "tamam"); sayim.set(a.ad, { giren, cikan: sonuc.length }); tasinan += giren + sonuc.length; } catch (e) { durum.set(a.ad, `kirik: ${e.message}`); } } return { durum, sayim, tasinan }; }
Ambar Yükü Alır, Karşılığında Gecikme Verir
Ambar bir çevrimde kaynakları okur, dönüştürür ve raporların okuyacağı özetleri bırakır. Bir rapor ancak kabul ettiği gecikme ambarınkinden büyükse ambardan cevap alır. TM12: hat saniyede 4.000 kayıt taşır; tazelik gecikmesi dönem ile koşum süresinin toplamıdır. TM13: artımlı boyut yüklemesi değişim damgası üzerinde bir dizin varsayar.
// ambar/yuk.mjs — isletim sistemine dusen sorgu ve okunan kayit: ambarsiz ve ambarli import { uret, RAPORLAR, raporOku, cek, kostur, GUN } from "./model.mjs"; const veri = uret(); const HIZ = 4000; // TM12: hat saniyede 4.000 kayit tasir const olcum = Object.fromEntries(Object.entries(RAPORLAR).map(([a, r]) => [a, raporOku(veri, r)])); console.log(`model: ${Object.entries(veri).map(([a, d]) => `${a} ${d.length}`).join(", ")}; ${GUN} gun`); console.log("\nrapor kosum/gun sorgu/kosum okunan/kosum okunan/gun tazelik (saat)"); console.log("-------------- ---------- ------------ ------------- ----------- ---------------"); let hamOkunan = 0, hamSorgu = 0; for (const [ad, r] of Object.entries(RAPORLAR)) { const o = olcum[ad]; hamOkunan += o.okunan * r.kosum; hamSorgu += o.sorgu * r.kosum; console.log(`${ad.padEnd(14)} ${String(r.kosum).padStart(10)} ${String(o.sorgu).padStart(12)} ` + `${String(o.okunan).padStart(13)} ${String(o.okunan * r.kosum).padStart(11)} ${String(r.tazelik).padStart(15)}`); } console.log(`ambarsiz toplam: ${hamSorgu} sorgu/gun, ${hamOkunan} okunan kayit/gun`); console.log("\ndonem boyut kosum (sn) gecikme ambarda isletimde sorgu/gun okunan/gun ambarsiza gore"); console.log("------ --------- ---------- -------- -------- ---------- ---------- ----------- --------------"); const kalanlar = {}; for (const boyut of ["tam", "artimli"]) for (const donem of [24, 12, 3, 1]) { const c = cek(veri, donem, boyut); const kosum = kostur(c.dilim).tasinan / HIZ; const gecikme = donem + kosum / 3600, cevrim = 24 / donem; let sorgu = c.sorgu * cevrim, okunan = c.okunan * cevrim; const kalan = []; for (const [ad, r] of Object.entries(RAPORLAR)) { if (r.tazelik >= gecikme) continue; kalan.push(ad); sorgu += olcum[ad].sorgu * r.kosum; okunan += olcum[ad].okunan * r.kosum; } kalanlar[donem] = kalan; console.log(`${String(donem).padStart(6)} ${boyut.padEnd(9)} ${kosum.toFixed(1).padStart(10)} ` + `${gecikme.toFixed(2).padStart(8)} ${String(5 - kalan.length).padStart(8)} ${String(kalan.length).padStart(10)} ` + `${String(Math.round(sorgu)).padStart(10)} ${String(Math.round(okunan)).padStart(11)} ` + `${`${(okunan / hamOkunan * 100).toFixed(1)}%`.padStart(14)}`); } console.log(`\n24 saatlik donemde isletimde kalan rapor: ${kalanlar[24].join(", ")}`); console.log(`3 saatlik donemde isletimde kalan rapor: ${kalanlar[3].join(", ")}`);
model: sube 12, eser 128000, uye 46000, odunc 240000; 180 gun
rapor kosum/gun sorgu/kosum okunan/kosum okunan/gun tazelik (saat)
-------------- ---------- ------------ ------------- ----------- ---------------
doluluk 48 2 1326 63648 1
gecikmis-eser 24 2 168293 4039032 4
yeni-uye 12 1 46000 552000 14
etkinlik 2 2 165808 331616 36
devir 1 3 368012 368012 72
ambarsiz toplam: 163 sorgu/gun, 5354308 okunan kayit/gun
donem boyut kosum (sn) gecikme ambarda isletimde sorgu/gun okunan/gun ambarsiza gore
------ --------- ---------- -------- -------- ---------- ---------- ----------- --------------
24 tam 133.4 24.04 2 3 160 4830006 90.2%
12 tam 131.9 12.04 3 2 152 4451992 83.1%
3 tam 130.8 3.04 4 1 128 1456912 27.2%
1 tam 130.6 1.04 4 1 192 4241160 79.2%
24 artimli 3.6 24.00 2 3 160 4656902 87.0%
12 artimli 1.8 12.00 3 2 152 4104854 76.7%
3 artimli 0.4 3.00 4 1 128 65392 1.2%
1 artimli 0.1 1.00 4 1 192 65160 1.2%
24 saatlik donemde isletimde kalan rapor: doluluk, gecikmis-eser, yeni-uye
3 saatlik donemde isletimde kalan rapor: doluluk
İlk tablo yükün nerede olduğunu gösteriyor. Günde 163 sorgu ve 5.354.308 okunan kayıt var; bunun 4.039.032’si, yani yüzde yetmiş beşi tek bir rapordan geliyor. Sebebi koşum sayısı değil, koşum başına okunan kayıt: gecikmiş eser listesi her koşumda katalogun 128.000 satırını tarıyor. Günde 48 kez koşan doluluk raporu toplamın yüzde birini bile bulmuyor.
İkinci tablo ambarın devraldığı payı dönem dönem sayıyor ve iki yönlü bir eğri çıkarıyor. Dönem kısaldıkça gecikme düşüyor, gecikme düştükçe daha çok rapor ambardan cevap alabiliyor: ikiden dörde. Ama çevrim sayısı da artıyor ve her çevrim kaynakları yeniden okuyor. Tam boyut yüklemesinde okunan kayıt 24 saatlik dönemde yüzde 90,2, 3 saatlikte yüzde 27,2, 1 saatlikte yeniden yüzde 79,2. En ucuz nokta uçlarda değil, ortada.
Asıl belirleyici sütun boyut. Aynı dönemde artımlı yükleme yüzde 27,2’yi yüzde 1,2’ye indiriyor, çünkü katalogun 128.000 satırı her çevrimde değil yalnız değişen kısmıyla okunuyor. İşletim yükünün düşüp düşmeyeceğini ambarın varlığı değil, kaynakları nasıl okuduğu belirliyor.
Bir rapor hiçbir dönemde ambara geçmiyor: doluluk raporunun kabul ettiği gecikme bir saat, en kısa dönemin gecikmesi 1,04 saat. Tazelik gereksinimi, ambarın kapsayamadığı yolu tanımlar.
Hattın Adımları ve Kaynak Şemasındaki Değişim
Hat sekiz adımdır ve her adım okuduğu alanı bildirir. Kaynak şemasındaki bir alan değişimi gerçekten uygulanır: alan yeniden adlandırılır ya da kaldırılır, hat koşturulur ve kırılan adım sayılır.
// ambar/kirilma.mjs — adim adim tasinan kayit ve kaynak sema degisiminin kirdigi adim import { uret, RAPORLAR, cek, kostur, ADIMLAR, SAHIP } from "./model.mjs"; const veri = uret(), DONEM = 3; const { dilim } = cek(veri, DONEM, "tam"); const temiz = kostur(dilim); const gecikme = DONEM + temiz.tasinan / 4000 / 3600; console.log("adim girdi giren cikan"); console.log("------------ ------------------------ -------- --------"); for (const a of ADIMLAR) { const s = temiz.sayim.get(a.ad); console.log(`${a.ad.padEnd(12)} ${(a.kaynak ?? a.girdi.join(",")).padEnd(24)} ` + `${String(s.giren).padStart(8)} ${String(s.cikan).padStart(8)}`); } console.log(`bir cevrimde ${ADIMLAR.length} adim ${temiz.tasinan} kayit tasidi; gecikme ${gecikme.toFixed(2)} saat`); // Uc sema degisimi uygulanir: alan yeniden adlandirilir ya da kaldirilir. const DEGISIM = { "eser.tur -> eser.tur_kodu": { varlik: "eser", alan: "tur", yeni: "tur_kodu" }, "odunc.sube_id -> odunc.sube": { varlik: "odunc", alan: "sube_id", yeni: "sube" }, "uye.kayit_gun kaldirildi": { varlik: "uye", alan: "kayit_gun", yeni: null }, }; const uygula = (d, g) => ({ ...d, [g.varlik]: d[g.varlik].map((k) => { const y = { ...k }; delete y[g.alan]; if (g.yeni) y[g.yeni] = k[g.alan]; return y; }) }); console.log("\ndegisim sahip kirik duran saglam ambarda dusen ambarsiz kirilan"); console.log("----------------------------- --------------------------- ------ ------ ------- -------------- -----------------"); for (const [ad, g] of Object.entries(DEGISIM)) { const d = [...kostur(uygula(dilim, g)).durum]; const say = (f) => d.filter(([, v]) => f(v)).length; const bozuk = new Set(d.filter(([, v]) => v !== "tamam").map(([k]) => k)); const dusen = Object.values(RAPORLAR).filter((r) => bozuk.has(r.dayanak)).length; const ambarsiz = Object.values(RAPORLAR).filter((r) => r.alan === `${g.varlik}.${g.alan}`).length; console.log(`${ad.padEnd(29)} ${SAHIP[g.varlik].padEnd(27)} ${String(say((v) => v.startsWith("kirik"))).padStart(6)} ` + `${String(say((v) => v === "durdu")).padStart(6)} ${String(say((v) => v === "tamam")).padStart(7)} ` + `${String(dusen).padStart(14)} ${String(ambarsiz).padStart(17)}`); } // Bag: ambarsiz rapor okudugu her kaynaga baglanir; ambarli kaynak yalniz hatta baglanir. const bagsiz = Object.values(RAPORLAR).flatMap((r) => Object.keys(r.okur)); const kalan = Object.values(RAPORLAR).filter((r) => r.tazelik < gecikme); const kalanBag = kalan.flatMap((r) => Object.keys(r.okur)); console.log(`\nbag: ambarsiz ${bagsiz.length} (rapor -> kaynak); ambarli ${4 + (5 - kalan.length) + kalanBag.length} ` + `(4 cekim + ${5 - kalan.length} ambar okumasi + ${kalanBag.length} kalan dogrudan okuma)`); console.log(`yonetim biriminin konustugu sahip: ambarsiz ${new Set(bagsiz).size}, ` + `ambarli ${new Set(["ambar", ...kalanBag]).size}`);
adim girdi giren cikan ------------ ------------------------ -------- -------- cek-odunc odunc 146 146 cek-uye uye 46000 46000 cek-eser eser 128000 128000 cek-sube sube 12 12 temiz cek-odunc 146 144 zengin temiz,cek-eser,cek-sube 128156 144 ozet-sube zengin 144 4 ozet-uye temiz,cek-uye 46144 143 bir cevrimde 8 adim 523341 kayit tasidi; gecikme 3.04 saat degisim sahip kirik duran saglam ambarda dusen ambarsiz kirilan ----------------------------- --------------------------- ------ ------ ------- -------------- ----------------- eser.tur -> eser.tur_kodu katalog (disaridan alinmis) 1 2 5 3 2 odunc.sube_id -> odunc.sube odunc servisi (iceride) 1 4 3 5 1 uye.kayit_gun kaldirildi uyelik sistemi 1 1 6 2 2 bag: ambarsiz 10 (rapor -> kaynak); ambarli 10 (4 cekim + 4 ambar okumasi + 2 kalan dogrudan okuma) yonetim biriminin konustugu sahip: ambarsiz 4, ambarli 3
Adım tablosu maliyetin nereden geldiğini gösteriyor. Üç saatlik çevrimde ödünç tarafından yalnız
146 kayıt geliyor, ama taşınan toplam 523.341. Farkın tamamı boyut okumasıdır: zengin adımına
128.156 kayıt giriyor, çünkü 144 ödünç kaydını zenginleştirmek için katalogun tamamı okunuyor.
temiz adımı 146 kayıttan 144’ünü geçiriyor; şubesi boş kalan iki kayıt düşüyor.
Kırılma tablosu bu kursun sorusunu veriyor. Üç değişimin üçü de tek bir alanı ilgilendiriyor ve üçü de hattın tek adımını kırıyor. Fark, kırığın nerede durduğudur. Katalogun alanı kırıldığında iki adım daha duruyor ve üç rapor cevapsız kalıyor; ambarsız durumda o alanı iki rapor kullanıyordu. Ödünç servisinin alanı kırıldığında dört adım duruyor ve beş raporun beşi düşüyor — oysa o alanı doğrudan okuyan tek rapor vardı. Üyelik alanında iki sayı eşitleniyor.
Ambar, aynı kaynağı okuyan raporları birbirine bağlar. Doluluk raporu katalogun tür alanını hiç
kullanmaz; ambarsız durumda o değişimden etkilenmezdi, ambarlı durumda ozet-sube adımı zengin
adımının ardında durduğu için etkilenir. Karşılığı düzeltmenin yerindedir: kırık tek bir adımdır
ve tek bir ekibin sorumluluğundadır. Ambarsız durumda aynı düzeltme, ayrı yazılmış rapor
sorgularının her birinde ayrı ayrı yapılır.
Son satırlar bağı sayıyor. Bağ sayısı değişmiyor: ambarsız 10, ambarlı 10. Değişen yönü ve sahibi. Yönetim birimi ambarsız durumda dört sahiple, ambarlı durumda üçüyle konuşur; kalan iki sahip ambarın karşılayamadığı doluluk raporu yüzündendir. Tümleştirme noktası bağ sayısını azaltmaz, koordinasyonun kiminle yapılacağını değiştirir.
Özet
- Beş rapor işletim sistemine günde 163 sorgu ve 5.354.308 okunan kayıt bindiriyor; bunun yüzde yetmiş beşi tek bir rapordan geliyor, çünkü o rapor her koşumda katalogun 128.000 satırını tarıyor (TM10, TM11).
- Dönem kısaldıkça ambardan cevaplanan rapor ikiden dörde çıkıyor, ama çevrim sayısı arttığı için okunan kayıt yüzde 27,2’den yüzde 79,2’ye geri tırmanıyor; en ucuz dönem uçlarda değil ortadadır.
- Artımlı boyut yüklemesi aynı dönemde işletim yükünü yüzde 27,2’den yüzde 1,2’ye indiriyor; belirleyici olan ambarın varlığı değil kaynakları nasıl okuduğudur (TM13).
- Tazelik gereksinimi bir saat olan rapor hiçbir dönemde ambara geçmiyor, çünkü en kısa dönemin gecikmesi 1,04 saat. Ambar kapsayamadığı yolu böyle bildirir (TM12).
- Sekiz adımlık hat bir çevrimde 523.341 kayıt taşıyor; kaynaktaki tek bir alan değişimi tek bir adımı kırıyor, ama ödünç alanında kırığın ardında dört adım durduğu için beş raporun beşi düşüyor — o alanı doğrudan okuyan tek rapor vardı (TM14).
Sonraki Adım
Bu dersin ölçümleri bir varsayıma yaslanıyordu: kaynak sistemlerin alanları biliniyor ve şema değişimi bir hata iletisiyle görünüyor. Ağdaki her sistem böyle değil. Ödünç geçmişini yıllardır tutan, kaynak koduna kimsenin erişemediği bir kayıt sistemi duruyor. Sıradaki ders bunu ele alır: değiştirilemeyen bir sistemin çağrı yüzeyi sarmalanınca kaç yol kapanır, kaç yol sarmalayıcının dışında kalır, veri sahipliği devredilirken iki sistemin birlikte yazdığı pencere ne kadar sürer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.