İçeriğe geç
academia.sh

Ders 18 / 24

Önbellek Stratejileri

Tarayıcıdaki iki önbellek katmanının iş bölümü, başlıklarla verilen tazelik kararı ve koşullu istek, önbellek anahtarının üretimi, dört stratejinin aynı durumlarda verdiği sonuç ve sürümleme ile temizlik.

İçindekiler

Önceki ders araya girme yetkisini kurdu, onunla ne yapılacağını açık bıraktı. İsteği karşılamanın tek bir doğru biçimi yoktur: istasyonun logosu ile ölçüm listesi aynı kuralla sunulamaz. Biri değişmez ve ağdan sorulması gereksizdir; diğeri dakikada bir değişir ve eski kopyası yanıltıcıdır.

Bu ders, kaynak başına verilen bu kararı iki soruya indirir: bir kopya nereden bulunur ve ne zaman taze sayılır.

İki Ayrı Önbellek Katmanı

Tarayıcıda iki ayrı önbellek vardır ve karıştırılmaları yanlış tanılara yol açar.

HTTP önbelleği, tarayıcının kendi yönettiği ve program tarafından görülmeyen katmandır. Neyin ne kadar saklanacağını sunucunun gönderdiği başlıklar belirler; sayfa kodunun sözü geçmez. Bir kaydı silmenin ya da içeriğini okumanın program yolu yoktur.

Program denetimli önbellek, servis çalışanının açıkça yazdığı ve sildiği ad–kayıt deposudur. Ne saklanacağına, ne kadar tutulacağına ve ne zaman sunulacağına yazan kod karar verir; başlıklar bu katmanda kendiliğinden hiçbir şey yapmaz.

İkisi bir zincirde durur. Servis çalışanı ağa çıktığında istek HTTP önbelleğinden geçer. Bu, ikili önbellekleme üretir: aynı kaynak iki katmanda da saklanabilir ve program katmanına yazılan kopya, HTTP katmanının eski kopyası olabilir. Kaçınmanın yolu, kritik kaynakları önbelleğe alırken isteği HTTP katmanını atlayacak biçimde kurmaktır.

Tazelik Kararı Nerede Verilir

HTTP katmanının kararı üç başlığa dayanır: saklama izni, ömür ve doğrulayıcı. Bunlar bir yanıtın başlıklarında okunabilir.

// sunucu.mjs — uc kaynagi uc ayri tazelik politikasiyla sunan yerel sunucu
import { createServer } from "node:http";

const KAYNAKLAR = {
  "/logo.svg": {
    tip: "image/svg+xml",
    etiket: '"logo-3f2a"',
    denetim: "public, max-age=31536000, immutable",
    govde: '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8"></svg>',
  },
  "/olcumler.json": {
    tip: "application/json",
    etiket: '"olcum-7"',
    denetim: "no-cache",
    govde: '{"sicaklik":-4.2,"nem":72}',
  },
  "/oturum": {
    tip: "text/plain; charset=utf-8",
    etiket: null,
    denetim: "no-store",
    govde: "kisiye ozel icerik",
  },
};

