İçeriğe geç
academia.sh

Ders 03 / 19

Statik Site Üretimi

İşaretlemenin derleme zamanında üretilip dosyaya yazılması; aynı şablonun iki ayrı anda çağrılması, sayfa sayısıyla doğrusal büyüyen derleme süresi ve statik çıktının tazelik sınırı.

İçindekiler

Sunucuda oluşturma, her istekte aynı işi yapmayı göze alır. İstasyonun arşiv sayfaları için bu iş tümüyle tekrardır: bir günün ölçüm kayıtları o gün kapandıktan sonra değişmez, aynı girdiden hep aynı işaretleme çıkar. Aynı çıktıyı veren bir hesabı her istekte tekrarlamak yerine bir kez yapıp sonucu saklamak mümkündür.

Bu ders o kaymayı yapar: iş, isteğin geldiği andan derleme anına taşınır. Ölçülecek olan, ilk bayt süresine ne kazandırdığı ve bedelin nereye yazıldığıdır.

Şablon Aynı Kalır, Çağrıldığı An Değişir

Karşılaştırmanın anlamlı olması için iki yolun aynı şablonu kullanması gerekir. Şablon ve veri kaynağı ayrı bir modüle konur; hem derleyici hem sunucu bunu çağırır.

// sayfa.mjs — arsiv sayfasinin veri kaynagi ve sablonu; iki cagirani da bunu kullanir
export const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

// Gunluk olcum kaydi: gunden belirlenimci olarak turetilir, veri kaynagi taklididir.
export function gununOlcumleri(gun) {
  return Array.from({ length: 24 }, (_, saat) => ({
    zaman: String(saat).padStart(2, "0") + ":00",
    sicaklik: (-8 + ((gun * 7 + saat * 3) % 130) / 10).toFixed(1),
    nem: 40 + ((gun * 11 + saat * 5) % 55),
  }));
}

export function sayfa(gun, olcumler) {
  const satirlar = olcumler
    .map((o) => `<tr><td>${o.zaman}</td><td>${o.sicaklik}</td><td>${o.nem}</td></tr>`)
    .join("");
  return `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac - gun ${gun}</title></head>
<body><h1>Kuzey Yamac Olcum Istasyonu</h1><h2>Gun ${gun}</h2>
<table><thead><tr><th>Saat</th><th>Sicaklik</th><th>Nem</th></tr></thead>
<tbody>${satirlar}</tbody></table></body></html>
`;
}

Statik üretimi bir teknik değil bir çağırma anı kararı yapan şey budur: kodda değişen hiçbir şey yoktur, yalnızca sayfa işlevinin ne zaman çalıştırıldığı değişir.

Derleme Zamanında Üretim

Derleyici, üretilecek sayfaların listesini gezer ve her biri için şablonu çağırıp sonucu diske yazar.

// uret.mjs — arsiv sayfalarini derleme zamaninda uretir ve sureyi olcer
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { gununOlcumleri, sayfa } from "./sayfa.mjs";

const CIKTI = "cikti";

function derle(sayfaSayisi) {
  rmSync(CIKTI, { recursive: true, force: true });
  mkdirSync(CIKTI, { recursive: true });
  const basla = performance.now();
  let bayt = 0;
  for (let gun = 1; gun <= sayfaSayisi; gun++) {
    const govde = sayfa(gun, gununOlcumleri(gun));
    writeFileSync(`${CIKTI}/gun-${gun}.html`, govde);
    bayt += Buffer.byteLength(govde);
  }
  return { sure: performance.now() - basla, bayt };
}

derle(50); // isinma turu: olcume girmez

console.log("sayfa".padStart(7) + "sure (ms)".padStart(12) + "sayfa basina (ms)".padStart(19) +
  "cikti (KB)".padStart(13));
let oncekiSure = null, oncekiSayi = null;
for (const n of [100, 500, 1000, 5000]) {
  const { sure, bayt } = derle(n);
  let oran = "";
  if (oncekiSure !== null) {
    oran = `   sayfa x${(n / oncekiSayi).toFixed(0)} -> sure x${(sure / oncekiSure).toFixed(1)}`;
  }
  console.log(String(n).padStart(7) + sure.toFixed(1).padStart(12) +
    (sure / n).toFixed(3).padStart(19) + (bayt / 1024).toFixed(0).padStart(13) + oran);
  oncekiSure = sure; oncekiSayi = n;
}

