İçeriğe geç
academia.sh

Ders 04 / 27

Ağ Başarımı

Tazelik ile doğrulamanın ayrılması, önbellek başlıklarının yerel bir sunucu üzerinde ölçülmesi, içerik karmalı adların değişmezlik sözü ve bağlantı ipuçlarının neyi kazandırdığı.

İçindekiler

Önceki üç ders belgenin ve varlıkların kendisini ele aldı: hangi dosya, ne kadar büyük, ne zaman istenir. Bir katman daha var ve orada tek bir bayt bile azaltmadan kazanç elde edilebilir. Aynı kullanıcı istasyon listesini ikinci kez açtığında aynı dosyaları yeniden indirmek zorunda mıdır?

Bu sorunun yanıtı istekte değil, yanıtın taşıdığı başlıklardadır. Tarayıcı ve Web Platformu kursundaki Önbellek Stratejileri dersi program denetimli önbelleği ele almıştı; buradaki konu, program yazılmadan sunucunun başlıklarla neyi belirlediğidir.

Tazelik ve Doğrulama İki Ayrı Karardır

HTTP önbelleği iki ayrı soruyu yanıtlar ve bunları karıştırmak yanlış yapılandırmanın en yaygın nedenidir.

Tazelik ömrü şu soruyu yanıtlar: bu yanıt ne kadar süre sorgusuz kullanılabilir? Cache-Control başlığındaki max-age yönergesi bir süre verir; süre dolana kadar tarayıcı hiç istek yapmaz. En ucuz istek, yapılmayan istektir.

Doğrulayıcı şu soruyu yanıtlar: elimdeki kopya hâlâ geçerli mi? ETag başlığı içeriğe bağlı bir imza taşır. Tazelik süresi dolduğunda tarayıcı bu imzayı If-None-Match başlığıyla geri gönderir; içerik değişmemişse sunucu gövdesiz bir yanıt döndürür.

İki karar birbirini tamamlar. Tazelik isteği tümüyle önler, doğrulama isteği ucuzlatır. no-cache yönergesi bir yanlış anlamaya yol açar: saklamayı yasaklamaz, yalnızca her kullanımdan önce doğrulamayı zorunlu kılar. Saklamayı yasaklayan yönerge no-store yönergesidir.

Başlıkları Ölçmek

Aşağıdaki sunucu üç kaynağı üç ayrı politikayla sunar ve koşullu isteği gerçekten karşılar.

// onbellek-sunucusu.mjs — tazelik ve dogrulama basliklarini uc ayri kaynakta gosterir
import { createServer } from "node:http";
import { createHash } from "node:crypto";

const govdeler = {
  "/varlik/istasyon.9f2c1a.css": {
    tur: "text/css",
    icerik: ".istasyon-listesi{display:grid;gap:12px}",
    // Icerik karmali ad: adres degisirse icerik degismistir.
    onbellek: "public, max-age=31536000, immutable",
  },
  "/api/istasyonlar": {
    tur: "application/json",
    icerik: JSON.stringify({ istasyonlar: ["kuzey-yamac-1", "kuzey-yamac-2"] }),
    // Her kullanimda dogrulanir; govde yalnizca degistiyse gonderilir.
    onbellek: "no-cache",
  },
  "/api/duyuru": {
    tur: "application/json",
    icerik: JSON.stringify({ duyuru: "Bakim penceresi: 03:00-04:00" }),
    // Bir dakika taze; sonraki bes dakika eskisi sunulup arkada tazelenir.
    onbellek: "public, max-age=60, stale-while-revalidate=300",
  },
};

