Ders 15 / 19
Statik Barındırma
Derleme çıktısının sunucu süreci olmadan yayınlanması; kenar önbelleğinin isabet, ıska, doğrulama ve yenilenme durumlarının ölçülmesi, ada göre önbellek politikasının istek ve bayt isabet oranına çevrimi, geçersiz kılma ve yol çözümü.
İçindekiler
Derleme çıktısı artık belirlenmiş durumda: parçaları, işlenmiş varlıkları, içerik karmalı adları ve ortamına bağlı sabitleriyle bir dizin. Bu dizin, üretildiği makinenin diskinde durduğu sürece kimsenin işine yaramaz.
Bu ders çıktının kullanıcıya ulaşmasının en yalın yolunu kurar: dizini olduğu gibi kopyalamak ve kullanıcılara yakın duran düğümlerden sunmak. Sorusu şudur — hiçbir sunucu süreci çalışmadığında, hız ve tazelik nereden gelir?
Sunucu Süreci Olmayan Yayın
Statik barındırmada yayın, dosyaların bir kopyasından ibarettir. Gelen her istek bir yola bakar, o yola karşılık gelen dosyayı bulur ve baytlarını gönderir. İstek başına çalışan bir program yoktur.
Bunun üç sonucu vardır.
İstek başına maliyet hemen hemen sıfırdır. Sunucuda Oluşturma dersindeki istek başına maliyet kalemi burada yoktur; ölçeklenmenin yolu daha güçlü makine değil, dosyanın daha çok yere kopyalanmasıdır.
Sunulan tarafta gizli değer bulunmaz. Bir önceki dersin sınırı burada kendiliğinden korunur: sunulan şey istemci paketinin baytlarıdır ve o baytlar zaten herkese açıktır.
Yanıt kullanıcıya göre değişemez. Aynı yola gelen iki istek aynı baytları alır. Kişiye özel içerik, yetkiye bağlı içerik ve isteğin kendisine bakarak verilen kararlar bu modelde üretilemez.
Birincisi modelin gücünü, öteki ikisi sınırını verir. Gücün kullanılabilmesi kopyaların kullanıcıya yakın durmasına bağlıdır; Kenar Oluşturma dersindeki mesafe tablosu bunu ölçmüştü. Kopyayı yakında tutan katman içerik dağıtım ağıdır ve statik barındırmada kod çalıştırmaz, yalnız bayt saklar.
Kenar Önbelleğinin Dört Durumu
Kenar düğümünün elindeki kopya, kaynaktaki dosyayla dört ayrı ilişkide olabilir. Aşağıdaki iki sunucu bu dört durumu ayırt edilebilir kılar. Kaynak sunucuda coğrafi uzaklık, yanıttan önce eklenen sabit bir gecikmeyle modellenmiştir.
// kaynak-sunucu.mjs — yayin dizinini sunan kaynak. Cografi uzaklik sabit gecikmedir. import { createServer } from "node:http"; import { createHash } from "node:crypto"; const UZAKLIK = 80; // ms, kaynaga gidip donmenin bedeli const bekle = (ms) => new Promise((c) => setTimeout(c, ms)); const DEGISMEZ = "public, max-age=31536000, immutable"; const BELGE_POLITIKASI = "public, no-cache"; // Yayin dizini: karmali adli varliklar (05. dersin adlandirmasi) ve tek belge. const VARLIKLAR = new Map([ ["/varlik/istasyon-7e1d6934.css", [".olcum { font-variant-numeric: tabular-nums; }\n", "text/css; charset=utf-8"]], ["/varlik/giris-5f45daac.js", ['export const ac = () => import("/varlik/panel-c3924741.js");\n', "text/javascript; charset=utf-8"]], ]); let yayinNo = 1; // yeni yayin bu sayiyi artirir, belgenin icerigi degisir const belge = () => '<!doctype html><html lang="tr"><head>' + '<link rel="stylesheet" href="/varlik/istasyon-7e1d6934.css">' + '</head><body><h1>Kuzey Yamac Olcum Istasyonu</h1>' + "<p>yayin " + yayinNo + "</p></body></html>\n"; const etiketle = (govde) => '"' + createHash("sha256").update(govde).digest("hex").slice(0, 12) + '"'; function coz(yol) { if (VARLIKLAR.has(yol)) { const [govde, tur] = VARLIKLAR.get(yol); return { govde, tur, politika: DEGISMEZ }; } // Karmali ad bulunamadiysa gercek bir 404'tur; yedek belge verilmez. if (yol.startsWith("/varlik/")) return null; // Geri kalan her yol belgeye cozulur: yol cozumu istemcide yapilir. return { govde: belge(), tur: "text/html; charset=utf-8", politika: BELGE_POLITIKASI }; } createServer(async (istek, yanit) => { yanit.sendDate = false; await bekle(UZAKLIK); if (istek.method === "POST" && istek.url === "/yonetim/yayin") { yayinNo += 1; yanit.writeHead(200, { "Content-Type": "text/plain" }).end("yayin " + yayinNo + "\n"); return; } const kayit = coz(istek.url); if (kayit === null) { yanit.writeHead(404, { "Cache-Control": "public, max-age=60" }).end(); return; } const etiket = etiketle(kayit.govde); const ortak = { ETag: etiket, "Cache-Control": kayit.politika }; if (istek.headers["if-none-match"] === etiket) { yanit.writeHead(304, ortak).end(); return; } yanit.writeHead(200, { ...ortak, "Content-Type": kayit.tur, "Content-Length": Buffer.byteLength(kayit.govde), }).end(kayit.govde); }).listen(8180, "127.0.0.1", () => console.log("kaynak: 127.0.0.1:8180"));
// kenar-onbellegi.mjs — kullaniciya yakin duran, yalniz bayt saklayan kenar dugumu. // Kod calistirmaz; Cache-Control ve ETag ne diyorsa onu uygular. import { createServer } from "node:http"; const KAYNAK = "http://127.0.0.1:8180"; const onbellek = new Map(); // yol -> { govde, tur, etiket, politika, tazeSonu } const saniye = () => Date.now() / 1000; // Tazelik suresi yalniz Cache-Control'den okunur; no-cache sifir tazelik demektir. function tazelik(politika) { if (!politika || /no-store|no-cache/.test(politika)) return 0; const e = /max-age=(\d+)/.exec(politika); return e ? Number(e[1]) : 0; } const gonder = (yanit, kayit, durum) => { yanit.writeHead(200, { "Content-Type": kayit.tur, "Content-Length": Buffer.byteLength(kayit.govde), "Cache-Control": kayit.politika, ETag: kayit.etiket, "X-Onbellek": durum, }).end(kayit.govde); }; createServer(async (istek, yanit) => { yanit.sendDate = false; // Gecersiz kilma: kenardaki kopya elle silinir, sonraki istek kaynaga gider. if (istek.method === "POST" && istek.url.startsWith("/yonetim/gecersiz-kil")) { const hedef = new URL(istek.url, "http://kenar").searchParams.get("yol"); const vardi = onbellek.delete(hedef); yanit.writeHead(200, { "Content-Type": "text/plain" }) .end((vardi ? "silindi : " : "yoktu : ") + hedef + "\n"); return; } const kayit = onbellek.get(istek.url); if (kayit && kayit.tazeSonu > saniye()) { gonder(yanit, kayit, "isabet"); return; } const baslik = kayit ? { "If-None-Match": kayit.etiket } : {}; const cevap = await fetch(KAYNAK + istek.url, { headers: baslik }); if (cevap.status === 304) { kayit.politika = cevap.headers.get("cache-control") ?? kayit.politika; kayit.tazeSonu = saniye() + tazelik(kayit.politika); gonder(yanit, kayit, "dogrulandi"); return; } if (cevap.status !== 200) { onbellek.delete(istek.url); yanit.writeHead(cevap.status, { "X-Onbellek": "kaynaktan" }).end(); return; } const yeni = { govde: await cevap.text(), tur: cevap.headers.get("content-type"), etiket: cevap.headers.get("etag"), politika: cevap.headers.get("cache-control"), }; yeni.tazeSonu = saniye() + tazelik(yeni.politika); onbellek.set(istek.url, yeni); gonder(yanit, yeni, kayit ? "yenilendi" : "iska"); }).listen(8181, "127.0.0.1", () => console.log("kenar: 127.0.0.1:8181"));
Aşağıdaki betik bu iki dosyayla aynı dizinde çalıştırılır. Bağlantı noktaları 8180 ve 8181 keyfîdir ve boş olmalıdır; kullanımdaysa üç dosyada da değiştirilir.
#!/usr/bin/env bash # Iki sunucuyu baslatir, kenar onbelleginin dort durumunu olcer, sonra durdurur. node kaynak-sunucu.mjs > /dev/null & kaynak=$! node kenar-onbellegi.mjs > /dev/null & kenar=$! sleep 1 K=http://127.0.0.1:8181 OLC() { printf '%-36s' "$1"; shift; curl -sS -o /dev/null -w 'ilk bayt %{time_starttransfer} s onbellek %header{x-onbellek}\n' "$@"; } OLC "1) degismez varlik, ilk istek" "$K/varlik/istasyon-7e1d6934.css" OLC "2) degismez varlik, ikinci istek" "$K/varlik/istasyon-7e1d6934.css" OLC "3) belge, ilk istek" "$K/" OLC "4) belge, icerik degismeden" "$K/" curl -sS -o /dev/null -X POST http://127.0.0.1:8180/yonetim/yayin OLC "5) belge, yeni yayindan sonra" "$K/" printf '%-36s' "6) kenardaki kopya silinir" curl -sS -X POST "$K/yonetim/gecersiz-kil?yol=/varlik/istasyon-7e1d6934.css" OLC "7) degismez varlik, silmeden sonra" "$K/varlik/istasyon-7e1d6934.css" echo "--- yol cozumu ---" curl -sS -o /dev/null -w 'bilinmeyen sayfa yolu : %{http_code} %{content_type}\n' \ "$K/olcum/kuzey-yamac/2026-03" curl -sS -o /dev/null -w 'bulunmayan karmali ad : %{http_code}\n' \ "$K/varlik/istasyon-00000000.css" echo "--- belgenin son hali ---" curl -sS "$K/" kill "$kaynak" "$kenar"
1) degismez varlik, ilk istek ilk bayt 0.100191 s onbellek iska 2) degismez varlik, ikinci istek ilk bayt 0.000624 s onbellek isabet 3) belge, ilk istek ilk bayt 0.083963 s onbellek iska 4) belge, icerik degismeden ilk bayt 0.084585 s onbellek dogrulandi 5) belge, yeni yayindan sonra ilk bayt 0.085991 s onbellek yenilendi 6) kenardaki kopya silinir silindi : /varlik/istasyon-7e1d6934.css 7) degismez varlik, silmeden sonra ilk bayt 0.084387 s onbellek iska --- yol cozumu --- bilinmeyen sayfa yolu : 200 text/html; charset=utf-8 bulunmayan karmali ad : 404 --- belgenin son hali --- <!doctype html><html lang="tr"><head><link rel="stylesheet" href="/varlik/istasyon-7e1d6934.css"></head><body><h1>Kuzey Yamac Olcum Istasyonu</h1><p>yayin 2</p></body></html>
Süre alanları makineye ve yüke bağlıdır; yorumu oranlar taşır. İlk iki satır arasındaki fark iki mertebedir — ıskada kaynağa gidilmiş, isabette gidilmemiştir. Geri kalan bütün satırlar ıskayla aynı mertebededir, çünkü hepsinde kaynağa bir tur yapılmıştır.
Bu, dersin ilk ölçülen sonucudur: doğrulama da bir turdur. Üçüncü ve dördüncü satırlar arasındaki tek fark, dördüncüde gövdenin inmemiş olmasıdır; süre neredeyse aynıdır. Küçük dosyalarda süreyi belirleyen şey gövdenin boyutu değil, gidip gelme sayısıdır.
Dört durum şöyle ayrılır. Iska, kopyanın hiç bulunmamasıdır. İsabet, kopyanın taze olmasıdır; kaynağa gidilmez. Doğrulandı, tazeliğin bitmesi ama içeriğin değişmemiş olmasıdır; kaynak gövdesiz bir yanıt verir, kenardaki kopya yeniden tazelenir. Yenilendi, tazeliğin bitmesi ve içeriğin de değişmiş olmasıdır; yeni gövde iner ve kopyanın yerini alır.
Politikanın İsabet Oranına Çevrimi
Yukarıdaki dört durumun hangi sıklıkla oluşacağını belirleyen şey, beşinci derste dosya sınıflarına göre verilen politikadır. Aşağıdaki hesap üç politikayı aynı trafik altında karşılaştırır.
// isabet.mjs — ada gore onbellek politikasinin kenar isabet oranina cevrimi. // Model ustten sinirdir: bir tazelik penceresi icinde en cok bir kaynak istegi sayilir. const GORUNTULEME = 240_000; // gunluk sayfa goruntulemesi const VARLIK = 12; // sayfa basina varlik istegi const DUGUM = 40; // kenar dugumu sayisi const YAYIN = 6; // gunluk yayin sayisi const DEGISEN = 3; // her yayinda icerigi degisen varlik sayisi const GUN = 86_400; // s const BELGE_BAYT = 14_000; const VARLIK_BAYT = 26_000; const dugumBasi = GORUNTULEME / DUGUM; // dugum basina belge istegi const pencere = (sure) => Math.min(dugumBasi, Math.floor(GUN / sure)); // Her politika, iki dosya sinifi icin [kaynak istegi, govdeli yanit] uretir. const POLITIKA = { "karmasiz ad, her sinifa 600 s": { belge: [pencere(600), 1 + YAYIN], varlik: [VARLIK * pencere(600), VARLIK + DEGISEN * YAYIN], bayatlik: "en cok 600 s", }, "degismez varlik + dogrulanan belge": { belge: [dugumBasi, 1 + YAYIN], varlik: [VARLIK + DEGISEN * YAYIN, VARLIK + DEGISEN * YAYIN], bayatlik: "yok", }, "degismez varlik + 60 s pencereli belge": { belge: [pencere(60) + YAYIN, 1 + YAYIN], varlik: [VARLIK + DEGISEN * YAYIN, VARLIK + DEGISEN * YAYIN], bayatlik: "en cok 60 s", }, }; const toplamIstek = GORUNTULEME * (1 + VARLIK); const toplamBayt = GORUNTULEME * (BELGE_BAYT + VARLIK * VARLIK_BAYT); const yuzde = (x) => (100 * x).toFixed(2) + "%"; console.log("gunluk goruntuleme " + GORUNTULEME.toLocaleString("tr-TR") + ", sayfa basina " + (1 + VARLIK) + " istek, " + DUGUM + " kenar dugumu, " + YAYIN + " yayin"); console.log("toplam istek " + toplamIstek.toLocaleString("tr-TR") + ", toplam gonderilen " + (toplamBayt / 1e9).toFixed(1) + " GB\n"); console.log("politika".padEnd(40) + "kaynak istegi".padStart(14) + "istek isabeti".padStart(15) + "bayt isabeti".padStart(14) + "goruntuleme basina tur".padStart(24) + " bayatlik"); for (const [ad, p] of Object.entries(POLITIKA)) { const istek = DUGUM * (p.belge[0] + p.varlik[0]); const bayt = DUGUM * (p.belge[1] * BELGE_BAYT + p.varlik[1] * VARLIK_BAYT); console.log(ad.padEnd(40) + istek.toLocaleString("tr-TR").padStart(14) + yuzde(1 - istek / toplamIstek).padStart(15) + yuzde(1 - bayt / toplamBayt).padStart(14) + (istek / GORUNTULEME).toFixed(3).padStart(24) + " " + p.bayatlik); } // Kaynaga giden her istek, 06. dersteki mesafe hesabinin turunu odetir. const TUR = 90; // ms, 9000 km icin gidis-donus console.log("\ngoruntuleme basina kaynaga odenen sure (" + TUR + " ms tur):"); for (const [ad, p] of Object.entries(POLITIKA)) { const istek = DUGUM * (p.belge[0] + p.varlik[0]); console.log(" " + ad.padEnd(40) + ((istek / GORUNTULEME) * TUR).toFixed(1) + " ms"); }
$ node isabet.mjs gunluk goruntuleme 240.000, sayfa basina 13 istek, 40 kenar dugumu, 6 yayin toplam istek 3.120.000, toplam gonderilen 78.2 GB politika kaynak istegi istek isabeti bayt isabeti goruntuleme basina tur bayatlik karmasiz ad, her sinifa 600 s 74.880 97.60% 99.96% 0.312 en cok 600 s degismez varlik + dogrulanan belge 241.200 92.27% 99.96% 1.005 yok degismez varlik + 60 s pencereli belge 59.040 98.11% 99.96% 0.246 en cok 60 s goruntuleme basina kaynaga odenen sure (90 ms tur): karmasiz ad, her sinifa 600 s 28.1 ms degismez varlik + dogrulanan belge 90.4 ms degismez varlik + 60 s pencereli belge 22.1 ms
Tablodaki en öğretici sütun, bütün politikalarda aynı çıkan sütundur: bayt isabet oranı üç politikada da aynıdır. Kaynaktan inen gövde miktarını belirleyen şey politika değil, içeriğin gerçekten kaç kez değiştiğidir. Bir varlık günde altı kez değişiyorsa her kenar düğümü onu en çok altı kez indirir.
Politikanın değiştirdiği şey istek sayısıdır. İkinci satırda, her kullanımda doğrulanan belge yüzünden görüntüleme başına bir tam tur kaynağa ödenir — dokuz bin kilometre için yaklaşık 90 milisaniye, üstelik hiçbir bayt inmeden. Bu, üçüncü sütunun neden yanıltıcı bir başarı ölçüsü olduğunu gösterir: bir yayın yüzde 99,96 bayt isabetiyle çalışırken kullanıcıya her sayfada bir kıtalar arası tur ödetiyor olabilir.
Üçüncü satır ödünleşimi bir ölçüye bağlar. Belgeye kısa da olsa bir tazelik penceresi vermek, görüntüleme başına ödenen turu dörtte bire indirir; karşılığı, yayından sonra en çok altmış saniye eski belgenin sunulmasıdır. Artımlı Yeniden Üretim dersindeki pencere hesabının aynısıdır; orada tazelik ile kaynak yükü, burada tazelik ile gecikme takas edilir.
Birinci satır karmalı adlandırmanın kazancını gösterir: karmasız adlarda değişmemiş varlıklar da pencere dolunca yeniden doğrulanır (varlık başına günde yüz kırk dört tur), değişmez işaretiyle bu turlar tümüyle kalkar.
Geçersiz Kılma ve Yayın Anı
Yayın yapıldığında kenar düğümlerindeki kopyalar hâlâ eskidir. İki yol vardır ve dosya sınıfına göre ayrılırlar.
Değişmez adlı varlıklar için hiçbir şey yapılmaz. Yeni yayının varlıkları yeni adlar taşır; o adlar hiçbir kenarda bulunmadığı için ilk istekte ıska verir. Beşinci dersteki eklemeli yayın kuralı kenar kopyaları için de geçerlidir.
Belge için geçersiz kılma gerekir. Belgenin adresi değişmediğinden, kenardaki kopyanın elle silinmesi ya da bir doğrulamaya zorlanması gerekir. Ölçümün altıncı ve yedinci satırları bu işlemi gösterir: silinen kopya bir sonraki istekte ıska verir.
Geçersiz kılmanın iki özelliği kararı etkiler. Birincisi, anında değildir: düğümler arasında yayılması zaman alır ve bu süre boyunca farklı kullanıcılar farklı sürümleri görür. İkincisi, tek başına yeterli değildir: kullanıcının kendi tarayıcı önbelleğindeki kopya kenardan silinemez. Belgeye uzun ömür verilmemesinin nedeni budur.
Yol Çözümü ve Yedek Belge
Statik barındırmada yolun karşılığı bir dosyadır. Adresler derleme zamanında biliniyorsa her adres için bir dosya üretilir ve eşleme doğrudandır. Adres istasyon kimliği ya da tarih aralığı taşıyorsa ve bu değerler derleme zamanında bilinmiyorsa dosya üretilemez.
Bu durumda kullanılan çözüm yedek belgedir: bilinen bir dosyaya eşlenmeyen her yol,
uygulamanın belgesine çözülür ve yolu istemcideki yönlendirme yorumlar. Ölçümün yol çözümü
bölümü bunu gösterir: bilinmeyen bir sayfa yolu belgeyi ve 200 durum kodunu alır.
Yedek belgenin iki kuralı vardır.
Varlık yolları yedeğe düşmemelidir. Bulunmayan bir karmalı ad 404 almalıdır. Yedeğe
düşerse tarayıcı, betik beklediği yerde HTML alır; hata ayrıştırma hatası olarak, gerçek
nedeninden uzakta görünür. Ölçümün son satırı bu ayrımı doğrular.
Var olmayan sayfa gerçekten 404 dönmelidir. Her yolun 200 ile belgeye çözülmesi,
olmayan bir kaynağın var gibi bildirilmesidir; dizinleyiciler bu yanıtı doğru kabul eder.
Bilinen sayfa yollarının listesi derleme zamanında üretilebiliyorsa, liste dışındaki yollar
gerçek bir hata yanıtı alır.
Bu Modelin Sınırı
Statik barındırmanın gücü yanıtın istekten bağımsız olmasından gelir; sınırı da aynı yerden.
Kişiye özel içerik üretilemez: yetkiye bağlı bölümler, oturuma bağlı görünümler ve isteğin
başlıklarına bakarak verilen kararlar bu modelde yoktur. Vary başlığıyla yanıtı isteğin bir
boyutuna göre çeşitlendirmek olanaklıdır, ama her çeşit önbellekte ayrı bir kopyadır; çeşit
sayısı arttıkça isabet oranı düşer ve modelin kazancı erir. Gizli değer gerektiren işler de
dışarıda kalır, çünkü sunulan her bayt açıktır.
Bu iki sınır, istek anında çalışan bir kod parçasını gerektirir.
Özet
- Statik barındırmada yayın bir dosya kopyasıdır; istek başına maliyet hemen hemen sıfırdır, ölçekleme kopya sayısını artırmakla yapılır, yanıt istekten bağımsızdır.
- Kenar önbelleğinin dört durumu ölçülebilir: ıska, isabet, doğrulandı ve yenilendi. İsabet dışındaki üçünde kaynağa bir tur yapılır; gövdesiz doğrulama da tam bir turdur.
- Bayt isabet oranını politika değil içeriğin değişme sıklığı belirler; üç ayrı politika aynı bayt oranını verir. Politikanın belirlediği şey istek sayısıdır.
- Her kullanımda doğrulanan belge, görüntüleme başına bir tam tur ödetir; belgeye altmış saniyelik bir pencere vermek bu turu dörtte bire indirir, karşılığı sınırlı bayatlıktır.
- Değişmez adlı varlıklar geçersiz kılma gerektirmez; belge gerektirir ve geçersiz kılma ne anında yayılır ne de kullanıcının kendi önbelleğine ulaşır.
- Bilinmeyen yollar yedek belgeye çözülür, ama varlık yolları
404dönmeli ve var olmayan sayfalar200ile bildirilmemelidir.
Sonraki Adım
Statik barındırmanın dışarıda bıraktığı her iş — oturuma bakan, gizli değer kullanan, isteğin kendisine göre karar veren iş — istek anında çalışan bir koda ihtiyaç duyar. Kuzey Yamaç sitesinde bu, arşiv yazma ucu, oturum yenileme ve yetkiye bağlı düzenleme görünümleridir. Sonraki ders bu kodun nerede çalışacağını sorar: sürekli ayakta duran bir süreçte mi, istek başına başlatılan bir örnekte mi, yoksa kenardaki dar bir çalışma zamanında mı. Üç seçenek soğuk başlatma, durum tutabilme, eşzamanlılık ve kapanma davranışı eksenlerinde ayrılır ve bu eksenlerin hepsi ölçülebilir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.