node uret.mjs komutu bu dosyayla aynı dizinde çalıştırılır ve yanına bir cikti dizini yazar.

  sayfa   sure (ms)  sayfa basina (ms)   cikti (KB)
    100         5.6              0.056          136
    500        26.0              0.052          680   sayfa x5 -> sure x4.6
   1000        48.9              0.049         1361   sayfa x2 -> sure x1.9
   5000       267.7              0.054         6813   sayfa x5 -> sure x5.5

Süreler makineye, disk hızına ve o andaki yüke bağlıdır; her çalıştırmada farklı çıkar. Okunması gereken sütun sonuncusudur: sayfa sayısı beş katına çıktığında süre de yaklaşık beş katına çıkar. Derleme doğrusaldır ve sayfa başına düşen maliyet sabit kalır.

Bu ölçekte sayfa başına maliyet şablonun işlenmesinden çok dosya yazmaya gider. Sayfa sayısı büyüdükçe ölçülen oranın beşin biraz üstüne çıkması bunun belirtisidir: dizin girdileri çoğaldıkça dosya sistemi işi ucuzlamaz.

İki Yol, Aynı Gövde

Aynı sayfa iki kaynaktan sunulup karşılaştırılabilir: diskteki dosya ve istek anında çağrılan şablon.

// statik-sunucu.mjs — ayni sayfayi iki yoldan sunar: diskteki dosya ve istek anindaki uretim
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { bekle, gununOlcumleri, sayfa } from "./sayfa.mjs";

