Ders 12 / 21
İmaj Depoları
Katman paylaşımlı bir imaj deposu gerçek dosyalarla kurulur ve diskten ölçülür: paylaşımın kaldırdığı bayt, saklama kuralı uygulandığında referans sayımı yüzünden silinemeyen katmanlar, yerelde bulunan katman atlandığında düşen çekme trafiği ve genel ile özel imajların paylaştığı katman sayısı sayılır.
İçindekiler
Önceki ders etiketi bir katman listesine bağladı ve listedeki her satırın karşılığının bir yerde durduğunu varsaydı. O yer bu dersin konusudur: imaj deposu. Depo, M22/K01’de ölçülen yapı çıktısı deposunun katmanlı biçimidir — aynı iki işlevi, içerik saklamayı ve ad çözümünü görür, farkı sakladığı nesnenin tek bir dosya değil bir katman kümesi olmasıdır. Bu fark her ölçüyü değiştirir: büyüme, silme ve indirme artık nesne başına değil, katman başına hesaplanır.
IM37. Depo bu derste gerçek dosyalarla kurulur: her katman kendi içerik özetiyle
adlandırılmış gerçek bir dosyadır, her manifest gerçek bir dosyadır ve ölçülen baytların hepsi
diskten okunur. Ölçek 1/1000’dir — önceki derslerin 34,5 MB’lik imajı burada 34,5 KB’lik gerçek
dosyalarla kurulur. IM38. Derleme geçmişi önceki dersin üretecinden gelir; tohum 20260604
görünürdür ve aynı 20 derleme, aynı 17 ayrı imaj çıkar. IM39. İmajların bir bölümü özel
depoya konur; bu ayrım ikinci bir üreteçten gelir, tohumu 20260812’dir.
Depo Neyi Saklar
İçerik adresli depoda bir katman dosyasının adı kendi özetidir. Bu tek kural depolamanın tamamını belirler: aynı içerik ikinci kez yazıldığında aynı ada yazılır, yani yeni yer tutmaz.
// olcum-agi/depo.mjs — katman paylasimli imaj deposu: gercek dosyalar, gercek baytlar import { createHash } from "node:crypto"; import { mkdirSync, rmSync, writeFileSync, readdirSync, statSync } from "node:fs"; import { join } from "node:path"; export const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-depo"); // Olcek 1/1000: onceki derslerin 34,5 MB imaji burada 34,5 KB'lik gercek dosyalarla kurulur. export const KATMAN = [["temel", 10500, 0.08], ["bagimliliklar", 16000, 0.25], ["uygulama", 7200, null], ["yapilandirma", 800, 0.30]]; export const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; export const ozetle = (b) => createHash("sha256").update(b).digest("hex").slice(0, 12); export const gecmis = (n = 20, tohum = 20260604, gizliTohum = 20260812) => { const r = uretec(tohum), g = uretec(gizliTohum); const surum = [1, 1, 1, 1], liste = []; let gun = 0; for (let no = 1; no <= n; no++) { gun += 1 + Math.round(r() * 8); if (r() > 0.25) surum[2] += 1; KATMAN.forEach(([, , p], k) => { if (p !== null && r() < p) surum[k] += 1; }); r(); r(); // onceki dersin iki alani: cizelge ayni kalsin diye cekilir liste.push({ no, gun, gizli: g() < 0.35, katmanlar: KATMAN.map(([ad, bayt], k) => ({ ad, bayt, surum: surum[k] })) }); } return liste; }; // Depo icerik adreslidir: katman dosyasinin adi kendi ozetidir, ayni katman ikinci kez yazilmaz. export const depoyuKur = (G) => { rmSync(KOK, { recursive: true, force: true }); mkdirSync(join(KOK, "katman"), { recursive: true }); mkdirSync(join(KOK, "manifest"), { recursive: true }); for (const d of G) { d.ozetler = d.katmanlar.map(({ ad, bayt, surum }) => { const icerik = Buffer.alloc(bayt, `${ad}@${surum}|`); const o = ozetle(icerik); writeFileSync(join(KOK, "katman", o), icerik); return o; }); d.imaj = ozetle(d.ozetler.join("|")); writeFileSync(join(KOK, "manifest", d.imaj), d.ozetler.join("\n")); } return G; }; export const dizinBayt = (yol) => readdirSync(yol).reduce((t, f) => t + statSync(join(yol, f)).size, 0); export const kb = (b) => (b / 1000).toFixed(1) + " KB"; if (import.meta.url === `file://${process.argv[1]}`) { const G = depoyuKur(gecmis()); const gercek = dizinBayt(join(KOK, "katman")); const paylasimsiz = G.reduce((t, d) => t + d.katmanlar.reduce((s, k) => s + k.bayt, 0), 0); const ayriKatman = readdirSync(join(KOK, "katman")).length; const ayriImaj = readdirSync(join(KOK, "manifest")).length; console.log(`${G.length} derleme, ${ayriImaj} ayri manifest, ${G.length * 4} katman referansi`); console.log(`ayri katman dosyasi: ${ayriKatman}`); console.log(`depo gercek (diskten olculdu): ${kb(gercek)}`); console.log(`paylasimsiz olsaydi : ${kb(paylasimsiz)}`); console.log(`paylasimin kaldirdigi : ${kb(paylasimsiz - gercek)} ` + `(%${(100 * (1 - gercek / paylasimsiz)).toFixed(1)})`); const sayac = new Map(); for (const d of G) for (const o of new Set(d.ozetler)) sayac.set(o, (sayac.get(o) ?? 0) + 1); const sirali = [...sayac.values()].sort((a, b) => b - a); console.log(`katman basina referans: en cok ${sirali[0]}, ortanca ` + `${sirali[Math.floor(sirali.length / 2)]}, bir kez referansli ` + `${sirali.filter((v) => v === 1).length}/${ayriKatman}`); }
20 derleme, 17 ayri manifest, 80 katman referansi ayri katman dosyasi: 27 depo gercek (diskten olculdu): 176.2 KB paylasimsiz olsaydi : 690.0 KB paylasimin kaldirdigi : 513.8 KB (%74.5) katman basina referans: en cok 16, ortanca 2, bir kez referansli 11/27
Seksen katman referansı diskte yirmi yedi dosyaya karşılık gelir. Depo 176,2 KB’dir; her imaj kendi katmanlarını taşısaydı 690,0 KB olurdu. Paylaşım baytların yüzde 74,5’ini ortadan kaldırmıştır. Kaldırılan 513,8 KB, önceki dersin taşıma ölçümündeki değişmeyen bayt toplamıyla aynı büyüklüktür — iki ölçüm aynı olgunun iki yüzüdür.
Referans dağılımı asıl yapıyı gösterir: bir katman on altı imajda geçer, ortanca iki, ama yirmi yedi katmanın on biri yalnız tek bir imajda geçer. Depo homojen değildir; bir ucunda neredeyse hiç değişmeyen alt katmanlar, öbür ucunda her derlemede yenilenen üst katmanlar durur. Bu dağılım bir sonraki ölçümün de kaynağıdır.
Silme Referans Sayımına Çarpınca
M22/K01 saklama kuralını bayt, geri alma penceresi ve ulaşılamaz dağıtım üzerinden taramıştı; o tarama tekrarlanmaz. Katmanlı depoda kuralın önünde yeni bir engel vardır: bir imajın silinmesi katmanlarının silinmesi demek değildir. Katman ancak hiçbir manifest onu referans etmiyorsa silinebilir.
IM40. Saklama kuralı sayıya bağlıdır — “son K imaj tutulur” — ve K, ayrı manifestler üzerinden sayılır. Tarama önce hesaplanır, sonra bir kural gerçekten uygulanır: dosyalar silinir ve dizin yeniden ölçülür.
// olcum-agi/silme.mjs — saklama kurali katman referansina carpinca ne siliniyor (gercek dosyalar) import { readdirSync, statSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { KOK, gecmis, depoyuKur, dizinBayt, kb } from "./depo.mjs"; const G = depoyuKur(gecmis()); const ayriImajlar = []; // yapim sirasina gore ayri manifestler for (const d of G) if (!ayriImajlar.some((x) => x.imaj === d.imaj)) ayriImajlar.push(d); const bayt = (o) => statSync(join(KOK, "katman", o)).size; const tumKatmanlar = readdirSync(join(KOK, "katman")); const toplamDepo = dizinBayt(join(KOK, "katman")); console.log("kural".padEnd(14) + "tutulan".padEnd(10) + "referansli".padEnd(13) + "silinebilir".padEnd(13) + "bosalan".padEnd(11) + "kalan depo"); for (const K of [3, 5, 8, 12, 17]) { const tutulan = ayriImajlar.slice(-K); const referansli = new Set(tutulan.flatMap((d) => d.ozetler)); const silinebilir = tumKatmanlar.filter((o) => !referansli.has(o)); const bosalan = silinebilir.reduce((t, o) => t + bayt(o), 0); console.log(`son ${K} imaj`.padEnd(14) + `${K}/${ayriImajlar.length}`.padEnd(10) + `${referansli.size}/${tumKatmanlar.length}`.padEnd(13) + `${silinebilir.length}/${tumKatmanlar.length}`.padEnd(13) + kb(bosalan).padEnd(11) + kb(toplamDepo - bosalan)); } // "son 8 imaj" kurali gercekten uygulanir: dosyalar silinir, dizin yeniden olculur. const tutulan = ayriImajlar.slice(-8); const referansli = new Set(tutulan.flatMap((d) => d.ozetler)); const boyut = new Map(tumKatmanlar.map((o) => [o, bayt(o)])); const dusen = ayriImajlar.slice(0, -8); const dusenKatman = new Set(dusen.flatMap((d) => d.ozetler)); const oncesi = dizinBayt(join(KOK, "katman")); let silinen = 0; for (const o of tumKatmanlar) if (!referansli.has(o)) { unlinkSync(join(KOK, "katman", o)); silinen++; } for (const d of dusen) unlinkSync(join(KOK, "manifest", d.imaj)); const sonrasi = dizinBayt(join(KOK, "katman")); const yasayan = [...dusenKatman].filter((o) => referansli.has(o)); const dusenBayt = [...dusenKatman].reduce((t, o) => t + boyut.get(o), 0); console.log(`\n"son 8 imaj" uygulandi: ${dusen.length} manifest ve ${silinen} katman dosyasi silindi`); console.log(`katman dizini ${kb(oncesi)} -> ${kb(sonrasi)} (diskten olculdu): ` + `${ayriImajlar.length} imajin ${dusen.length}'u dustu, bayt %${(100 * (oncesi - sonrasi) / oncesi).toFixed(0)} azaldi`); console.log(`dusen imajlarin ${dusenKatman.size} ayri katmanindan ${yasayan.length} tanesi hala ` + `referanslidir: ${kb(dusenBayt)} baytin ${kb(yasayan.reduce((t, o) => t + boyut.get(o), 0))} kadari silinemedi`);
kural tutulan referansli silinebilir bosalan kalan depo son 3 imaj 3/17 7/27 20/27 126.5 KB 49.7 KB son 5 imaj 5/17 10/27 17/27 111.3 KB 64.9 KB son 8 imaj 8/17 13/27 14/27 102.5 KB 73.7 KB son 12 imaj 12/17 20/27 7/27 56.1 KB 120.1 KB son 17 imaj 17/17 27/27 0/27 0.0 KB 176.2 KB "son 8 imaj" uygulandi: 9 manifest ve 14 katman dosyasi silindi katman dizini 176.2 KB -> 73.7 KB (diskten olculdu): 17 imajin 9'u dustu, bayt %58 azaldi dusen imajlarin 17 ayri katmanindan 3 tanesi hala referanslidir: 129.8 KB baytin 27.3 KB kadari silinemedi
Tablo doğrusal değildir. On yedi imajın üçü tutulduğunda depo 49,7 KB’ye iner; sekizi tutulduğunda 73,7 KB olur. Tutulan imaj sayısı iki buçuk katına çıkarken depo yalnız yarı yarıya büyür, çünkü yeni tutulan imajların katmanlarının çoğu zaten oradadır.
Uygulanan kural asıl noktayı verir. Dokuz manifest silinmiş, ama bu dokuz imajın referans ettiği on yedi ayrı katmanın üçü hâlâ tutulan imajlarda geçtiği için silinememiştir: 129,8 KB’nin 27,3 KB’si diskte kalmıştır. Saklama kuralı manifestler üzerinde yazılır, baytlar üzerinde çalışmaz. “Şu imajı sil” cümlesi, katmanlı bir depoda “şu manifesti sil ve referansı düşen katmanları topla” anlamına gelir — ve bu iki iş arasında bir toplayıcı adım vardır.
Bu, silme yönünde bir yalıtım eksiğidir de. Bir imajın silinmiş olması, içindeki baytların depodan gitmiş olması anlamına gelmez; özetini bilen için o katmanlar hâlâ okunabilir.
Çekme: Yerelde Olan Atlanınca
Depodaki paylaşım yalnız disk kazancı değildir; çekmede de aynı hesap işler. Bir düğüm zaten sahip olduğu bir katmanı yeniden indirmez.
IM41. Bir düğüm on yedi imajı sırayla çeker ve yerel katman önbelleği hiç boşaltılmaz. Kopyalama gerçektir: katman dosyaları depodan yerel dizine kopyalanır ve inen bayt diskten ölçülür. IM42. Kimlik doğrulamanın kendisi modelin dışındadır; ölçülen şey erişim denetiminin hangi düzeyde uygulanabildiğidir.
// olcum-agi/cekme.mjs — yerelde var olan katman atlaninca inen bayt (gercek kopyalama) import { mkdirSync, rmSync, existsSync, copyFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { KOK, gecmis, depoyuKur, dizinBayt, kb } from "./depo.mjs"; const G = depoyuKur(gecmis()); const YEREL = join(KOK, "yerel"); rmSync(YEREL, { recursive: true, force: true }); mkdirSync(YEREL, { recursive: true }); const ayri = []; for (const d of G) if (!ayri.some((x) => x.imaj === d.imaj)) ayri.push(d); // Bir dugum imajlari sirayla ceker: yerelde olan katman dosyasi kopyalanmaz. let inen = 0, tam = 0, atlanan = 0, ilkCekme = 0; const satirlar = []; for (const d of ayri) { let cekmeBayt = 0, yeni = 0; for (const o of d.ozetler) { const hedef = join(YEREL, o); if (existsSync(hedef)) { atlanan += statSync(join(KOK, "katman", o)).size; continue; } copyFileSync(join(KOK, "katman", o), hedef); cekmeBayt += statSync(hedef).size; yeni += 1; } const imajBayt = d.katmanlar.reduce((t, k) => t + k.bayt, 0); inen += cekmeBayt; tam += imajBayt; if (satirlar.length === 0) ilkCekme = cekmeBayt; satirlar.push(`${String(d.no).padStart(3)} ${yeni}/4 ${kb(cekmeBayt).padStart(8)} ${kb(imajBayt)}`); } console.log("ilk cekme ve son uc cekme"); console.log(" no yeni inen tam imaj"); for (const s of [satirlar[0], ...satirlar.slice(-3)]) console.log(s); console.log(`\n${ayri.length} cekme: inen ${kb(inen)}, tam boyutlar toplami ${kb(tam)}, ` + `atlanan ${kb(atlanan)} (%${(100 * atlanan / tam).toFixed(1)})`); console.log(`ilk (soguk) cekme ${kb(ilkCekme)}; sonraki ${ayri.length - 1} cekmenin ortalamasi ` + kb((inen - ilkCekme) / (ayri.length - 1))); console.log(`yerel onbellek diskte ${kb(dizinBayt(YEREL))}, depo ${kb(dizinBayt(join(KOK, "katman")))}`); // Erisim denetimi: genel ve ozel imajlar tek bir icerik adresli havuzu paylasiyor. const genel = ayri.filter((d) => !d.gizli), ozel = ayri.filter((d) => d.gizli); const genelK = new Set(genel.flatMap((d) => d.ozetler)); const ozelK = new Set(ozel.flatMap((d) => d.ozetler)); const ortak = [...ozelK].filter((o) => genelK.has(o)); const ortakBayt = ortak.reduce((t, o) => t + statSync(join(KOK, "katman", o)).size, 0); const ozelTek = [...ozelK].filter((o) => !genelK.has(o)); console.log(`\ngenel imaj ${genel.length}, ozel imaj ${ozel.length}`); console.log(`ozel imajlarin ${ozelK.size} katmanindan ${ortak.length} tanesi genel imajlarda da ` + `gecer (${kb(ortakBayt)}); yalniz ozel olan ${ozelTek.length} katman`);
ilk cekme ve son uc cekme no yeni inen tam imaj 1 4/4 34.5 KB 34.5 KB 17 2/4 8.0 KB 34.5 KB 18 1/4 7.2 KB 34.5 KB 19 2/4 8.0 KB 34.5 KB 17 cekme: inen 176.2 KB, tam boyutlar toplami 586.5 KB, atlanan 410.3 KB (%70.0) ilk (soguk) cekme 34.5 KB; sonraki 16 cekmenin ortalamasi 8.9 KB yerel onbellek diskte 176.2 KB, depo 176.2 KB genel imaj 10, ozel imaj 7 ozel imajlarin 16 katmanindan 9 tanesi genel imajlarda da gecer (63.4 KB); yalniz ozel olan 7 katman
On yedi çekmenin toplam indirdiği 176,2 KB, imajların toplam boyutunun 586,5 KB olmasına karşın. Atlanan 410,3 KB, trafiğin yüzde 70’idir. İlk çekme tam bir imaj indirir (34,5 KB); sonraki on altı çekmenin ortalaması 8,9 KB, yani bir imajın dörtte biri.
İnen toplamın depo boyutuna eşit çıkması rastlantı değildir: bütün imajları çeken bir düğüm, ayrı katman kümesini tam olarak bir kez indirir. Bu, çekme trafiğinin üst sınırının imaj sayısı değil ayrı katman sayısı olduğu anlamına gelir.
Bedelin sayısı ise yerel önbellektir: düğümde 176,2 KB tutulur ve bu, o an çalışan tek bir imajın beş katıdır. Yalıtımın çekme tarafındaki karşılığı budur — indirme azalır, saklama artar.
Genel Depo, Özel Depo
Çıktının son iki satırı erişim denetimini ölçer. On yedi imajın onu genel, yedisi özeldir. Kimlik doğrulama manifest düzeyinde uygulanır: özel bir imajın manifestini istemek için kimlik gerekir. Katmanlar ise tek bir içerik adresli havuzda durur.
Sonuç ölçülmüştür: özel imajların on altı katmanından dokuzu genel imajlarda da geçer — 63,4 KB. Bu dokuz katmanın özeti genel bir manifestten okunabilir ve okunduğunda o baytlar kimlik gerektirmeden çekilebilir. Özel imaja özgü olan yalnız yedi katmandır; gizli olan da odur.
Bu, bu dersin yalıtımın delindiği yeridir ve iki yönü vardır. Bir yanda kayıp gerçektir: “özel depo” ifadesi baytların tamamının gizli olduğunu düşündürür, ölçüm bunun yedi katmanla sınırlı olduğunu söyler. Öbür yanda kapatmanın bedeli sayılabilir: paylaşımı kaldırıp özel imajlara ayrı bir havuz vermek gizliliği katmanların tamamına yayar, ama deponun paylaşımdan kazandığı 513,8 KB’nin bir bölümünü geri verir. Yalıtım bir bütçedir ve bu kalem de bütçeden çıkar.
Özet
- İçerik adresli depoda 80 katman referansı 27 dosyaya karşılık gelir: depo diskte 176,2 KB, paylaşımsız olsaydı 690,0 KB. Paylaşım baytların yüzde 74,5’ini kaldırır.
- Referans dağılımı homojen değildir: bir katman 16 imajda geçer, ortanca 2’dir, 27 katmanın 11’i yalnız tek imajda geçer.
- Saklama kuralı manifestler üzerinde yazılır, baytlar üzerinde çalışmaz. “Son 8 imaj” uygulandığında 9 manifest silinmiş, düşen imajların 17 ayrı katmanından 3’ü hâlâ referanslı olduğu için 129,8 KB’nin 27,3 KB’si diskte kalmıştır.
- Yerelde bulunan katman atlandığında 17 çekmenin indirdiği 586,5 KB yerine 176,2 KB olur; atlanan yüzde 70’tir. Çekme trafiğinin üst sınırı imaj sayısı değil ayrı katman sayısıdır. Bedeli düğümde tutulan 176,2 KB’lik yerel önbellektir.
- Erişim denetimi manifest düzeyinde uygulanır, katmanlar havuzu paylaşır: 7 özel imajın 16 katmanından 9’u (63,4 KB) genel imajlarda da geçer ve özeti bilinerek kimliksiz okunabilir. Gizli olan yalnız 7 katmandır.
Sonraki Adım
Depo ölçüldü, ama bir soru açık kaldı. Özel imajın dokuz katmanı genel havuzda okunabiliyorsa, o katmanların içinde ne var? Bu dersin ölçümü katmanlara birer bayt yığını olarak baktı ve içlerine hiç bakmadı.
Sonraki ders içeri bakar ve konuyu kapatır: üst katmanda silinmiş bir dosya alt katmanda gerçekte duruyor mu ve kaç bayt gerçekten silinmemiş, bilinen kusurlar üzerinde koşan bir tarama kaçını yakalıyor ve kaçını kaçırıyor, imzalama zinciri neyi doğruluyor ve imzasız bir çekme hangi yolu açıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.