İçeriğe geç
academia.sh

Ders 12 / 19

Önbellek Kırma

Dosya adının içeriğe bağlanması; karma üretimi ve belirlenimcilik, karmanın bağımlı parçalarda zincirleme değişmesi, içe aktarım haritasıyla zincirin kırılması ve ada göre önbellek politikası.

İçindekiler

Bir önceki dersin çıktı dizinindeki dosyalar kaynaktaki adlarını taşıyordu. Bu, önbellek tarafında çözümsüz bir ikilem bırakır. Kuzey Yamaç Ölçüm İstasyonu sitesinin biçem dosyası /istasyon.css adresinden veriliyorsa ve bu adrese uzun ömürlü bir önbellek politikası uygulanıyorsa, dosyanın içeriği değiştiğinde kullanıcıya eskisi verilmeye devam eder. Politika kısa tutulursa her ziyarette yeniden doğrulama yapılır ve önbelleğin kazancı yeniden doğrulama turlarına gider.

İkilemin kaynağı adresin içerikten bağımsız olmasıdır. Adres içeriğe bağlanırsa ikilem ortadan kalkar: içerik değişmedikçe adres değişmez ve o adres sonsuza yakın bir süre önbellekte tutulabilir; içerik değişirse adres de değişir ve yeni adres önbellekte hiç bulunmadığı için kaçınılmaz olarak indirilir. Önbellek kırma (cache busting), bu bağı kuran adlandırma yöntemidir.

Adı İçeriğe Bağlamak

Bağ bir özet işleviyle kurulur. Dosyanın baytları bir kriptografik özet işlevinden geçirilir, sonucun bir bölümü ada eklenir. Sonuç iki özelliği birden sağlar.

Belirlenimcilik: aynı baytlar aynı adı verir. Aynı kaynaktan iki kez derleme yapıldığında adlar özdeş çıkar; bu, derlemenin yeniden üretilebilir olması için gereken koşullardan biridir ve önizleme dağıtımları dersinde tekrar kullanılacaktır.

Çakışmama: farklı baytların aynı adı vermesi olasılığı, alınan özet uzunluğuyla denetlenir. Sekiz onaltılık basamak, dört milyara yakın ayrı değer verir; bir yayındaki dosya sayısı bunun yanında küçük kaldığı için pratikte yeterlidir. Kısaltma bir güvenlik kararı değil, ad uzunluğu kararıdır — bütünlük denetimi ayrı bir düzenekle, alt kaynak bütünlüğüyle yapılır.

Karmanın son içerik üzerinden hesaplanması zorunludur. Dönüşümlerden önce hesaplanan bir karma, çıktıdaki baytları temsil etmez; ad, içeriğin değiştiği bazı durumlarda aynı kalır ve önbellek eski dosyayı vermeye devam eder.

Zincirleme Değişim

Karmalı adlandırmanın kolay görünen tarafı budur. Zor tarafı, çıktıların birbirine bağlı olmasıdır.

Bir parça, dinamik olarak yüklediği başka bir parçanın adını gövdesinde taşır. O parçanın adı değiştiğinde, bu parçanın içeriği değişir; içeriği değişince karması değişir; karması değişince adı değişir. Değişim çizgede yapraktan köke doğru yayılır. Üç katmanlı bir zincirde bir yaprak modülün tek satırlık değişikliği üç dosyanın adını değiştirir ve geri dönen bir ziyaretçiye üçünü birden yeniden indirtir.

Zinciri kıran şey, adın gövdeye gömülmemesidir. Modül belirteçleri bir içe aktarım haritası (import map) üzerinden çözülürse, parçanın gövdesi çocuğunun karmalı adını taşımaz; yalnız değişmeyen bir belirteç taşır. Harita, belgeye yazılan ve önbelleklenmeyen küçük bir eşlemedir.

Yayın Üretimi

Aşağıdaki betik iki adlandırma kipini yan yana çalıştırır ve iki değişiklik senaryosunun yeniden indirttiği baytı sayar. Biçem dosyası ve iki varlık bir önceki dersin çıktı dizininden okunur.

Bu dersin betiği, Varlık İşleme dersinin ürettiği cikti/ ağacını okur; o dersin blokları bu dizinde önce çalıştırılmalıdır.