const HTML = "text/html; charset=utf-8";

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const statik = istek.url.match(/^\/statik\/gun-(\d+)\.html$/);
  const anlik = istek.url.match(/^\/istek-aninda\/gun-(\d+)$/);
  let govde;
  if (statik) {
    govde = await readFile(`cikti/gun-${statik[1]}.html`, "utf8");
  } else if (anlik) {
    await bekle(40); // veri kaynagindan gunun kayitlarini okuma isi
    govde = sayfa(Number(anlik[1]), gununOlcumleri(Number(anlik[1])));
  } else {
    return yanit.writeHead(404).end();
  }
  yanit.setHeader("Content-Type", HTML);
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
}).listen(8173, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8173"));
#!/usr/bin/env bash
# Once sayfalari uretir, sonra iki yolu ayni olcuyle karsilastirir.
node uret.mjs > /dev/null   # sayfalari uretir, tablosu ayrica calistirilir
node statik-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1
curl -sS -o /dev/null http://127.0.0.1:8173/statik/gun-1.html   # isinma

BICIM='  ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

echo "--- diskteki dosya ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8173/statik/gun-7.html
echo "--- istek aninda uretim ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8173/istek-aninda/gun-7

echo "--- iki govde ayni mi? ---"
curl -sS http://127.0.0.1:8173/statik/gun-7.html > a.html
curl -sS http://127.0.0.1:8173/istek-aninda/gun-7 > b.html
if cmp -s a.html b.html; then echo "ayni"; else echo "farkli"; fi
rm -f a.html b.html

kill "$sunucu"
--- diskteki dosya ---
  ilk bayt 0.000912 s   govde 1386 B
--- istek aninda uretim ---
  ilk bayt 0.042926 s   govde 1386 B
--- iki govde ayni mi? ---
ayni

Gövdeler bayt bayt aynıdır; ilk bayt süreleri arasında iki mertebe fark vardır — 0,0009 saniyeye karşı 0,0429 saniye. Kullanıcıya giden şey değişmemiştir, o şeyi üretme işi zamanda geriye alınmıştır.

Farkın kaynağı, veri kaynağının 40 milisaniyelik işidir. Statik yolda bu iş derleme sırasında bir kez yapılmıştır ve istek anında hiç yapılmaz. İstek anındaki üretimde ise her istekte yeniden yapılır: bin ziyaretçi, bin kez aynı hesap demektir.

Statik Çıktının Getirdikleri

Diskteki bir dosya olmanın, dosya olmaktan doğan sonuçları vardır.

Sunucu çalıştırma zamanı gerektirmez. Yanıt bir dosya okumasıdır; bir uygulama süreci, bir veri kaynağı bağlantısı ya da bir çalışma zamanı ortamı olmadan verilebilir. Bu, bir içerik dağıtım ağının çıktıyı olduğu gibi kopyalayıp sunabilmesi anlamına gelir.

Yanıt herkes için aynıdır. Aynı adres her istemciye aynı baytları verir; bu, yanıtın paylaşımlı katmanlarda uzun ömürle saklanabilmesini sağlar. Önbellek Stratejileri dersindeki ayrım burada en uygun tarafındadır: değişmeyen içerik, doğrulama isteği bile gerektirmeyen içeriktir.

Yük, istek hacminden bağımsızlaşır. Sunucuda oluşturmada maliyet istek sayısıyla büyürdü; statik üretimde maliyet sayfa sayısıyla büyür ve bir kez ödenir. On ziyaretçi ile on bin ziyaretçi arasındaki fark, yalnızca bant genişliğidir.

Hata yüzeyi daralır. İstek anında çalışan kod yoksa, istek anında oluşacak hata da yoktur. Bir şablon hatası derleme sırasında görünür ve yayına çıkmadan yakalanır.

Nerede Uygulanamaz

Statik üretimin koşulu tektir: sayfanın çıktısı yalnızca derleme anında bilinen girdilere bağlı olmalıdır. Bu koşul üç durumda bozulur.

Sayfa kullanıcıya göre değişiyorsa üretilemez. İstasyon sorumlusunun paneli her kişi için farklıdır; derleme zamanında hangi kişi için üretileceği bilinmez.

İçerik derlemeler arasında değişiyorsa çıktı eskir. Canlı ölçüm listesi dakikada bir değişir; günde bir kez derlenen bir dosya, gösterdiği değerin yaşını hiçbir zaman bildiremez. Statik üretimde tazelik, derleme sıklığıyla sınırlıdır.

Sayfa sayısı derlemeyi taşıyamaz hâle geliyorsa yöntem tıkanır. Ölçülen doğrusallık iyi haber gibi görünür ama doğrusal büyümenin sonu vardır: sayfa başına 0,05 milisaniye, yüz bin sayfada beş saniye, bir milyon sayfada bir dakikanın üstü demektir. Gerçek bir derlemede sayfa başına iş bu örnekten büyüktür ve tek bir yazım düzeltmesi için bütün sitenin yeniden üretilmesi kabul edilemez hâle gelir.

Bu üçüncü sınır, statik üretimin kendi içinden çıkan bir soruya yol açar: bütün sayfaları yeniden üretmeden yalnızca değişenleri tazelemek mümkün müdür?

Özet

  • Statik üretim, işaretlemeyi üreten şablonu istek anında değil derleme anında çağırır; şablonun kendisi değişmez.
  • Ölçüm iki yolun gövdesinin bayt bayt aynı olduğunu, ilk bayt sürelerinin ise iki mertebe ayrıldığını gösterir; fark, veri kaynağının işinin zamanda öne alınmasıdır.
  • Derleme süresi sayfa sayısıyla doğrusal büyür ve sayfa başına maliyet sabit kalır; toplam maliyet istek hacmine değil sayfa sayısına bağlıdır.
  • Statik çıktı çalışma zamanı gerektirmez, herkese aynı yanıtı verir ve uzun ömürle önbelleklenebilir; istek anında çalışan kod olmadığı için hata yüzeyi daralır.
  • Yöntem, çıktı derleme anında bilinmeyen girdilere bağlıysa, içerik derlemeler arasında değişiyorsa ya da sayfa sayısı derlemeyi taşıyamıyorsa uygulanamaz.

Sonraki Adım

Üçüncü sınır, ikisinin arasında bir yer olduğunu düşündürür. Bir sayfa statik olarak üretilmiş olabilir ama sonsuza kadar geçerli sayılmak zorunda değildir: kendisine bir geçerlilik süresi tanınabilir, süre dolduğunda bir sonraki istek eski kopyayı alırken arka planda yeni sürümü üretilebilir. Böylece hem çalışma zamanı maliyeti düşük kalır hem de tazelik derleme sıklığına mahkûm olmaz. Sonraki ders bu düzeneği kurar ve ölçer: tazelik penceresi, arka plan tazeleme kuyruğu, isabet ve ıska sayıları.

İ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