Ders 22 / 23
Anlık Görüntü ve Geri Yükleme
Dizine özgü yedekleme kararı: bölütlerin değişmezliği üzerine kurulan artımlı anlık görüntünün kopyaladığı bayt, arka plan birleştirmesinin artımlılığı bozması, depoda tutulanın canlı dizinden büyük olması ve geri yükleme ile kaynaktan yeniden dizinlemenin kopyalanan bayt, işlenen belge, küme ile sıra üzerinden karşılaştırılması.
İçindekiler
Devretme, eski veriden kurtulmayı dosya düşürmeye indirdi. Aynı yapı yedeklemeyi de değiştirir: yazıldıktan sonra bir daha değişmeyen bölütler, yedeklenecek şeyi “durmadan değişen bir durum” olmaktan çıkarıp değişmez dosyalardan oluşan bir kümeye çevirir.
Bellek içi bir deponun anlık görüntüsü daha önce ölçüldü: aralık ile kayıp penceresi arasındaki ilişki, görüntü almanın çalışan depoya maliyeti. O ölçüm burada tekrarlanmaz. Buranın sorusu dizine özgüdür. Bölütler değişmezse görüntü artımlı olabilir; peki arka planda çalışan birleştirme buna ne yapar, ve dizin geri gelmesi gerektiğinde onu depodan taşımak mı yoksa kaynaktan yeniden dizinlemek mi daha ucuzdur.
Bölüt Düzeyinde Görüntü
AK18. Bölüt dosyası adıyla birlikte değişmezdir: depoda belirli bir ada sahip dosya her zaman aynı içeriği taşır. Artımlı görüntü bunun üzerine kurulur — depoda o ad varsa dosya yeniden kopyalanmaz.
AK19. Görüntünün bedeli kopyalanan bayttır; depoda tutulan, birbirinden farklı bölüt dosyalarının toplamıdır.
AK20. Yeniden dizinleme kaynak kayıtların elde olduğunu, aynı günlük partilerin ve aynı birleştirme politikasının uygulandığını varsayar. Süre koşum ortamına bağlıdır ve bu yüzden basılmaz; onun yerine koşumdan bağımsız iki nicelik sayılır: kopyalanan bayt ile işlenen belge ve belirteç.
Derlem 8.000 kitap kaydıdır, tohum 20260731.
// yedek.mjs — katalog derlemi, çözümleyiciler, bölütlü dizin ve bölüt düzeyinde görüntü deposu import { writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs'; export function uretec(tohum) { // belirlenimci sözde rastgele üreteç let a = tohum >>> 0; return () => { a = (a + 0x6d2b79f5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const SIFAT = 'sessiz uzak kırık yitik beyaz kara ince derin sarı uzun'.split(' '); const ADLAR = 'kapı deniz yol ev şehir bahçe ada defter ırmak kuyu'.split(' '); const KONU = ('çocuk edebiyatı|kısa öykü|roman|deneme|şiir|tarih|coğrafya|felsefe|psikoloji|' + 'ekonomi|mimarlık|müzik|sinema|gezi|biyografi|anı|bilim kurgu|polisiye|halk bilimi').split('|'); const KOK = ('kitap yazar anlatı öykü çocuk kent bellek yolculuk kuşak savaş göç aile deniz kış ' + 'sabah mektup fotoğraf tren sokak okul kütüphane rüzgâr sessizlik umut ada yağmur orman köy ' + 'dağ nehir sınır yüzyıl kadın asker öğretmen hekim ressam şarkı dize kapak').split(' '); const DOLGU = 've ile bir ya'.split(' '); function cekim(kok, i) { // ek yüzey biçimi: ünlü uyumu ve sertleşme const v = [...kok].reverse().find((c) => 'aeıioöuü'.includes(c)); const k = 'aıou'.includes(v), d = 'fhkpsştç'.includes(kok.at(-1)) ? 't' : 'd'; return kok + ['', k ? 'lar' : 'ler', d + (k ? 'a' : 'e'), d + (k ? 'an' : 'en'), k ? 'la' : 'le', k ? 'lara' : 'lere', k ? 'larda' : 'lerde', k ? 'lardan' : 'lerden'][i]; } export function derlemUret(sayi, tohum) { const r = uretec(tohum), liste = []; for (let i = 0; i < sayi; i++) { const ozet = []; for (let j = 0; j < 16; j++) ozet.push(j % 4 === 3 ? DOLGU[Math.floor(r() * 4)] : cekim(KOK[Math.floor(r() ** 2 * KOK.length)], Math.floor(r() ** 2 * 8))); liste.push({ id: 'k' + String(i).padStart(5, '0'), ad: `${SIFAT[Math.floor(r() * 10)]} ${ADLAR[Math.floor(r() * 10)]}`, ozet: ozet.join(' '), konu: KONU[Math.floor(r() * 19)] }); } return liste; } export const metin = (b) => `${b.ad} ${b.ozet} ${b.konu}`; const DURAK = new Set(DOLGU); const EK = ['lardan', 'lerden', 'larda', 'lerde', 'lara', 'lere', 'lar', 'ler', 'dan', 'den', 'tan', 'ten', 'da', 'de', 'ta', 'te', 'la', 'le']; const kok = (t) => { // kök bulma: en uzun eşleşen eki kırpar for (const e of EK) if (t.length > e.length + 2 && t.endsWith(e)) return t.slice(0, -e.length); return t; }; export const temel = (s) => // küçük harf + harf dışı ayırıcı s.toLocaleLowerCase('tr').split(/[^\p{L}\p{N}]+/u).filter((t) => t.length > 1); export const gelismis = (s) => // + durak sözcük atma + kök bulma temel(s).filter((t) => !DURAK.has(t)).map(kok); let sayac = 0; export class Bolut { // adı içeriğiyle birlikte sabittir: bir daha yazılmaz constructor(belgeler = [], coz = temel) { this.ad = 'b' + String(++sayac).padStart(3, '0'); this.sozluk = new Map(); this.belge = new Set(); for (const b of belgeler) { this.belge.add(b.id); const sayim = new Map(); for (const t of coz(metin(b))) sayim.set(t, (sayim.get(t) || 0) + 1); for (const [t, tf] of sayim) { if (!this.sozluk.has(t)) this.sozluk.set(t, []); this.sozluk.get(t).push([b.id, tf]); } } } serile() { let s = ''; for (const [t, g] of this.sozluk) s += t + '\t' + g.map(([i, f]) => i + ':' + f).join(' ') + '\n'; return s; } get bayt() { return Buffer.byteLength(this.serile()); } } export function yigin(carpan = 4) { // katmanlı birleştirme yığını const katman = []; let birlestirme = 0; const yerlestir = (b, k) => { while (katman.length <= k) katman.push([]); katman[k].push(b); if (katman[k].length < carpan) return; birlestirme++; yerlestir(birlestir(katman[k].splice(0, carpan)), k + 1); }; return { ekle(belgeler, coz) { birlestirme = 0; yerlestir(new Bolut(belgeler, coz), 0); }, get bolutler() { return katman.flat(); }, get birlestirme() { return birlestirme; }, }; } export function birlestir(grup) { const y = new Bolut(); for (const bl of grup) { for (const id of bl.belge) y.belge.add(id); for (const [t, g] of bl.sozluk) { if (!y.sozluk.has(t)) y.sozluk.set(t, []); for (const p of g) y.sozluk.get(t).push(p); } } return y; } export function ara(bolutler, terimler, k = 10) { const puan = new Map(); for (const bl of bolutler) { const N = bl.belge.size; for (const t of terimler) { const g = bl.sozluk.get(t); if (!g) continue; const idf = Math.log(1 + N / g.length); for (const [id, tf] of g) puan.set(id, (puan.get(id) || 0) + tf * idf); } } const sira = [...puan].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)); return { ilkK: sira.slice(0, k).map(([id]) => id), eslesen: sira.length }; } // görüntü: bölüt dosyası depoda yoksa yazılır; artımlı olmayan görüntü hepsini yeniden yazar export function goruntuAl(bolutler, depo, artimli) { mkdirSync(depo, { recursive: true }); let kopyalanan = 0, dosya = 0; for (const b of bolutler) { const yol = `${depo}/${b.ad}`; if (artimli && existsSync(yol)) continue; writeFileSync(yol, b.serile()); kopyalanan += statSync(yol).size; dosya++; } return { kopyalanan, dosya, liste: bolutler.map((b) => b.ad) }; }
Artımlı Görüntü ve Birleştirmenin Bozduğu
Katalog on altı gün boyunca günde 500 kayıt alır ve her günün sonunda bir görüntü alınır. Aynı gün iki kez ölçülür: dizinin tamamını kopyalayan tam görüntü ve yalnız depoda bulunmayan bölüt dosyalarını kopyalayan artımlı görüntü.
// goruntu.mjs — on altı günlük dizinde tam ve artımlı anlık görüntünün kopyaladığı bayt import { derlemUret, yigin, goruntuAl } from './yedek.mjs'; const derlem = derlemUret(8000, 20260731); const y = yigin(4); console.log('derlem: 8000 belge, tohum 20260731 | günde 500 belgelik bir toplu iş, çarpan 4'); console.log('gün bölüt birleştirme tam görüntü bayt artımlı bayt artımlı dosya depoda tutulan'); let tamToplam = 0, depoBayt = 0; for (let gun = 1; gun <= 16; gun++) { y.ekle(derlem.slice((gun - 1) * 500, gun * 500)); const tam = goruntuAl(y.bolutler, `tam/g${gun}`, false); const art = goruntuAl(y.bolutler, 'depo', true); tamToplam += tam.kopyalanan; depoBayt += art.kopyalanan; console.log(String(gun).padStart(3), String(y.bolutler.length).padStart(6), String(y.birlestirme).padStart(12), String(tam.kopyalanan).padStart(17), String(art.kopyalanan).padStart(13), String(art.dosya).padStart(14), String(depoBayt).padStart(15)); } console.log('toplam: tam görüntüler', tamToplam, 'bayt, artımlı görüntüler', depoBayt, 'bayt');
derlem: 8000 belge, tohum 20260731 | günde 500 belgelik bir toplu iş, çarpan 4 gün bölüt birleştirme tam görüntü bayt artımlı bayt artımlı dosya depoda tutulan 1 1 0 81694 81694 1 81694 2 2 0 162974 81280 1 162974 3 3 0 244317 81343 1 244317 4 1 1 315289 315289 1 559606 5 2 0 397163 81874 1 641480 6 3 0 478662 81499 1 722979 7 4 0 560200 81538 1 804517 8 2 1 631586 316297 1 1120814 9 3 0 713127 81541 1 1202355 10 4 0 794677 81550 1 1283905 11 5 0 876578 81901 1 1365806 12 3 1 947640 316054 1 1681860 13 4 0 1029181 81541 1 1763401 14 5 0 1111100 81919 1 1845320 15 6 0 1192704 81604 1 1926924 16 1 2 1253638 1253638 1 3180562 toplam: tam görüntüler 10790530 bayt, artımlı görüntüler 3180562 bayt
Birleştirme olmayan günlerde artımlı görüntü sabit bir bedel öder: yaklaşık 81.500 bayt, yani o gün eklenen tek bölüt. Tam görüntü ise her gün bütün dizini kopyalar ve on beşinci günde tek bir yedek 1.192.704 bayta çıkar. On altı günün toplamında fark on kata yakındır: 10.790.530 bayta karşılık 3.180.562 bayt.
Kritik satırlar 4, 8, 12 ve 16. numaralı günlerdir. Birleştirmenin çalıştığı günde artımlı görüntü de tam görüntü kadar iş yapar, çünkü birleştirmenin ürettiği bölüt yeni bir dosyadır: içeriğinin tamamı zaten depoda olsa bile, girdileri değil kendisi kopyalanır. On altıncı gün bunun uç hâlidir — iki birleştirme dizini tek bölüte indirir ve artımlı görüntü 1.253.638 baytın hepsini yeniden yazar. Artımlılık, bölütlerin değişmezliğinden gelen bir kazançtır ve birleştirme tam da o değişmezliği kullanıp yeni dosya ürettiği için düzenli aralıklarla sıfırlanır.
Son sütun üçüncü bir sonucu verir. Depoda tutulan 3.180.562 bayttır, oysa canlı dizin 1.253.638 bayttır. Depo, canlı dizinin iki buçuk katını tutar; fazlalık, birleştirmeyle geçersizleşen ama eski görüntüler onlara başvurduğu için silinemeyen bölüt dosyalarıdır. Eski görüntüler düşürülmeden bu alan geri gelmez. Birleştirme çarpanını düşürmek — bir önceki derste sorgu tarafında küçük bir kazançtı — burada hem artımlı görüntü trafiğini hem depo boyutunu doğrudan büyütür.
Geri Yükleme mi, Yeniden Dizinleme mi
Dizin kaybolduğunda iki yol vardır. Depodaki son görüntünün dosyaları yerine taşınabilir, ya da katalog kaynağındaki 8.000 kayıt yeniden dizinlenebilir. İkincisi ilk bakışta yedeğe hiç gerek bırakmaz. Ölçüm iki yolun neyi ödediğini ve ne getirdiğini ayırır: üçüncü satırda yeniden dizinleme, o sırada çözümleyici zinciri değiştirilmiş bir sistemde koşar.
// kurtarma.mjs — geri yükleme ile yeniden dizinlemenin karşılaştırması import { mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs'; import { derlemUret, yigin, goruntuAl, ara, metin, temel, gelismis } from './yedek.mjs'; const derlem = derlemUret(8000, 20260731); const SORGU = [['çocuk', 'edebiyatı', 'öykü'], ['yitik', 'kapılar'], ['kütüphaneler', 'bellek'], ['denizde', 'ada'], ['polisiye', 'kent']]; function kur(coz) { // aynı günlük partiler, aynı birleştirme politikası const y = yigin(4); let belirtec = 0; for (let g = 0; g < 16; g++) { const parti = derlem.slice(g * 500, (g + 1) * 500); for (const b of parti) belirtec += coz(metin(b)).length; y.ekle(parti, coz); } return { bolutler: y.bolutler, belirtec }; } const asil = kur(temel); // özgün dizin ve son görüntünün içeriği const gor = goruntuAl(asil.bolutler, 'depo', true); mkdirSync('dizin', { recursive: true }); // geri yükleme: depodaki dosyalar yerine taşınır let geriBayt = 0; for (const ad of gor.liste) { writeFileSync(`dizin/${ad}`, readFileSync(`depo/${ad}`)); geriBayt += statSync(`dizin/${ad}`).size; } const olc = (bolutler, coz) => { let bayt = 0, sozluk = 0, eslesen = 0; const ilk = []; for (const b of bolutler) { bayt += b.bayt; sozluk += b.sozluk.size; } for (const s of SORGU) { const c = ara(bolutler, coz(s.join(' '))); eslesen += c.eslesen; ilk.push(c.ilkK); } return { bayt, sozluk, eslesen, ilk }; }; const temelOlcu = olc(asil.bolutler, temel); const fark = (o) => { // özgün dizine göre ilk on değişimi let cikan = 0, oynayan = 0; for (let q = 0; q < SORGU.length; q++) { for (const id of temelOlcu.ilk[q]) if (!o.ilk[q].includes(id)) cikan++; for (let i = 0; i < 10; i++) if (o.ilk[q][i] !== temelOlcu.ilk[q][i]) oynayan++; } return [cikan, oynayan]; }; const yeni = kur(gelismis); const yeniOlcu = olc(yeni.bolutler, gelismis); console.log('derlem: 8000 belge, tohum 20260731 | 5 sorgu, ilk on: 50 basamak'); console.log('yol kopyalanan işlenen belge belirteç dizin bayt sözlük eşleşen çıkan oynayan'); const satir = (ad, kop, belge, belirtec, o) => console.log(ad.padEnd(32), String(kop).padStart(10), String(belge).padStart(14), String(belirtec).padStart(9), String(o.bayt).padStart(11), String(o.sozluk).padStart(7), String(o.eslesen).padStart(8), String(fark(o)[0]).padStart(6), String(fark(o)[1]).padStart(8)); satir('geri yükleme', geriBayt, 0, 0, temelOlcu); satir('yeniden dizinleme (aynı zincir)', 0, 8000, asil.belirtec, temelOlcu); satir('yeniden dizinleme (yeni zincir)', 0, 8000, yeni.belirtec, yeniOlcu);
derlem: 8000 belge, tohum 20260731 | 5 sorgu, ilk on: 50 basamak yol kopyalanan işlenen belge belirteç dizin bayt sözlük eşleşen çıkan oynayan geri yükleme 1253638 0 0 1253638 363 7879 0 0 yeniden dizinleme (aynı zincir) 0 8000 153690 1253638 363 7879 0 0 yeniden dizinleme (yeni zincir) 0 8000 121690 919902 79 17387 41 49
İlk iki satır aynı dizini üretir: 1.253.638 bayt, 363 terim, beş sorguda 7.879 eşleşen belge ve ilk onda tek basamak farkı yok. Aynı sonuca giden iki yolun ödediği ise bambaşkadır. Geri yükleme 1.253.638 bayt taşır ve hiçbir belgeye dokunmaz; yeniden dizinleme tek bayt taşımaz ama 8.000 belgeyi okuyup 153.690 belirteç üretir. Süre koşum ortamına bağlıdır ve burada basılmaz; karar bu iki niceliğin nasıl ölçekleneceğine bakılarak verilir. Geri yüklemenin bedeli dizin boyutuyla, yeniden dizinlemenin bedeli kaynak belge sayısı ve belge başına belirteçle büyür. Yeniden dizinlemenin ayrıca sessiz bir koşulu vardır: kaynak kayıtların eksiksiz durması. Dizin, katalogda artık bulunmayan bir alanı da tutuyorsa yeniden dizinleme onu geri getiremez.
Üçüncü satır, yeniden dizinlemenin neden yedeğin yerine geçmediğini gösterir. Çözümleyici zinciri durak sözcük atma ve kök bulma ile genişletildiğinde aynı 8.000 kayıt farklı bir dizin üretir: 153.690 yerine 121.690 belirteç (aradaki 32.000, atılan dolgu sözcükleridir), 363 yerine 79 terim, 1.253.638 yerine 919.902 bayt. Etki yanıtta görünür. Beş sorgunun eşleşen belge toplamı 7.879’dan 17.387’ye çıkar, çünkü “kapılar” artık “kapı” ile aynı terime iner ve çekimli biçimler tek gönderi listesinde toplanır — anma yükselir. Sıra da yeniden kurulur: ilk on sonucun elli basamağının 49’u değişir ve 41 belge ilk ondan bütünüyle düşer. Bu bir arıza değildir, kararın kendisidir; ama geri yükleme ile aynı işlem sayılamaz. Geri yükleme dizini olduğu gibi getirir, yeniden dizinleme o günkü yapılandırmayla yeniden üretir.
Özet
- Bölütler değişmez olduğu için görüntü artımlı alınabilir: birleştirme olmayan günlerde kopyalanan bayt o günün tek bölütüdür (yaklaşık 81.500), tam görüntü ise dizinin tamamıdır.
- On altı günün toplamında tam görüntüler 10.790.530 bayt, artımlı görüntüler 3.180.562 bayt kopyaladı.
- Birleştirme artımlılığı bozar: birleştirmenin çalıştığı günde artımlı görüntü tam görüntü kadar iş yapar, çünkü birleşen bölüt yeni bir dosyadır; on altıncı günde 1.253.638 baytın hepsi yeniden kopyalandı.
- Depo 3.180.562 bayt tutar, canlı dizin 1.253.638 bayt: fark, eski görüntülerin başvurduğu için silinemeyen geçersiz bölüt dosyalarıdır.
- Geri yükleme 1.253.638 bayt taşıyıp sıfır belge işler, yeniden dizinleme sıfır bayt taşıyıp 8.000 belge ve 153.690 belirteç işler; ikisi aynı dizini verir.
- Çözümleyici zinciri değişmişse yeniden dizinleme aynı dizini vermez: eşleşen belge 7.879’dan 17.387’ye çıktı, ilk on sonucun 49 basamağı değişti ve 41 belge ilk ondan düştü.
Sonraki Adım
Bu ders bir dizinin bütün olarak kopyalanmasını, taşınmasını ve yeniden üretilmesini ele aldı. Hepsinin ortak varsayımı, kümeye erişen herkesin bütün dizini görmeye yetkili olduğuydu: görüntü alan da, geri yükleyen de, sorgulayan da aynı belgeleri görüyordu. Kütüphane katalogunda bu doğru değildir — kişisel ödünç kayıtları, satın alma notları ve bağış yazışmaları aynı dizinde durabilir ama her kullanıcıya açık değildir. Sonraki ders bu ayrımı ölçer: aynı sorgu iki farklı rolde koştuğunda dönen kümenin nasıl daraldığı, belge ve alan düzeyinde süzmenin sıraya ne yaptığı ve bu süzmenin sorgu maliyetine ne eklediği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.