// karma.mjs — icerik karmali dosya adlari, zincirleme degisim ve ice aktarim haritasi.
import { createHash } from "node:crypto";
import { readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";

const karma = (govde) => createHash("sha256").update(govde).digest("hex").slice(0, 8);
const karmali = (ad, k) => ad.replace(/\.([^.]+)$/, "-" + k + ".$1");

// Cikti kumesi. Govdesi null olanlar dorduncu dersin cikti dizininden okunur.
// {{...}} yer tutucusu, cocuk ciktinin yayindaki adiyla degistirilir.
const GOVDE = {
  "varlik/istasyon.svg": null,
  "varlik/olcum.woff2": null,
  "istasyon.css": '@font-face { font-family: "Olcum";\n' +
    '  src: url("{{varlik/olcum.woff2}}") format("woff2"); font-display: swap; }\n' +
    '.istasyon-harita { background-image: url("{{varlik/istasyon.svg}}"); }\n',
  "parca/grafik.js": 'export const yol = (o) => o.map((x) => x.sicaklik).join(" ");\n',
  "parca/panel.js": 'import { yol } from "{{parca/grafik.js}}";\n' +
    'export const ciz = (o) => "<svg>" + yol(o) + "</svg>";\n',
  "giris.js": 'export const ac = async () => (await import("{{parca/panel.js}}")).ciz;\n',
};
const SIRA = ["varlik/istasyon.svg", "varlik/olcum.woff2", "istasyon.css",
              "parca/grafik.js", "parca/panel.js", "giris.js"];
const BELIRTEC = { "parca/grafik.js": "kuzey:grafik", "parca/panel.js": "kuzey:panel" };

function yayinla(kip, degisiklik = {}) {
  const ad = {}, govde = {}, boyut = {};
  for (const yol of SIRA) {
    let g = degisiklik[yol] ?? GOVDE[yol] ?? readFileSync(join("cikti", yol));
    if (typeof g === "string") {
      // gomulu kip: cocugun karmali adi govdeye yazilir.
      // harita kipi: yalniz modul belirtecleri ciplak birakilir; bicem dosyasindaki
      // url() bagi ice aktarim haritasiyla cozulemez, adi gommeye devam eder.
      g = Buffer.from(g.replace(/\{\{([^}]+)\}\}/g, (_, cocuk) =>
        kip === "harita" && BELIRTEC[cocuk] ? BELIRTEC[cocuk] : "/" + ad[cocuk]));
    }
    ad[yol] = karmali(yol, karma(g));
    govde[yol] = g;
    boyut[yol] = g.length;
  }
  const harita = { imports: Object.fromEntries(
    Object.entries(BELIRTEC).map(([yol, belirtec]) => [belirtec, "/" + ad[yol]])) };
  return { ad, govde, boyut, harita: Buffer.from(JSON.stringify(harita) + "\n") };
}

const ilk = yayinla("gomulu");
console.log("-- birinci yayin --");
for (const yol of SIRA) console.log("  " + yol.padEnd(22) + ilk.ad[yol]);
console.log("  ayni kaynak iki kez derlendi, adlar ozdes: " +
  SIRA.every((y) => ilk.ad[y] === yayinla("gomulu").ad[y]));

rmSync("yayin", { recursive: true, force: true });
for (const yol of SIRA) {
  const hedef = join("yayin", ilk.ad[yol]);
  mkdirSync(dirname(hedef), { recursive: true });
  writeFileSync(hedef, ilk.govde[yol]);
}

const SENARYO = {
  "yaprak modul degisti": {
    "parca/grafik.js": GOVDE["parca/grafik.js"].replace('join(" ")', 'join(", ")'),
  },
  "gorsel varlik degisti": {
    "varlik/istasyon.svg": Buffer.from(readFileSync("cikti/varlik/istasyon.svg", "utf8")
      .replace('stroke-width="2"', 'stroke-width="3"')),
  },
};

for (const [baslik, degisiklik] of Object.entries(SENARYO)) {
  console.log("-- " + baslik + " --");
  for (const kip of ["gomulu", "harita"]) {
    const onceki = yayinla(kip), sonraki = yayinla(kip, degisiklik);
    const degisen = SIRA.filter((y) => onceki.ad[y] !== sonraki.ad[y]);
    const haritaDegisti = !onceki.harita.equals(sonraki.harita);
    const inen = degisen.reduce((t, y) => t + sonraki.boyut[y], 0) +
      (haritaDegisti ? sonraki.harita.length : 0);
    console.log("  " + kip.padEnd(8) + " adi degisen: " + degisen.join(", "));
    console.log("           yeniden inen: " + inen + " B / toplam " +
      SIRA.reduce((t, y) => t + sonraki.boyut[y], 0) + " B" +
      (haritaDegisti ? "  (harita da yenilendi)" : ""));
  }
}