createServer((istek, yanit) => {
  yanit.sendDate = false; // ciktiyi belirlenimci kilmak icin: Date basligi yazilmaz
  const kaynak = KAYNAKLAR[istek.url];
  if (!kaynak) { yanit.writeHead(404).end(); return; }

  yanit.setHeader("Cache-Control", kaynak.denetim);
  if (kaynak.etiket) yanit.setHeader("ETag", kaynak.etiket);

  // Kosullu istek: istemcinin elindeki etiket sunucudakiyle ayniysa govde gonderilmez.
  if (kaynak.etiket && istek.headers["if-none-match"] === kaynak.etiket) {
    yanit.writeHead(304).end();
    return;
  }
  yanit.setHeader("Content-Type", kaynak.tip);
  yanit.setHeader("Content-Length", Buffer.byteLength(kaynak.govde));
  yanit.writeHead(200).end(kaynak.govde);
}).listen(8137, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8137"));

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası olarak seçilen 8137 keyfîdir ve boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

#!/usr/bin/env bash
# sunucu.mjs'yi baslatir, uc kaynagin yanit basliklarini okur, sonra durdurur.
node sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- /logo.svg (surumlenmis varlik) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/logo.svg

echo "--- /olcumler.json (her kullanimda dogrulanir) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/olcumler.json

echo '--- /olcumler.json + If-None-Match: "olcum-7" ---'
curl -sS -D - -o /dev/null -H 'If-None-Match: "olcum-7"' http://127.0.0.1:8137/olcumler.json

echo "--- /oturum (hic saklanmaz) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8137/oturum

kill "$sunucu"
--- /logo.svg (surumlenmis varlik) ---
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "logo-3f2a"
Content-Type: image/svg+xml
Content-Length: 67
Connection: keep-alive
Keep-Alive: timeout=5

--- /olcumler.json (her kullanimda dogrulanir) ---
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "olcum-7"
Content-Type: application/json
Content-Length: 26
Connection: keep-alive
Keep-Alive: timeout=5

--- /olcumler.json + If-None-Match: "olcum-7" ---
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "olcum-7"
Connection: keep-alive
Keep-Alive: timeout=5

--- /oturum (hic saklanmaz) ---
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: text/plain; charset=utf-8
Content-Length: 18
Connection: keep-alive
Keep-Alive: timeout=5

Bağlantıya ilişkin iki başlık taşıyıcı katmandan gelir ve tazelikle ilgisi yoktur; Date başlığı çıktının her çalıştırmada aynı olması için bilerek kapatılmıştır, gerçek bir sunucuda bulunur.

Üç politika üç ayrı kaynak sınıfını temsil eder. Adı sürümlenmiş bir varlık uzun ömür alır ve içeriği hiç değişmeyeceği bildirilir; böyle bir kaynak için doğrulama isteği bile gereksizdir. Sık değişen veri saklanabilir ama her kullanımda doğrulanmalıdır — başlığın adı yanıltıcıdır, saklamayı değil doğrulamasız kullanmayı yasaklar. Kişiye özel içerik hiçbir katmanda saklanmaz.

Üçüncü isteğin yanıtı doğrulamanın kazancını gösterir: istemci elindeki doğrulayıcıyı gönderir, sunucu içerik değişmediğini gövdesiz bir yanıtla bildirir. Ağ turu yapılmıştır ama gövde aktarılmamıştır. Bu, gecikmeyi ortadan kaldırmaz, bant genişliğini kurtarır; kopan bir bağlantıda doğrulama da yapılamayacağı için tek başına çevrimdışı davranış sağlamaz.

Önbellek Anahtarı

Program denetimli önbellekte bir kaydın anahtarı bir istektir, ve eşleme öntanımlı olarak adresin tamamı üzerinden yapılır. Bunun üç sonucu vardır.

Birincisi, sorgu dizgisi anahtarın parçasıdır. /olcumler?tur=sicaklik ile /olcumler?tur=nem iki ayrı kayıttır. Aynı kaynağa yönlendirdiği bilinen sorgu parametreleri varsa eşlemede sorgunun göz ardı edilmesi istenebilir; bu, izleme parametreleri yüzünden aynı sayfanın onlarca kez saklanmasını önler.

İkincisi, aynı adresin farklı yöntemlerle yapılan istekleri ayrılabilir; önbellek öntanımlı olarak yalnızca güvenli yöntemleri saklamaya elverişlidir. Gövdeli bir isteğin yanıtını adres anahtarıyla saklamak, farklı gövdelerin aynı kayda düşmesi demektir.

Üçüncüsü, sunucu bir yanıtın hangi istek başlığına göre değiştiğini bildirebilir. Dil tercihine ya da kabul edilen içerik biçimine göre farklı yanıt veren bir uç için bu bildirim yapılmazsa, bir kullanıcının aldığı yanıt diğerine sunulur. Eşleme sırasında bu bildirimin dikkate alınıp alınmayacağı da seçilebilir.

Anahtar üretimini kendi eline alan bir çalışan, adresi yazmadan önce normalleştirir: izleme parametrelerini atar, kalan parametreleri sıraya koyar, adres parçasını siler. Aynı içeriğin tek anahtara düşmesi, hem yeri hem de isabet oranını iyileştirir.

Dört Strateji ve Verdikleri Söz

İsteği karşılamanın dört temel biçimi vardır. Aralarındaki fark, aynı üç durumda ne sundukları ve ağı ne zaman kullandıklarıyla görünür hâle gelir.

// strateji.mjs — dort onbellek stratejisinin ayni uc durumda verdigi sonuc
const ADRES = "/istasyon/olcumler.json";

function kur({ onbelleklenmis, cevrimici }) {
  const durum = { agCagrisi: 0, onbellek: new Map() };
  if (onbelleklenmis) durum.onbellek.set(ADRES, "v1");
  durum.ag = async () => {
    durum.agCagrisi += 1;
    if (!cevrimici) throw new Error("ag yok");
    return "v2";
  };
  return durum;
}

async function onbellekOnce(d) {
  const kayit = d.onbellek.get(ADRES);
  if (kayit) return kayit;
  const yanit = await d.ag();
  d.onbellek.set(ADRES, yanit);
  return yanit;
}

async function agOnce(d) {
  try {
    const yanit = await d.ag();
    d.onbellek.set(ADRES, yanit);
    return yanit;
  } catch {
    return d.onbellek.get(ADRES) ?? "(yedek sayfa)";
  }
}

async function yenidenDogrulayarakSun(d) {
  const kayit = d.onbellek.get(ADRES);
  const tazeleme = d.ag().then((yanit) => d.onbellek.set(ADRES, yanit)).catch(() => {});
  if (kayit) return kayit;          // bekletmeden eski kopya verilir
  await tazeleme;
  return d.onbellek.get(ADRES) ?? "(yedek sayfa)";
}

async function yalnizAg(d) {
  try { return await d.ag(); } catch { return "(yedek sayfa)"; }
}

const stratejiler = [
  ["onbellek once", onbellekOnce],
  ["ag once", agOnce],
  ["yeniden dogrulayarak sun", yenidenDogrulayarakSun],
  ["yalniz ag", yalnizAg],
];

const durumlar = [
  ["onbellek bos, cevrimici", { onbelleklenmis: false, cevrimici: true }],
  ["onbellekte v1, cevrimici", { onbelleklenmis: true, cevrimici: true }],
  ["onbellekte v1, cevrimdisi", { onbelleklenmis: true, cevrimici: false }],
];

console.log("strateji".padEnd(26) + "durum".padEnd(28) + "sunulan".padEnd(16) + "ag  onbellekte kalan");
for (const [stratejiAdi, calistir] of stratejiler) {
  for (const [durumAdi, secenek] of durumlar) {
    const d = kur(secenek);
    const sunulan = await calistir(d);
    await new Promise((c) => setImmediate(c)); // arka plandaki tazeleme bitsin
    console.log(
      stratejiAdi.padEnd(26) + durumAdi.padEnd(28) +
      String(sunulan).padEnd(16) + String(d.agCagrisi).padEnd(4) +
      (d.onbellek.get(ADRES) ?? "(bos)"));
  }
}
strateji                  durum                       sunulan         ag  onbellekte kalan
onbellek once             onbellek bos, cevrimici     v2              1   v2
onbellek once             onbellekte v1, cevrimici    v1              0   v1
onbellek once             onbellekte v1, cevrimdisi   v1              0   v1
ag once                   onbellek bos, cevrimici     v2              1   v2
ag once                   onbellekte v1, cevrimici    v2              1   v2
ag once                   onbellekte v1, cevrimdisi   v1              1   v1
yeniden dogrulayarak sun  onbellek bos, cevrimici     v2              1   v2
yeniden dogrulayarak sun  onbellekte v1, cevrimici    v1              1   v2
yeniden dogrulayarak sun  onbellekte v1, cevrimdisi   v1              1   v1
yalniz ag                 onbellek bos, cevrimici     v2              1   (bos)
yalniz ag                 onbellekte v1, cevrimici    v2              1   v1
yalniz ag                 onbellekte v1, cevrimdisi   (yedek sayfa)   1   v1

İkinci satır önbellek önce stratejisinin belirleyici özelliğidir: kopya varken ağ hiç kullanılmaz. Yanıt en hızlı biçimde gelir ve bağlantıdan bağımsızdır; bedeli, kaynak sunucuda değişse bile eski kopyanın süresiz sunulmasıdır. Bu yüzden yalnızca adı sürümlenmiş, içeriği asla değişmeyen varlıklar için doğrudur.

Beşinci ve altıncı satır ağ önce stratejisini özetler: çevrimiçiyken her zaman güncel, çevrimdışıyken elde ne varsa. Ağın yavaş olduğu durumda kullanıcı ağın gecikmesi kadar bekler; gerçek dağıtımlarda bu bekleme bir zaman aşımıyla sınırlanır ve süre dolunca kopyaya düşülür.

Sekizinci satır yeniden doğrulayarak sunma stratejisinin ödünleşimini tek satırda gösterir: sunulan v1, önbellekte kalan v2. Kullanıcı beklemeden yanıt alır ama bir sürüm geriden gelir; güncel içerik bir sonraki ziyarette görünür. Bu, tazeliğin bir tur gecikmesine razı olunabilen içerik için doğru seçimdir ve arayüzün bunu üstü kapalı bırakmaması gerekir.

Son üç satır yalnız ağ stratejisinin önbelleği hiç yazmadığını gösterir: üçüncü durumda önbellekte v1 durduğu hâlde yedek sayfa sunulmuştur. Ölçüm gönderimi gibi biriktirilemeyen istekler bu stratejiye aittir; çevrimdışıyken yapılacak şey eski veriyi göstermek değil, isteğin gerçekleşmediğini bildirmektir.

Sürümleme, Temizlik ve Kota

Önbellekler adlandırılır ve ad sürüm taşır. Yeni bir çalışan sürümü kurulurken kendi adına yazar; etkinleşirken kendi adı dışındaki bütün önbellekleri siler. Bu iki adım birlikte, eski ve yeni varlıkların karışmasını önler: bir sürümün varlıkları ya tümüyle vardır ya tümüyle yoktur.

Kurulum sırasında toplanan kaynak listesi kısa tutulur. Uygulamanın açılması için gereken en küçük küme — belge kabuğu, temel biçem, temel betik, çevrimdışı yedek sayfa — kuruluma girer; geri kalan varlıklar kullanıldıkça yazılır. Kurulumun bir bütün olarak başarısız sayılması, uzun bir listenin tek bir kalemi yüzünden bütün dağıtımı engellemesi anlamına gelir.

Depolanan verinin sınırsız olmadığı da hesaba katılır. Tarayıcı köken başına bir üst sınır uygular ve yer daraldığında kökenin verisini bütün olarak silebilir. Buradan iki kural çıkar: önbellek kalıcı bir veritabanı gibi kullanılmaz, ve önbellekten okunan bir kaydın bulunamama olasılığı her zaman ele alınır.

Özet

  • Tarayıcıda iki önbellek katmanı vardır: başlıklarla yönetilen HTTP önbelleği ve kodla yönetilen program denetimli önbellek; ikisi zincirde arka arkaya durur.
  • Tazelik başlıkları üç kararı bildirir: saklanabilir mi, ne kadar taze sayılır, hangi doğrulayıcıyla sınanır. Doğrulama gövdesiz bir yanıtla sonuçlanır ve bant genişliğini kurtarır, gecikmeyi kaldırmaz.
  • Önbellek anahtarı öntanımlı olarak adresin tamamıdır; sorgu dizgisi anahtarın parçasıdır ve normalleştirilmediğinde aynı içerik birden çok kayda düşer.
  • Önbellek önce en hızlı ve en eskiye açık, ağ önce en taze ve en yavaş, yeniden doğrulayarak sunma hızlı ama bir sürüm geriden, yalnız ağ ise çevrimdışı davranışı olmayan stratejidir.
  • Önbellek adları sürüm taşır; kurulumda yazılır, etkinleşmede eski adlar silinir. Kurulum listesi kısa tutulur ve depolama sınırlıdır.

Sonraki Adım

Bu ders sayfayı ağdan bağımsızlaştırdı: kaynaklar yerelde, kararlar tanımlı. Ortaya çıkan şey artık bir sayfadan fazlasıdır ve kullanıcıya da öyle görünmesi gerekir. Kuzey Yamaç sayfasını sahada kullanan kişi onu her seferinde adres yazarak açmak istemez; kendi penceresinde, kendi simgesiyle, tarayıcı arayüzü olmadan açılmasını bekler. Bunun için sayfanın kendini tanıtan bir bildirim dosyası, kurulabilirliğin ölçütleri ve arka plandan gelen bildirimlere ilişkin bir izin modeli gerekir. Sonraki ders bu üç parçayı ele alır.

İ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