İçeriğe geç
academia.sh

Ders 06 / 21

İmaj ve Katman Modeli

İmaj sıralı bir katman listesi olarak ölçülür: iki servisin ortak temel üzerine kurulduğunda kaç katmanı paylaştığı, kaç baytın bir kez saklandığı ve düz kopyalamaya göre kazancın ne olduğu sayılır; bir alt katman değişince kaç üst katmanın zincir kimliğinin geçersizleştiği ve silinen bir dosyanın depoda kaç bayt olarak durduğu ölçülür.

İçindekiler

Önceki konu yalıtımın ne olduğunu tanımladı ve bir sürecin çevresine çekilen sınırın neyi kaç bayta yalıttığını ölçtü. O ölçümlerin tamamı boyunca çalıştırılan şeyin nereden geldiği hiç sorulmadı. Yalıtılan süreç bir dosya sisteminin içinde açılır ve o dosya sistemi bir yerden gelir; bu ders onun nereden geldiğini ölçer.

Önceki kursta yapı çıktısı tek bir nesne olarak tanımlanmış, değişmezlik ilkesi ve içerik özeti orada ölçülmüştü. Konteyner çıktısı aynı ilkeye bağlıdır — bir kez üretilir, yerinde değiştirilmez, kimliği içeriğinin özetidir — ama tek dosya değildir. İmaj, sıralı bir katman listesidir ve bu ayrımın nedeni sayılabilir: iki servis aynı tabanı kullandığında tek dosyalık çıktı iki tam kopya saklar, katmanlı çıktı ortak bölümü bir kez saklar. Sorular şunlardır: kaç katman paylaşılır, kaç bayt bir kez saklanır, bir alt katman değişince üstünde ne geçersizleşir?

IM1. Aşağıdaki katman modeli node ile kurulur ve gerçek dizinler üzerinde koşar; gerçek bir konteyner çalıştırılmaz. Katman bir dizindir, imaj bir katman adı listesidir. IM2. İçerik özeti, sıralı yol ve bayt çiftleri üzerinden alınan sha256 değerinin ilk on iki onaltılık basamağıdır; kısaltma okunabilirlik içindir ve önceki kursta kullanılan biçimle aynıdır. IM3. Kurgudaki bölgesel ölçüm ağının iki servisi ölçülür: su sayacı okumalarını toplayan servis ve okumaları doğrulayan servis. Ağ ve servisler kurgudur.

Katman, İmaj, Zincir Kimliği

Katman modelinin üç parçası vardır. Katman, bir dosya kümesidir ve kendi içerik özetiyle adlandırılır. İmaj, alttan üste sıralı bir katman listesidir; çalıştırılacak dosya sistemi bu listenin sırayla üst üste bindirilmesiyle oluşur. Zincir kimliği, bir katmanın imaj içindeki konumunu adlandırır ve önceki zincir kimliği ile katmanın içerik özetinden türetilir — aynı katman iki farklı imajda aynı içerik özetini taşır ama altındaki yığın farklıysa farklı zincir kimliği alır. Ayrım, ölçümün geri kalanının dayandığı yerdir.

IM4. Katman içerikleri satır sayısıyla ölçeklenen üretilmiş dosyalardır; ölçülen büyüklükler bu üretimin gerçek bayt karşılığıdır, bir ürünün gerçek imaj boyutu değildir.

