Ders 10 / 19
Toplama Boru Hattı
Aşamalı dönüşümün kendi gerçekleştiriminde kurulması ve aynı sorunun üç farklı aşama sırasıyla yanıtlanması: işlenen kayıt sayısının 43.610'dan 124.606'ya, engelleyici aşamanın bellekte tuttuğu kaydın 2.418'den 40.276'ya çıkması ve sonucun üç sırada da aynı kalması.
İçindekiler
Önceki ders bir belgenin koşulu sağlayıp sağlamadığını söyleyen değerlendiriciyi kurdu. Kütüphanenin sorularının çoğu bununla bitmez: “hangi yazarın kaç kopyası onarımda”, “en çok ödünç alınan beş kitap”, “şube başına rafta duran kopya sayısı”. Bu soruların ortak yanı, yanıtın tek tek belgelerde değil belgelerin toplamında olmasıdır ve bir koşul ifadesi toplama yapamaz.
Belge modeli bu işi toplama boru hattıyla (aggregation pipeline) yapar: sıralı aşamalardan (stage) oluşan bir zincir. Her aşama bir kayıt akışı alır, dönüştürür ve bir sonrakine verir. Sözcük kabuk komutlarını birbirine bağlayan boru hattıyla aynıdır ve benzerlik gerçektir; ayrıldıkları yer akışın birimidir — kabukta akan şey bayttır, burada akan şey kayıttır ve aşamalar kayıt üzerinde çalışır. Bu dersin sorusu aşamaların ne yaptığı değil, sıralarının ne kadara mal olduğudur.
Akış Aşaması ve Engelleyici Aşama
Aşamalar iki sınıfa ayrılır. Akış aşaması kaydı alır, işler, bırakır: süzme, dizi açma ve yansıtma böyledir; bellekte bir anda tek bir kayıt tutulur. Engelleyici aşama ise sonucunu üretebilmek için akışın tamamını görmek zorundadır: sıralama akışı bütünüyle bellekte tutar, gruplama ise grup tablosunu tutar. Bir boru hattının bellek maliyeti, engelleyici aşamalarının o anda tuttuğu kayıt sayısıdır.
// boruhatti.mjs — asamali donusum. Her asama bir kayit akisi alir, donusturur, sonrakine // verir. Asama basina giren/cikan kayit ve o asamanin bellekte tuttugu kayit sayilir. export function calistir(kaynak, asamalar) { let akis = kaynak, doruk = 0, islenen = 0; const rapor = []; for (const asama of asamalar) { const ad = Object.keys(asama)[0], giren = akis.length; let bellek = 1; // akis asamasi: tek kayit tutar if (ad === "suz") akis = akis.filter(asama.suz); else if (ad === "ac") { // diziyi ogelerine acar const alan = asama.ac; akis = akis.flatMap((b) => (b[alan] ?? []).map((o) => ({ ...b, [alan]: o }))); } else if (ad === "grupla") { const m = new Map(); for (const b of akis) { const k = asama.grupla.anahtar(b); m.set(k, asama.grupla.topla(m.get(k) ?? asama.grupla.bos(k), b)); } akis = [...m.values()]; bellek = m.size; // engelleyici: grup tablosu bellekte } else if (ad === "sirala") { bellek = giren; // engelleyici: akisin tamami bellekte akis = [...akis].sort(asama.sirala); } else if (ad === "sinirla") akis = akis.slice(0, asama.sinirla); else if (ad === "yansit") akis = akis.map(asama.yansit); islenen += giren; doruk = Math.max(doruk, bellek); rapor.push({ ad, giren, cikan: akis.length, bellek }); } return { sonuc: akis, rapor, islenen, doruk }; } export function yazdir(baslik, c) { console.log(`${baslik}: islenen kayit ${c.islenen}, doruk bellek ${c.doruk} kayit`); for (const r of c.rapor) console.log(` ${r.ad.padEnd(8)} giren ${String(r.giren).padStart(6)}` + ` -> cikan ${String(r.cikan).padStart(6)} bellek ${r.bellek}`); }
Dizi açma aşaması belge modeline özgüdür ve boru hattının en pahalı aşamasıdır: bir kopya dizisi taşıyan kitap belgesi, dizinin öge sayısı kadar kayda dönüşür. 20.000 kitap belgesi, açıldığında 59.494 kayıt olur. Bu aşamadan sonra gelen her aşama üç kat fazla kayıt işler.
Aynı Soru, Üç Sıra
NS7 (varsayım): katalog 20.000 kitap belgesidir, kitap başına 1–5 kopya ve 2–4 etiket bulunur, tohum 424242’dir. Soru sabittir: Kadıköy şubesinde onarımdaki kopya sayısına göre en yüksek beş yazar. Üç boru hattı bu soruyu yanıtlar. A süzmeyi en başa koyar; B önce diziyi açıp sonra süzer; C hiç süzmeden gruplar ve süzmeyi grupların üstünde yapar. Üçünün de doğru yanıtı vermesi gerekir — ölçülen şey doğruluk değil, bedel.
// sira-olcum.mjs — ayni soru uc farkli asama sirasiyla yanitlanir. Sonuc ayni, islenen // kayit ve doruk bellek farkli. Ayni dizinde boruhatti.mjs bulunur. import { calistir, yazdir } from "./boruhatti.mjs"; let cekirdek = 424242; // gorunur tohum const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648; const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"]; const DURUM = ["rafta", "oduncte", "onarimda"]; const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"]; const KATALOG = []; for (let i = 1; i <= 20000; i += 1) { const kopya = [], etiket = []; for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1) kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`, sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] }); for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1) etiket.push(ETIKETLER[Math.floor(rast() * 6)]); KATALOG.push({ _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`, yayin_yili: 1950 + (i % 75), etiket, kopya }); } console.log(`katalog ${KATALOG.length} kitap, ` + `${KATALOG.reduce((t, b) => t + b.kopya.length, 0)} kopya`); // Soru: Kadikoy subesinde onarimdaki kopya sayisina gore en yuksek 5 yazar. const ONARIM = (k) => k.sube === "Kadikoy" && k.durum === "onarimda"; const GRUP = { anahtar: (b) => b.yazar, bos: (k) => ({ yazar: k, sayi: 0 }), topla: (g, b) => ({ ...g, sayi: g.sayi + 1 }) }; const SIRALA = (a, b) => b.sayi - a.sayi || (a.yazar < b.yazar ? -1 : 1); const A = calistir(KATALOG, [ { suz: (b) => b.kopya.some(ONARIM) }, // once suz: kitap duzeyinde { ac: "kopya" }, { suz: (b) => ONARIM(b.kopya) }, // acilan ogeyi suz { grupla: GRUP }, { sirala: SIRALA }, { sinirla: 5 }, ]); const B = calistir(KATALOG, [ { ac: "kopya" }, { suz: (b) => ONARIM(b.kopya) }, { grupla: GRUP }, { sirala: SIRALA }, { sinirla: 5 }, ]); const C = calistir(KATALOG, [ { ac: "kopya" }, { grupla: { anahtar: (b) => `${b.yazar}|${b.kopya.sube}|${b.kopya.durum}`, bos: (k) => ({ yazar: k.split("|")[0], sube: k.split("|")[1], durum: k.split("|")[2], sayi: 0 }), topla: (g, b) => ({ ...g, sayi: g.sayi + 1 }) } }, { suz: (g) => g.sube === "Kadikoy" && g.durum === "onarimda" }, { sirala: SIRALA }, { sinirla: 5 }, ]); yazdir("A once suz", A); yazdir("B ac sonra suz", B); yazdir("C once grupla sonra suz", C); const ozet = (c) => c.sonuc.map((g) => `${g.yazar}=${g.sayi}`).join(" "); console.log(`A sonuc: ${ozet(A)}`); console.log(`ucu ayni mi: ${ozet(A) === ozet(B) && ozet(B) === ozet(C)}`); console.log(`islenen kayit A ${A.islenen} B ${B.islenen} C ${C.islenen}` + ` B/A ${(B.islenen / A.islenen).toFixed(2)} C/A ${(C.islenen / A.islenen).toFixed(2)}`); console.log(`doruk bellek A ${A.doruk} B ${B.doruk} C ${C.doruk}` + ` C/A ${(C.doruk / A.doruk).toFixed(1)} kat`);
katalog 20000 kitap, 59494 kopya A once suz: islenen kayit 43610, doruk bellek 2418 kayit suz giren 20000 -> cikan 3340 bellek 1 ac giren 3340 -> cikan 11926 bellek 1 suz giren 11926 -> cikan 3508 bellek 1 grupla giren 3508 -> cikan 2418 bellek 2418 sirala giren 2418 -> cikan 2418 bellek 2418 sinirla giren 2418 -> cikan 5 bellek 1 B ac sonra suz: islenen kayit 87838, doruk bellek 2418 kayit ac giren 20000 -> cikan 59494 bellek 1 suz giren 59494 -> cikan 3508 bellek 1 grupla giren 3508 -> cikan 2418 bellek 2418 sirala giren 2418 -> cikan 2418 bellek 2418 sinirla giren 2418 -> cikan 5 bellek 1 C once grupla sonra suz: islenen kayit 124606, doruk bellek 40276 kayit ac giren 20000 -> cikan 59494 bellek 1 grupla giren 59494 -> cikan 40276 bellek 40276 suz giren 40276 -> cikan 2418 bellek 1 sirala giren 2418 -> cikan 2418 bellek 2418 sinirla giren 2418 -> cikan 5 bellek 1 A sonuc: Yazar 1068=4 Yazar 111=4 Yazar 1530=4 Yazar 1580=4 Yazar 1618=4 ucu ayni mi: true islenen kayit A 43610 B 87838 C 124606 B/A 2.01 C/A 2.86 doruk bellek A 2418 B 2418 C 40276 C/A 16.7 kat
Sıranın Bedeli
Üç boru hattı aynı beş yazarı aynı sayılarla döndürür; ucu ayni mi satırı bunu
doğrular. Ayrıldıkları yer bedeldir ve bedel iki ayrı sayıyla ölçülür.
İşlenen kayıt sayısı A’da 43.610, B’de 87.838, C’de 124.606’dır. A ile B arasındaki fark yalnız ilk süzmeden gelir: A, dizi açma aşamasına 20.000 kitabın 3.340’ını sokar, B ise tamamını sokar. Açma aşaması A’da 11.926, B’de 59.494 kayıt üretir — beş kat fazla. Buna karşılık A fazladan bir aşama koşturur ve ilk süzme 20.000 belgeye bakar. Toplamda oran 2,01’dir. Kural şudur: süzme, akışı en çok daraltacağı yere değil, kaydı çoğaltan aşamadan önceye konur; dizi açma bir çoğaltıcıdır.
A’nın ilk süzmesi öge düzeyinde değil belge düzeyindedir ve önceki dersin ölçtüğü gevşek biçimdedir: 3.340 belge döndürür. Ardından açılan 11.926 kaydın yalnız 3.508’i ikinci süzmeden geçer. Bu gevşeklik bir hata değil, kasıtlı bir seçimdir — belge düzeyi süzme doğru sonucu kaçırmaz, yalnız fazladan kayıt geçirir. Boru hattında öne konan süzmenin tek koşulu budur: kapsayıcı olmalı, daraltıcı olması gerekmez.
İkinci sayı belleği verir. A ve B’de doruk bellek 2.418 kayıttır — grup tablosunun boyu, yani Kadıköy’de onarımda kopyası olan farklı yazar sayısı. C’de aynı sayı 40.276’dır, 16,7 kat fazla. C’nin yaptığı hata gruplamayı süzmeden önce koymaktır: gruplama artık yalnız ilgilenilen yazarları değil, yazar–şube–durum üçlüsünün bütün birleşimlerini tutar. Bu sayı derlem büyüdükçe grup anahtarının çarpımıyla büyür ve engelleyici aşamanın bellek sınırını aşması, boru hattının diske taşmasına ya da tümden reddedilmesine yol açar.
Son aşama olan sinirla üçünde de aynı yerdedir ve hiçbirinde işi azaltmaz: sıralama zaten
2.418 kaydın tamamını görmüştür. Sınırlama, kendisinden önceki engelleyici aşamanın işini
küçültmez — bunu ancak sıralamanın kendisinin ilk beşi tutan bir biçimi yapabilir ve o da
sıralama aşamasının içinde bir karardır, boru hattının sırasında değil.
Özet
- Toplama boru hattı sıralı aşamalardan oluşur; akış aşamaları tek kayıt tutar, engelleyici aşamalar (gruplama, sıralama) akışın tamamını ya da grup tablosunu bellekte tutar.
- Dizi açma aşaması kaydı çoğaltır: 20.000 kitap belgesi 59.494 kayda dönüşür.
- Aynı soru üç sırayla aynı yanıtı verir, ama 43.610, 87.838 ve 124.606 kayıt işler; süzmeyi çoğaltıcı aşamadan önceye almak işlenen kaydı yarıya indirir.
- Öne konan süzmenin kapsayıcı olması yeter; A’nın belge düzeyi süzmesi 3.340 belge geçirir, bunların açılmasından çıkan 11.926 kaydın 3.508’i sonuca kalır.
- Gruplamayı süzmeden önce koymak doruk belleği 2.418 kayıttan 40.276 kayda çıkarır (16,7 kat).
Sonraki Adım
Üç boru hattının ortak yanı gözden kaçtı: üçü de ilk aşamada derlemin tamamını, 20.000 belgeyi okudu. Aşama sırası işlenen kaydı ve tutulan belleği değiştirdi, okunan belge sayısını değiştirmedi. Onu değiştiren tek şey dizindir. Belge modelinde dizin, ilişkisel kursta ölçülen dizinden yapı olarak ayrılmaz — ama üstüne kurulduğu veri ayrılır: dizi alanı bir belge için birden çok giriş üretir, gömülü alan noktalı bir yolla adreslenir ve dizinin yalnız belgelerin bir bölümünü kapsaması istenebilir. Sonraki ders bu üç durumu kurar ve dizin boyutu ile yazma bedelini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.