console.log("-- harita kipinde uretilenler --");
process.stdout.write("  giris.js  : " + yayinla("harita").govde["giris.js"]);
process.stdout.write("  panel.js  : " + yayinla("harita").govde["parca/panel.js"]
  .toString().split("\n")[0] + "\n");
process.stdout.write("  harita    : " + yayinla("harita").harita);
$ node karma.mjs
-- birinci yayin --
  varlik/istasyon.svg   varlik/istasyon-77c2bd82.svg
  varlik/olcum.woff2    varlik/olcum-7b962f03.woff2
  istasyon.css          istasyon-7e1d6934.css
  parca/grafik.js       parca/grafik-69c71ea6.js
  parca/panel.js        parca/panel-c3924741.js
  giris.js              giris-5f45daac.js
  ayni kaynak iki kez derlendi, adlar ozdes: true
-- yaprak modul degisti --
  gomulu   adi degisen: parca/grafik.js, parca/panel.js, giris.js
           yeniden inen: 343 B / toplam 5697 B  (harita da yenilendi)
  harita   adi degisen: parca/grafik.js
           yeniden inen: 161 B / toplam 5671 B  (harita da yenilendi)
-- gorsel varlik degisti --
  gomulu   adi degisen: varlik/istasyon.svg, istasyon.css
           yeniden inen: 1356 B / toplam 5696 B
  harita   adi degisen: varlik/istasyon.svg, istasyon.css
           yeniden inen: 1356 B / toplam 5670 B
-- harita kipinde uretilenler --
  giris.js  : export const ac = async () => (await import("kuzey:panel")).ciz;
  panel.js  : import { yol } from "kuzey:grafik";
  harita    : {"imports":{"kuzey:grafik":"/parca/grafik-69c71ea6.js","kuzey:panel":"/parca/panel-7688ac67.js"}}

Çıktının Söyledikleri

Birinci bölümdeki doğrulama satırı, adlandırmanın belirlenimci olduğunu gösterir: aynı kaynaktan iki kez üretilen adlar özdeştir. Bu, yayının bir kimliği hâline gelir — iki derlemenin aynı sonucu verip vermediği, çıktıyı açmadan ad listesi karşılaştırılarak anlaşılabilir.

İlk senaryo zincirlemenin bedelini verir. Gömülü adlandırmada, yaprak parçadaki tek satırlık değişiklik üç dosyanın adını değiştirir ve geri dönen ziyaretçiye 343 bayt indirtir. İçe aktarım haritası kipinde yalnız değişen dosyanın adı değişir ve yeniden inen miktar 161 bayta düşer — yarıdan az. Oran, zincirin derinliğiyle büyür: dört katmanlı bir parça çizgesinde gömülü adlandırma dört dosyayı, harita kipi yine bir dosyayı değiştirir.

İkinci senaryo yöntemin sınırını gösterir. Görsel bir varlığın değişmesi her iki kipte de biçem dosyasının adını değiştirir, çünkü biçem dosyasındaki url() bağı içe aktarım haritasıyla çözülemez; adres oraya yazılmak zorundadır. Zincir kırma yalnız modül belirteçleri için geçerlidir. Bu, dördüncü dersteki gömme kararına yeni bir ölçüt ekler: sık değişen bir varlığı biçem dosyasına gömmek, biçem dosyasını da her yayında değiştirir.

Ada Göre Önbellek Politikası

Karmalı adlandırma tek başına bir kazanç sağlamaz; kazancı önbellek başlıkları üretir. Tarayıcı ve Web Platformu kursundaki önbellek stratejileri dersinde tanımlanan başlıklar burada dosya sınıflarına göre uygulanır.

Karmalı dosyalar en uzun ömürle ve değişmezlik bildirimiyle verilir: bir yıllık tazelik süresi ve değişmez işareti. Bu bildirimin doğru olmasının güvencesi adın kendisidir — içerik değişirse dosya artık o adla istenmez. Değişmez işareti, tazelik süresi dolmadan yapılan yeniden doğrulama isteklerini de kaldırır.