const etiket = (icerik) =>
  `"${createHash("sha256").update(icerik).digest("hex").slice(0, 16)}"`;

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const kaynak = govdeler[istek.url];
  if (!kaynak) {
    yanit.writeHead(404, { "Content-Type": "text/plain" });
    return yanit.end("yok\n");
  }

  const dogrulayici = etiket(kaynak.icerik);
  const basliklar = {
    "Cache-Control": kaynak.onbellek,
    ETag: dogrulayici,
    Vary: "Accept-Encoding",
  };

  // Kosullu istek: istemcinin elindeki dogrulayici hala gecerliyse govde gonderilmez.
  if (istek.headers["if-none-match"] === dogrulayici) {
    yanit.writeHead(304, basliklar);
    return yanit.end();
  }

  yanit.writeHead(200, {
    ...basliklar,
    "Content-Type": kaynak.tur,
    "Content-Length": Buffer.byteLength(kaynak.icerik),
  });
  yanit.end(kaynak.icerik);
}).listen(8241, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8241"));
#!/usr/bin/env bash
# onbellek-sunucusu.mjs'yi baslatir, tazelik ve dogrulama davranisini olcer, durdurur.
node onbellek-sunucusu.mjs > /dev/null &
sunucu=$!
sleep 1
temel=http://127.0.0.1:8241

for yol in /varlik/istasyon.9f2c1a.css /api/istasyonlar /api/duyuru; do
  printf '%s\n' "$yol"
  curl -sS -o /dev/null -D - "$temel$yol" \
    | grep -iE '^(HTTP/|cache-control|etag|content-length)' \
    | tr -d '\r' | sed 's/^/  /'
done

printf '\n--- kosullu istek ---\n'
etiket=$(curl -sS -o /dev/null -D - "$temel/api/istasyonlar" \
  | awk 'tolower($1) == "etag:" { print $2 }' | tr -d '\r')
printf 'istemcideki dogrulayici: %s\n' "$etiket"

curl -sS -o /dev/null -H "If-None-Match: $etiket" "$temel/api/istasyonlar" \
  -w 'kosullu istek  -> durum %{http_code}, indirilen govde %{size_download} bayt\n'
curl -sS -o /dev/null "$temel/api/istasyonlar" \
  -w 'kosulsuz istek -> durum %{http_code}, indirilen govde %{size_download} bayt\n'

kill "$sunucu"
/varlik/istasyon.9f2c1a.css
  HTTP/1.1 200 OK
  Cache-Control: public, max-age=31536000, immutable
  ETag: "5102555c20f3670b"
  Content-Length: 40
/api/istasyonlar
  HTTP/1.1 200 OK
  Cache-Control: no-cache
  ETag: "c0c1001d3d358ea1"
  Content-Length: 49
/api/duyuru
  HTTP/1.1 200 OK
  Cache-Control: public, max-age=60, stale-while-revalidate=300
  ETag: "caefa267f7a6d1f0"
  Content-Length: 41

--- kosullu istek ---
istemcideki dogrulayici: "c0c1001d3d358ea1"
kosullu istek  -> durum 304, indirilen govde 0 bayt
kosulsuz istek -> durum 200, indirilen govde 49 bayt

Doğrulayıcı değerleri içeriğin özetinden üretildiği için bu çıktıda sabittir; içerik değiştiğinde değişir. Bağlantı noktası olarak seçilen 8241 keyfîdir ve boş olmalıdır.

Ölçülen fark açıktır: koşullu istek gövdesiz döner. Gidiş-dönüş yine yapılmıştır — koşullu istek gecikmeyi ortadan kaldırmaz, yalnızca aktarımı ortadan kaldırır. Tazelik ömrü ise gidiş-dönüşü de ortadan kaldırır. İkisi arasındaki bu fark, hangi kaynağa hangi politikanın yazılacağını belirler.

Değişmez Varlık, Değişken Belge

immutable yönergesi bir söz verir: bu adresteki içerik hiç değişmeyecek. Söz ancak adres içerikten türetiliyorsa tutulabilir. Oluşturma Stratejileri ve Altyapı kursundaki önbellek kırma yaklaşımı tam olarak bunu sağlar: dosya adına içerik karması yazıldığında, içerik değiştiğinde adres de değişir ve eski adresin sonsuza kadar taze kalması bir sorun yaratmaz.

Belge bu sözü veremez, çünkü adresi sabittir ve içeriği her yayında değişir. Belgeye uygulanan doğru politika, uzun tazelik değil doğrulamadır.

Aradaki üçüncü durum için stale-while-revalidate yönergesi vardır: tazelik süresi dolduktan sonra belirtilen süre boyunca eski kopya hemen sunulur ve tazeleme arka planda yapılır. Kullanıcı beklemez, veri bir tur gecikmeyle tazelenir. Duyuru şeridi gibi gecikmeye toleranslı içerik için uygundur; hesap bakiyesi gibi içerik için değildir.

Bu üç politikanın tekrar ziyarette ne kadar bayt aktardığı hesaplanabilir.

// tekrar-ziyaret.mjs — baslik politikasinin tekrar ziyarette aktarilan bayta etkisi

const BASLIK_YUKU = 320; // bayt: istek + yanit basliklarinin yaklasik payi

// Istasyon listesi sayfasinin kaynaklari ve secilen politika.
const kaynaklar = [
  { ad: "belge",         bayt:  18_400, politika: "no-cache" },
  { ad: "bicem",         bayt:  62_000, politika: "immutable" },
  { ad: "giris betigi",  bayt: 148_112, politika: "immutable" },
  { ad: "ortak betik",   bayt: 101_760, politika: "immutable" },
  { ad: "yazi tipi",     bayt:  41_600, politika: "immutable" },
  { ad: "kart gorseli",  bayt:  86_400, politika: "immutable" },
  { ad: "istasyon veri", bayt:   9_240, politika: "max-age=60" },
];

const tamBoyut = (k) => BASLIK_YUKU + k.bayt;
const ilkZiyaret = kaynaklar.reduce((t, k) => t + tamBoyut(k), 0);

// Tekrar ziyarette bir kaynak icin aktarilan bayt.
function tekrarBayti(kaynak, gecenSaniye, degisenler) {
  const degisti = degisenler.has(kaynak.ad);
  switch (kaynak.politika) {
    case "immutable":
      // Icerik karmali ad: degistiyse adres degismistir, yeni kaynak bastan iner.
      return degisti ? tamBoyut(kaynak) : 0;
    case "no-cache":
      // Her kullanimda dogrulanir: degismediyse yalniz basliklar gelir.
      return degisti ? tamBoyut(kaynak) : BASLIK_YUKU;
    case "max-age=60":
      if (gecenSaniye <= 60) return 0;               // hala taze: istek yok
      return degisti ? tamBoyut(kaynak) : BASLIK_YUKU;
    default:
      return tamBoyut(kaynak);
  }
}

const senaryolar = [
  { ad: "30 saniye sonra, surum ayni", gecen: 30, degisenler: new Set() },
  { ad: "300 saniye sonra, surum ayni", gecen: 300, degisenler: new Set() },
  {
    ad: "yeni surum yayinlandi",
    gecen: 300,
    degisenler: new Set(["belge", "bicem", "giris betigi"]),
  },
];

console.log(`ilk ziyaret: ${(ilkZiyaret / 1024).toFixed(1)} KB`);
for (const s of senaryolar) {
  console.log(`\n--- ${s.ad} ---`);
  console.log("kaynak".padEnd(14) + "tekrar".padStart(10) + "  politika");
  let toplam = 0;
  for (const k of kaynaklar) {
    const b = tekrarBayti(k, s.gecen, s.degisenler);
    toplam += b;
    console.log(k.ad.padEnd(14) + `${(b / 1024).toFixed(2)} KB`.padStart(10) + "  " + k.politika);
  }
  console.log("-".repeat(36));
  console.log("TOPLAM".padEnd(14) + `${(toplam / 1024).toFixed(2)} KB`.padStart(10));
  console.log(`ilk ziyarete oran: ${(toplam / ilkZiyaret).toFixed(4)}`);
}

console.log("\nhicbir tazelik basligi yazilmasaydi her tekrar ziyaret oran 1.0000 olurdu.");
ilk ziyaret: 458.7 KB

--- 30 saniye sonra, surum ayni ---
kaynak            tekrar  politika
belge            0.31 KB  no-cache
bicem            0.00 KB  immutable
giris betigi     0.00 KB  immutable
ortak betik      0.00 KB  immutable
yazi tipi        0.00 KB  immutable
kart gorseli     0.00 KB  immutable
istasyon veri    0.00 KB  max-age=60
------------------------------------
TOPLAM           0.31 KB
ilk ziyarete oran: 0.0007

--- 300 saniye sonra, surum ayni ---
kaynak            tekrar  politika
belge            0.31 KB  no-cache
bicem            0.00 KB  immutable
giris betigi     0.00 KB  immutable
ortak betik      0.00 KB  immutable
yazi tipi        0.00 KB  immutable
kart gorseli     0.00 KB  immutable
istasyon veri    0.31 KB  max-age=60
------------------------------------
TOPLAM           0.63 KB
ilk ziyarete oran: 0.0014

--- yeni surum yayinlandi ---
kaynak            tekrar  politika
belge           18.28 KB  no-cache
bicem           60.86 KB  immutable
giris betigi   144.95 KB  immutable
ortak betik      0.00 KB  immutable
yazi tipi        0.00 KB  immutable
kart gorseli     0.00 KB  immutable
istasyon veri    0.31 KB  max-age=60
------------------------------------
TOPLAM         224.41 KB
ilk ziyarete oran: 0.4892

hicbir tazelik basligi yazilmasaydi her tekrar ziyaret oran 1.0000 olurdu.

Kaynak boyutları hesabın girdisidir, ölçülmüş bir uygulamanın çıktısı değildir; başlık payı da bir varsayımdır. Hesaplanan şey politikanın sonucudur.

İlk iki senaryoda tekrar ziyaret neredeyse bedavadır. Üçüncü senaryo asıl dersi verir: yeni sürüm yayınlandığında yalnızca değişen parçalar yeniden iner. Ortak betik değişmediği için adresi de değişmemiştir ve hiç istenmez. Paketleme kararlarının başarım sonucu buradadır — sık değişen kodla seyrek değişen kod aynı parçada toplanırsa, her yayın ikisini birden yeniden indirtir.

Vary ve Önbellek Anahtarı

Bir yanıt istek başlıklarına göre değişiyorsa bu, Vary başlığıyla bildirilmelidir. Bildirilmezse ara önbellekler tek bir kopyayı herkese sunar: sıkıştırılmış bir yanıt sıkıştırma desteklemeyen bir istemciye, bir dile göre üretilmiş sayfa başka dildeki kullanıcıya gidebilir.

Kural, doğruluk kuralıdır ve başarımı da etkiler: Vary listesine gereksiz başlık eklemek, önbellek anahtarını gereğinden fazla böler ve isabet oranını düşürür. Kullanıcıya özgü bir başlık listeye girerse önbellek pratikte devre dışı kalır.

Bağlantı İpuçları

Belgede yazılan bağlantı ipuçları, tarayıcıya bir işi erken yapmasını bildirir. Her biri farklı bir aşamayı öne çeker.

dns-prefetch yalnızca ad çözümlemesini yapar. En ucuz ipucudur ve en az kazandırır.

preconnect ad çözümlemesini, taşıma bağlantısını ve güvenli el sıkışmayı önden yapar. Kritik yol modelinde “bağlantı kurulumu” olarak sayılan turları, kaynak istenmeden önce harcar. Bedeli vardır: kurulan ve kullanılmayan bağlantı hem istemcide hem sunucuda kaynak tutar, bu yüzden yalnızca kesin kullanılacak kökenler için yazılır.

preload belirli bir kaynağı erken ister. Kritik Oluşturma Yolu dersinde anlatıldığı gibi bu bir düzeltmedir: geç keşfedilen kritik kaynağı öne çeker. as özniteliği zorunludur; yanlış yazıldığında kaynak iki kez indirilir.

prefetch bir sonraki gezinmede gerekecek kaynağı boşta indirir. Öncelik en düşüktür ve kullanıcı o sayfaya gitmezse harcanmış bant genişliğidir.

Sıra bir öncelik sırasıdır: önce zinciri düzleştirmek, sonra preconnect, en son preload. Ölçüm göstermeden yazılan ipucu, çoğu zaman kritik kaynağın bant genişliğini alarak zarar verir.

Özet

  • Tazelik ömrü isteği tümüyle önler, doğrulayıcı isteği gövdesiz hâle getirir; ikisi ayrı sorunları çözer ve no-cache saklamayı değil doğrulamasız kullanımı yasaklar.
  • İçerik karmalı adres, immutable sözünün tutulabilmesinin koşuludur; sabit adresli belge bu sözü veremez ve doğrulamayla yönetilir.
  • Yeni bir yayında yalnızca adresi değişen parçalar yeniden iner; paketleme kararı bu yüzden bir önbellek kararıdır.
  • Vary başlığı önce doğruluk, sonra başarım konusudur: eksikliği yanlış yanıt sunar, fazlalığı önbelleği böler.
  • Bağlantı ipuçları farklı aşamaları öne çeker ve hepsinin bir bedeli vardır; ölçüm göstermeden yazılan ipucu kritik kaynağın önünü keser.

Sonraki Adım

Ağ tarafı düzenlendiğinde baytlar zamanında gelir ve tekrar ziyaret neredeyse istek yapmaz. Buna karşın uygulama hâlâ ağır hissettirebilir: liste kaydırılırken takılıyor, süzgeç kutusuna yazılan harf ekranda geç görünüyor, tarih aralığı değiştirildiğinde sayfa bir an donuyor olabilir. Bu sorunların hiçbiri ağda değildir; hepsi indirilmiş kodun tek bir sırada çalışmasından doğar. Sonraki ders bu sırayı ele alır: uzun görevlerin nasıl ölçüldüğünü, bölüntülemenin ne kazandırdığını ve yerleşim sarsıntısının satır sayısıyla nasıl ölçeklendiğini hesaplar.

İ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