// olcum-agi/katman.mjs — katmanlari gercek dizinler olarak kurar (model)
import { mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from "node:fs";
import { createHash } from "node:crypto";

const KOK = "katmanlar";
const ozetle = (s) => createHash("sha256").update(s).digest("hex").slice(0, 12);

// Katman govdesi: satir sayisiyla olceklenen, uretimi yinelenebilir dosya icerigi.
export const govde = (ad, satir) =>
  Array.from({ length: satir }, (_, i) => `// ${ad} ${i}\nexport const a${i} = ${i * 7};\n`).join("");

export const KATMAN = {
  "temel-kok": { "kok/kabuk.js": govde("kabuk", 30), "kok/saat.js": govde("saat", 14) },
  "ortak-kutuphane": { "lib/olcu.js": govde("olcu", 120), "lib/kod.js": govde("kod", 46) },
  "ortak-ayar": { "ayar/bolge.json": '{"bolge":"kuzey","sayac":41820}\n' },
  "toplayici-bagimlilik": { "bagimlilik/kuyruk.js": govde("kuyruk", 190), "bagimlilik/csv.js": govde("csv", 62) },
  "toplayici-kaynak": { "uygulama/topla.js": govde("topla", 34), "uygulama/giris.js": govde("giris", 9) },
  "dogrulayici-bagimlilik": { "bagimlilik/kural.js": govde("kural", 88) },
  "dogrulayici-kaynak": { "uygulama/dogrula.js": govde("dogrula", 27), "uygulama/giris.js": govde("dgiris", 11) },
};

export const kur = (tanim = KATMAN) => {
  rmSync(KOK, { recursive: true, force: true });
  for (const [ad, dosyalar] of Object.entries(tanim))
    for (const [yol, icerik] of Object.entries(dosyalar)) {
      mkdirSync(`${KOK}/${ad}/${yol}`.split("/").slice(0, -1).join("/"), { recursive: true });
      writeFileSync(`${KOK}/${ad}/${yol}`, icerik);
    }
};

// Diskteki gercek dizini gezer; katmanin dosya listesi, bayti ve icerik ozeti buradan cikar.
export const gez = (ad, alt = "") => {
  const kok = `${KOK}/${ad}${alt}`;
  return readdirSync(kok, { withFileTypes: true }).flatMap((g) =>
    g.isDirectory() ? gez(ad, `${alt}/${g.name}`) : [`${alt}/${g.name}`.slice(1)]).sort();
};

export const katman = (ad) => {
  const yollar = gez(ad);
  const govdeler = yollar.map((y) => readFileSync(`${KOK}/${ad}/${y}`));
  return { ad, dosya: yollar.length, yollar,
    bayt: govdeler.reduce((t, g) => t + g.length, 0),
    ozet: ozetle(yollar.map((y, i) => `${y}\n${govdeler[i]}`).join("\n")) };
};

// Zincir kimligi: onceki zincirin kimligi ile katmanin icerik ozetinden turer.
export const zincir = (adlar) =>
  adlar.reduce((z, ad) => [...z, ozetle((z.at(-1) ?? "") + katman(ad).ozet)], []);

export const TOPLAYICI = ["temel-kok", "ortak-kutuphane", "ortak-ayar", "toplayici-bagimlilik", "toplayici-kaynak"];
export const DOGRULAYICI = ["temel-kok", "ortak-kutuphane", "ortak-ayar", "dogrulayici-bagimlilik", "dogrulayici-kaynak"];

Yığının sırası rastgele değildir: altta değişme sıklığı en düşük olan taban, üstte en sık değişen uygulama kaynağı durur. Bu sıranın karşılığı bir sonraki ölçümde çıkar.

Paylaşılan Katmanın Ölçüsü

İki servis aynı taban üzerine kurulur: kök dosyaları, ortak ölçü kütüphanesi ve bölge ayarı üç katmandır. Üstlerinde her servisin kendi bağımlılık ve kaynak katmanları durur.

// olcum-agi/olc.mjs — iki imajin katman paylasimini ve depo kazancini sayar
import { kur, katman, zincir, TOPLAYICI, DOGRULAYICI } from "./katman.mjs";

kur();
const hepsi = [...new Set([...TOPLAYICI, ...DOGRULAYICI])].map(katman);
console.log("katman".padEnd(24) + "dosya  bayt   icerik ozeti");
for (const k of hepsi)
  console.log(k.ad.padEnd(24) + String(k.dosya).padStart(3) + String(k.bayt).padStart(8) + "   " + k.ozet);

const boyut = (imaj) => imaj.reduce((t, ad) => t + katman(ad).bayt, 0);
const paylasilan = TOPLAYICI.filter((ad) => DOGRULAYICI.includes(ad));
const depo = hepsi.reduce((t, k) => t + k.bayt, 0);
const duz = boyut(TOPLAYICI) + boyut(DOGRULAYICI);

console.log(`\nokuma-toplayici: ${TOPLAYICI.length} katman, ${boyut(TOPLAYICI)} bayt`);
console.log(`dogrulayici    : ${DOGRULAYICI.length} katman, ${boyut(DOGRULAYICI)} bayt`);
console.log(`paylasilan katman ${paylasilan.length}, bir kez saklanan ${boyut(paylasilan)} bayt`);
console.log(`duz kopyalama ${duz} bayt / katman deposu ${hepsi.length} katman ${depo} bayt`);
console.log(`kazanc ${duz - depo} bayt, %${(100 - (depo * 100) / duz).toFixed(1)}`);
console.log("zincir kimlikleri (toplayici): " + zincir(TOPLAYICI).map((z) => z.slice(0, 8)).join(" "));
katman                  dosya  bayt   icerik ozeti
temel-kok                 2    1497   7388fc361497
ortak-kutuphane           2    5730   9a6817a68730
ortak-ayar                1      32   48b10c98a78a
toplayici-bagimlilik      2    9291   906f2e7219b3
toplayici-kaynak          2    1482   af5db4e03ee4
dogrulayici-bagimlilik    1    3131   fcc132f093eb
dogrulayici-kaynak        2    1363   f578b5ac2648

okuma-toplayici: 5 katman, 18032 bayt
dogrulayici    : 5 katman, 11753 bayt
paylasilan katman 3, bir kez saklanan 7259 bayt
duz kopyalama 29785 bayt / katman deposu 7 katman 22526 bayt
kazanc 7259 bayt, %24.4
zincir kimlikleri (toplayici): 6555bbc9 54daa57b adf0e7c1 3334e87d ad53ce55

İki imajın toplam boyutu 29 785 bayt, katman deposunda saklanan ise 22 526 bayt: ortak üç katmanın 7 259 baytı iki imaj için bir kez saklanır, kazanç yüzde 24,4’tür. Ölçünün okunması gereken yeri oran değil, oranın nereden geldiğidir: kazanç, paylaşılan tabanın toplam içindeki payı kadardır. Her servisin kendi bağımlılık katmanı büyüdükçe oran düşer, ortak taban büyüdükçe yükselir. İki servis yerine yirmi servis aynı tabanı kullanırsa aynı 7 259 bayt yirmi imaj için bir kez saklanır ve tasarruf katman sayısıyla değil, paylaşan imaj sayısıyla çarpılır.

Bu, önceki kursun tek dosyalık yapı çıktısına göre kazanılan şeydir. Aynı ölçüde ortamdan çıkarılan fark da buradadır: ortak kütüphanenin sürümü ve bölge ayarı artık çalıştırılan makinede kurulu olan bir şey değil, çıktının içinde duran ve özetle adlandırılan iki katmandır. Ortamda o kütüphanenin başka bir sürümünün bulunması ölçümü değiştirmez, çünkü ölçülen dosya sistemi katmanlardan doğar.

Bir Alt Katman Değiştiğinde

Katmanlı çıktının bedeli, katmanların birbirine zincirle bağlı olmasıdır. Ortak kütüphaneye tek bir satır eklenir ve iki imajda ne değiştiği sayılır.

IM5. Değişiklik en küçük ölçekte tutulur — ortak kütüphanenin bir dosyasına tek satır eklenir — ki geçersizleşen zincirin uzunluğu değişikliğin büyüklüğünden değil, yalnız konumundan gelsin. IM6. Silme, üst katmana konan bir işaret dosyasıyla modellenir: silindi/ önekiyle yazılan boş bir yol, alttaki aynı yolu görünümden çıkarır. Gerçek birleşim dosya sistemlerinde bu işaretin biçimi başkadır, davranışı aynıdır.

// olcum-agi/degis.mjs — alt katman degisince ne gecersizlesir, silinen dosya nerede kalir
import { writeFileSync, readFileSync } from "node:fs";
import { kur, katman, zincir, govde, KATMAN, TOPLAYICI, DOGRULAYICI } from "./katman.mjs";

kur();
const once = { t: zincir(TOPLAYICI), d: zincir(DOGRULAYICI) };
const oncekiOzet = Object.fromEntries([...TOPLAYICI, ...DOGRULAYICI].map((a) => [a, katman(a).ozet]));
const eskiBayt = katman("ortak-kutuphane").bayt;

writeFileSync("katmanlar/ortak-kutuphane/lib/olcu.js", govde("olcu", 121)); // tek satir eklendi
const sonra = { t: zincir(TOPLAYICI), d: zincir(DOGRULAYICI) };
const ozetDegisen = [...new Set([...TOPLAYICI, ...DOGRULAYICI])].filter((a) => katman(a).ozet !== oncekiOzet[a]);
const zincirDegisen = ["t", "d"].map((i) => once[i].filter((z, s) => z !== sonra[i][s]).length);

console.log(`icerik ozeti degisen katman : ${ozetDegisen.length} (${ozetDegisen.join(", ")})`);
console.log(`zincir kimligi gecersizlesen: ${zincirDegisen.reduce((a, b) => a + b)} (imaj basina ${zincirDegisen.join(", ")})`);
console.log(`icerigi ayni kalan ust katman: ${[...new Set([...TOPLAYICI, ...DOGRULAYICI])].length - ozetDegisen.length - 1}`);
console.log(`deponun buyumesi: ${katman("ortak-kutuphane").bayt} bayt (eski surum ${eskiBayt} bayt yerinde durur)`);

// Ust katman: bir yolu golgeler, bir yolu siler. Silme, alt katmani degil gorunumu degistirir.
kur({ ...KATMAN, "toplayici-yama": { "ayar/bolge.json": '{"bolge":"kuzey","sayac":41820,"tur":"yama"}\n',
  "silindi/lib/kod.js": "" } });
const yigin = [...TOPLAYICI, "toplayici-yama"];

const gorunum = new Map();
let golge = 0, silinenBayt = 0;
for (const ad of yigin)
  for (const yol of katman(ad).yollar) {
    if (yol.startsWith("silindi/")) {
      const hedef = yol.slice(8);
      silinenBayt += readFileSync(`katmanlar/${gorunum.get(hedef)}/${hedef}`).length;
      gorunum.delete(hedef);
    } else {
      if (gorunum.has(yol)) { golge++; silinenBayt += readFileSync(`katmanlar/${gorunum.get(yol)}/${yol}`).length; }
      gorunum.set(yol, ad);
    }
  }

const yiginBayt = yigin.reduce((t, ad) => t + katman(ad).bayt, 0);
console.log(`\nyigindaki katman ${yigin.length}, yigindaki toplam bayt ${yiginBayt}`);
console.log(`birlesim gorunumundeki dosya ${gorunum.size}, golgelenen yol ${golge}, silinen yol 1`);
console.log(`gorunumde olmayan ama depoda duran bayt: ${silinenBayt}`);
console.log("gorunumde lib/kod.js var mi: " + gorunum.has("lib/kod.js"));
icerik ozeti degisen katman : 1 (ortak-kutuphane)
zincir kimligi gecersizlesen: 8 (imaj basina 4, 4)
icerigi ayni kalan ust katman: 5
deponun buyumesi: 5767 bayt (eski surum 5730 bayt yerinde durur)

yigindaki katman 6, yigindaki toplam bayt 18077
birlesim gorunumundeki dosya 8, golgelenen yol 1, silinen yol 1
gorunumde olmayan ama depoda duran bayt: 1559
gorunumde lib/kod.js var mi: false

Tek satırlık bir değişiklik bir katmanın içerik özetini değiştirir ama sekiz zincir kimliğini geçersizleştirir: iki imajın her birinde değişen katman ve onun üstündeki üç katman. Üstteki beş ayrı katmanın içeriği bit düzeyinde aynı kalır; geçersizleşen şey içerik değil, konumdur. Değişiklik ne kadar aşağıdaysa geçersizleşen zincir o kadar uzar — bir satırın maliyeti, satırın kendisi değil, üstünde kaç katman olduğudur.

İkinci bedel depodadır. Değişmezlik ilkesi gereği eski katman silinmez: depo 5 730 baytlık sürümü yerinde tutar ve 5 767 baytlık yenisini yanına ekler. Katman deposu yalnız büyür; iki sürüm arasında bir satırlık fark olması bunu değiştirmez, çünkü katman içerik özetiyle adlanır ve içeriği değişen katman yeni bir nesnedir. Küçük ve sık değişen katmanları üste, büyük ve seyrek değişenleri alta koymanın karşılığı bu iki sayıdır.

Aynı ölçüm ters yönden de okunur. Aynı model üzerinde iki uç denendiğinde sayılar şöyle çıkar: en üstteki kaynak katmanı değiştiğinde geçersizleşen zincir kimliği toplayıcı imajında 1, doğrulayıcı imajında 0; en alttaki kök katmanı değiştiğinde imaj başına 5, toplam 10. Aynı büyüklükteki bir değişikliğin bedeli konumuna göre birden ona kadar oynar. Yığın sırası bu yüzden bir düzen tercihi değil, bir maliyet kararıdır: her katmanın konumu, o katman değiştiğinde ödenecek bedeli önceden belirler. Ölçünün karar taşıyan biçimi budur — katman sayısı tek başına iyi ya da kötü değildir, katmanın hangi sıklıkta değiştiği ile üstünde kaç katman olduğu çarpımı bir bedeldir.

Birleşim Görünümünün Gizlediği

Katmanlar üst üste bindirilirken bir birleşim dosya sistemi görünümü doğar: aynı yol iki katmanda varsa üstteki kazanır, üst katmandaki bir silme işareti alttaki dosyayı görünümden çıkarır. Ölçüm, görünümün gizlediğini sayar. Altı katmanlık yığın 18 077 bayt taşır, birleşim görünümünde 8 dosya görünür; bir yol gölgelenmiş, bir yol silinmiştir. Ama görünümden çıkan 1 559 bayt hâlâ depodadır ve imajın parçasıdır.

Yalıtımın delindiği yer burasıdır. Katman modeli dosya sistemini ortamdan çıkarır ve çıktının içine alır; karşılığında çıktının geçmişini de içine alır. Bir üst katmanda silinen dosya görünümde yoktur, imajda vardır: alt katman diskte durduğu ve içerik özetiyle çözülebildiği için imajı eline geçiren biri o dosyayı okuyabilir. Silme bir görünüm işlemidir, bir depo işlemi değil.

# dogrula.sh — katmanlar diskte gercek dizin mi, silinen dosya hala orada mi
find katmanlar -type f | wc -l
ls katmanlar/toplayici-yama/silindi/lib/
wc -c katmanlar/ortak-kutuphane/lib/kod.js
      14
kod.js
    1527 katmanlar/ortak-kutuphane/lib/kod.js

Kabuktan bakıldığında yedi katmanın toplam on dört dosyası diskte durur; üst katmanda silme işareti bulunan lib/kod.js, alt katmanda 1 527 baytlık gerçek içeriğiyle okunabilir hâldedir. Birleşim görünümü onu göstermez, dosya sistemi gösterir.

Katmanın Taşımadığı

Buraya kadarki ölçümler katman modelinin çıktının içine aldığı şeyi saydı: dosya sistemi düzeni, kütüphane sürümü, ayar dosyası. Yalıtım bütçesinin diğer yarısı, içine alınmayanlardır. Katman bir dosya kümesidir — süreç değildir, çekirdek değildir, saat değildir. Yığının en altındaki temel-kok katmanı da yalnız dosyalardan oluşur; imaj çalıştığında altında duran çekirdek, çalıştırıldığı makinenin çekirdeğidir.

Bunun üç somut karşılığı vardır. Birincisi işlemci mimarisi: katmanlardaki çalıştırılabilir dosyalar bir mimari için üretilir, imaj bu bağı içinde taşımaz, dışarıda varsayar. İçerik özeti dosyaların baytlarını adlandırır, o baytların hangi mimaride anlam taşıdığını değil; aynı özet, başka bir mimaride çalışmayan bir dosya sistemi tanımlayabilir. İkincisi çekirdek arayüzü: katman içindeki bir dosya ortamda bulunmayan bir çekirdek yeteneğini çağırırsa imaj kusursuz biçimde çözülür, zincir kimlikleri doğrulanır ve süreç yine de başlamaz. Üçüncüsü makine künyesi: önceki kursta yeniden üretilebilirliği bozan kaynaklar arasında sayılan zaman damgası ve mutlak yol, katman içeriğine yazıldığı anda katmanın içerik özetine karışır. Katman modeli bu kaynakları kapatmaz; yalnız nereye yazıldıklarını görünür kılar, çünkü artık ölçülebilir bir nesnenin içindedirler.

Ayrım tek cümleye iner: imaj ne çalıştırılacağını taşır, nerede çalıştırılacağını taşımaz. Bu dersin ölçtüğü 22 526 baytlık katman deposu, ortamdan çıkarılan farkın tamamıdır; geri kalanı makinenin kendisi olarak durur. Yalıtım bütçesinin bu dersteki hesabı üç kalemdir: çıkarılan fark bir dosya sistemi, bedeli 22 526 bayt ile her değişiklikte sekiz zincir kimliği, delindiği yer ise görünümden çıkıp depoda kalan 1 559 bayttır.

Özet

  • İmaj tek dosyalık bir yapı çıktısı değil, alttan üste sıralı bir katman listesidir; her katman kendi içerik özetiyle, imaj içindeki her konum ise önceki zincirden ve katman özetinden türeyen bir zincir kimliğiyle adlanır.
  • Ortak temel üzerine kurulan iki imaj üç katmanı paylaştı: düz kopyalamada 29 785 bayt olan toplam, katman deposunda 22 526 bayta indi; 7 259 bayt bir kez saklandı, kazanç yüzde 24,4 oldu. Kazanç paylaşan imaj sayısıyla çarpılır, katman sayısıyla değil.
  • Ortak kütüphaneye eklenen tek satır bir katmanın içerik özetini değiştirdi ama sekiz zincir kimliğini geçersizleştirdi; içeriği bit düzeyinde aynı kalan beş üst katman yeni konum kimliği aldı. Değişiklik yığında ne kadar aşağıdaysa geçersizleşen zincir o kadar uzar.
  • Değişmezlik gereği eski katman silinmez: 5 730 baytlık sürüm yerinde kalır, 5 767 baytlık yenisi yanına eklenir. Katman deposu yalnız büyür.
  • Birleşim görünümü siler gibi görünür, depo silmez. Bir yol gölgelendiğinde ve bir yol silindiğinde görünümde 8 dosya kaldı, ama görünümün dışına çıkan 1 559 bayt imajın içinde durmayı sürdürdü.

Sonraki Adım

Bu dersin katmanları elle tanımlandı: hangi dosyanın hangi katmana gireceği bir nesne sabitinde yazılıydı. Gerçek bir imaj böyle kurulmaz — katmanlar, sırayla uygulanan yönergelerden doğar ve her yönerge bir katman üretir. Bu da iki yeni soru açar. Birincisi, yönergelerin kopyalayacağı dosyalar nereden gelir: derleme bağlamı olarak adlandırılan bu kümeye kaç dosya ve kaç bayt girer, girenlerin kaçı imaja hiç ulaşmaz? İkincisi, bu kümeyi daraltan dışlama kuralı kaç dosyayı ve kaç baytı keser? Sonraki ders bir imaj tanım dosyası biçimi tanımlar, yönergeleri sırayla uygulayan bir yorumlayıcı yazar ve derleme bağlamının büyüklüğünü ölçer.

İ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