Belge hiçbir zaman uzun ömürle verilmez. Karmalı adları belge taşır; belge önbelleklenirse kullanıcı eski adları istemeye devam eder ve yeni yayını hiç görmez. Belge için doğru politika, her kullanımda yeniden doğrulanacak biçimde işaretlemek ve bir doğrulayıcı vermektir; içerik değişmemişse yanıt gövdesiz döner.

İçe aktarım haritası belgenin içinde yer alır ve onunla aynı politikaya tabidir. Ayrı bir dosyada tutulursa belge gibi işaretlenmelidir; uzun ömürle verilirse zinciri kırmak için yapılan her şey boşa gider.

Doğrudan kopyalanan dosyalar adlarını koruduğu için karmalı politikayı alamaz. Bunlara orta uzunlukta bir tazelik süresi ve doğrulayıcı verilir.

Sorgu dizgisiyle sürümleme — adres sonuna bir sürüm parametresi eklemek — aynı amaca hizmet eder gibi görünür ama iki zayıflığı vardır: bazı ara önbellekler sorgu dizgisi taşıyan adresleri önbelleklemez ve aynı dosyanın iki farklı parametreyle iki kopyası oluşabilir. Ad üzerinden sürümleme bu iki sorunu da doğurmaz.

Eski Adların Ömrü

Karmalı adlandırmanın getirdiği bir yükümlülük vardır: eski adlar bir süre daha yaşamalıdır. Bir kullanıcı sayfayı açıkken yeni bir yayın yapılırsa, açık sayfanın belgesi eski adları taşımaya devam eder. O sayfadan tetiklenen bir dinamik parça isteği, eski ada gider. Yeni yayın eski dosyaları sildiyse istek bulunamaz ve kod bölme dersinde tanımlanan çalışma zamanı hatası ortaya çıkar.

Bu yüzden yayın, dosyaları yerinde değiştirmek yerine eklemeli yapılır: yeni adlar yazılır, eski adlar silinmez. Silme, açık oturumların ömrünü aşan bir gecikmeyle ayrı bir temizlik işi olarak yapılır. Aynı kural geri alma için de gerekir ve dağıtım konusundaki aşamalı yayın dersinde bir yayın politikasına dönüşecektir.

Özet

  • İçerik karması dosya adını içeriğe bağlar: aynı baytlar aynı adı verir, değişen baytlar yeni bir ad üretir; karma son içerik üzerinden hesaplanmalıdır.
  • Adlandırma belirlenimcidir; aynı kaynaktan iki derleme özdeş ad listesi üretir ve bu liste yayının kimliği olarak kullanılabilir.
  • Karmalı ad gövdeye gömüldüğünde değişim yapraktan köke zincirlenir: örnekte tek satırlık bir değişiklik üç dosyanın adını değiştirir ve 343 bayt yeniden indirtir.
  • İçe aktarım haritası modül belirteçlerini gövdeden çıkarır ve zinciri kırar; aynı değişiklik 161 bayta iner. Biçem dosyasındaki varlık bağları bu yolla çözülemez.
  • Politika dosya sınıfına göre verilir: karmalı dosyalar uzun ömürlü ve değişmez, belge ve içe aktarım haritası her kullanımda yeniden doğrulanan.
  • Eski adlar hemen silinmez; açık sayfaların isteyeceği parçalar bir süre daha yayında kalır.

Sonraki Adım

Buraya kadar üretilen her şey yayın içindir: parçalar birleştirilmiş, ölü kod atılmış, varlıklar işlenmiş, adlar içeriğe bağlanmıştır. Bu işlemlerin hepsi zaman alır ve geliştirme sırasında hiçbirinin karşılığı yoktur. Bir satırı değiştirip sonucu görmek isteyen biri için tüm çizgenin yeniden paketlenmesi, karmaların yeniden hesaplanması ve sayfanın baştan yüklenmesi gereksiz bir bekleme ve kaybolmuş bir ekran durumu demektir. Sonraki ders derlemenin ikinci kipini kurar: kaynağı olduğu gibi sunan, yalnız değişen modülü yeniden değerlendiren ve değişimi çizgede yukarı doğru yayan bir geliştirme sunucusu.

İ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