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-cachesaklamayı değil doğrulamasız kullanımı yasaklar. - İçerik karmalı adres,
immutablesö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.
Varybaş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.