İçeriğe geç
academia.sh

Ders 02 / 19

Sunucuda Oluşturma

İşaretlemenin isteğe yanıt verilirken üretilmesi; ilk bayt süresinin nasıl büyüdüğü, seri istek zincirinin tek tura inmesi, ağ turunun ağırlığı ve sunucunun istek başına ödediği hesaplama bedeli.

İçindekiler

Önceki ders istemcide oluşturmanın ilk içeriğe kadar üç seri istek gerektirdiğini gösterdi: belge, betik, veri. Bu üç isteğin ikisi, veri ile işaretlemenin ayrı taşınmasından doğar. Sunucu veriyi zaten okuyabildiğine göre şablonu da kendisi işleyip içeriği hazır gönderebilir.

Bu ders o değişikliği yapar ve sonucunu aynı ölçülerle sınar: ilk bayt süresi, gövde boyutu ve belgede içeriğin bulunup bulunmaması.

Aynı Sayfa, İki Model

Karşılaştırmanın adil olması için iki model aynı veri kaynağını, aynı gecikmeyi ve aynı şablonu kullanır. Aşağıdaki sunucu her ikisini de sunar: /istemci yolu boş kabuğu, /sunucu yolu işaretlemesi hazır belgeyi verir.

// iki-model.mjs — ayni istasyon sayfasini iki modelde sunan tek sunucu
import { createServer } from "node:http";

const OLCUMLER = [
  { zaman: "06:00", sicaklik: -4.2, nem: 72 },
  { zaman: "07:00", sicaklik: -3.8, nem: 70 },
  { zaman: "08:00", sicaklik: -2.1, nem: 66 },
];

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
const okuOlcumler = async () => { await bekle(40); return OLCUMLER; }; // veri kaynagi isi

const liste = (olcumler) =>
  olcumler.map((o) => `<li>${o.zaman} ${o.sicaklik} C, %${o.nem}</li>`).join("");

const belge = (govde, betik) => `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="${betik}" defer></script></head>
<body>${govde}</body></html>
`;

const KABUK = belge('<div id="istasyon"></div>', "/uygulama.js");

const BETIK_ISTEMCI = `const kok = document.getElementById("istasyon");
const olcumler = await (await fetch("/api/olcumler")).json();
kok.innerHTML = "<h1>Kuzey Yamac Olcum Istasyonu</h1><ul>" +
  olcumler.map((o) => "<li>" + o.zaman + " " + o.sicaklik + " C, %" + o.nem + "</li>").join("") +
  "</ul>";
`;

const BETIK_SUNUCU = `document.querySelector("#yenile")
  .addEventListener("click", () => location.reload());
`;

const gonder = (yanit, tip, govde) => {
  yanit.setHeader("Content-Type", tip);
  yanit.setHeader("Content-Length", Buffer.byteLength(govde));
  yanit.writeHead(200).end(govde);
};

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

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  switch (istek.url) {
    case "/istemci":
      return gonder(yanit, HTML, KABUK);
    case "/uygulama.js":
      return gonder(yanit, JS, BETIK_ISTEMCI);
    case "/api/olcumler":
      return gonder(yanit, "application/json", JSON.stringify(await okuOlcumler()));
    case "/sunucu": {
      const olcumler = await okuOlcumler();
      const govde = `<div id="istasyon"><h1>Kuzey Yamac Olcum Istasyonu</h1>` +
        `<ul>${liste(olcumler)}</ul><button id="yenile">Yenile</button></div>`;
      return gonder(yanit, HTML, belge(govde, "/etkilesim.js"));
    }
    case "/etkilesim.js":
      return gonder(yanit, JS, BETIK_SUNUCU);
    default:
      return yanit.writeHead(404).end();
  }
}).listen(8172, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8172"));

Tek fark, /sunucu dalının veriyi yanıt üretilirken okuyup listeyi işaretlemeye yazmasıdır. Şablon işlevi ikisinde de aynıdır; ayrı olan, çağrıldığı yerdir.

Ölçüm

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

#!/usr/bin/env bash
# iki-model.mjs'yi baslatir, iki modeli ayni olculerle karsilastirir, sonra durdurur.
node iki-model.mjs > /dev/null &
sunucu=$!
sleep 1
curl -sS -o /dev/null http://127.0.0.1:8172/istemci   # isinma istegi: olcume girmez

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

echo "--- istemcide olusturma: ilk belge ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8172/istemci
echo "belgede gecen olcum degeri sayisi: $(curl -sS http://127.0.0.1:8172/istemci | grep -o -- '-4.2\|-3.8\|-2.1' | wc -l | tr -d ' ')"

echo "--- sunucuda olusturma: ilk belge ---"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8172/sunucu
echo "belgede gecen olcum degeri sayisi: $(curl -sS http://127.0.0.1:8172/sunucu | grep -o -- '-4.2\|-3.8\|-2.1' | wc -l | tr -d ' ')"

echo "--- sunucuda uretilmis govde ---"
curl -sS http://127.0.0.1:8172/sunucu

echo "--- istemci modelinde icerige kadar uc istegin toplami ---"
toplam=0
for yol in /istemci /uygulama.js /api/olcumler; do
  s=$(curl -sS -o /dev/null -w '%{time_starttransfer}' "http://127.0.0.1:8172$yol")
  printf '  %-14s %s s\n' "$yol" "$s"
  toplam=$(echo "$toplam + $s" | bc)
done
printf '  %-14s %s s\n' "toplam" "$toplam"

kill "$sunucu"
--- istemcide olusturma: ilk belge ---
ilk bayt 0.000909 s   govde 199 B
belgede gecen olcum degeri sayisi: 0
--- sunucuda olusturma: ilk belge ---
ilk bayt 0.042922 s   govde 358 B
belgede gecen olcum degeri sayisi: 3
--- sunucuda uretilmis govde ---
<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/etkilesim.js" defer></script></head>
<body><div id="istasyon"><h1>Kuzey Yamac Olcum Istasyonu</h1><ul><li>06:00 -4.2 C, %72</li><li>07:00 -3.8 C, %70</li><li>08:00 -2.1 C, %66</li></ul><button id="yenile">Yenile</button></div></body></html>
--- istemci modelinde icerige kadar uc istegin toplami ---
  /istemci       0.000837 s
  /uygulama.js   0.000796 s
  /api/olcumler  0.042007 s
  toplam         .043640 s

Süre alanları makineye ve o andaki yüke bağlıdır; her çalıştırmada farklı çıkar. Yorumu oranlar taşır.

Üç değişiklik ölçülmüştür. İlk bayt süresi iki mertebe uzamıştır — 0,0009 saniyeden 0,0429 saniyeye: boş kabuk için sunucu hiçbir iş yapmaz, hazır belge içinse veri kaynağını beklemek zorundadır. Kabuğun süresi yerel bağlantının gürültü tabanındadır ve çalıştırmadan çalıştırmaya belirgin biçimde oynar; sabit kalan, sunucuda oluşturmada ilk baytın veri kaynağının işi kadar gecikmesidir. Gövde yaklaşık 1,8 kat büyümüştür: işaretleme artık üç ölçüm satırını da taşır. Belgede içerik vardır: istemci modelinde sıfır olan ölçüm değeri sayısı üçtür.

Ağ Turu Olmayan Bir Ölçümün Sınırı

Son bölüm dikkatle okunmalıdır. İstemci modelinde içeriğe kadar geçen toplam süre 0,0436 saniyedir; sunucu modelinin tek isteği 0,0429 saniye sürer. İkisi neredeyse aynıdır.

Bunun nedeni ölçümün geri döngü arayüzünde yapılmasıdır: 127.0.0.1 adresine yapılan bir istekte ağ turu sıfıra yakındır. Ölçüm bu yüzden yalnızca sunucu işini yalıtır ve sunucu işi iki modelde de aynı 40 milisaniyedir. Sunucuda oluşturmanın kazancı sunucu işinden değil, kaç kez gidip gelindiğinden doğar.

Turun ağırlığı ayrıca hesaplanır. Aşağıdaki hesabın girdileri yukarıdaki ölçümden ve önceki dersteki betik varsayımlarından gelir.

// tur-etkisi.mjs — ag turunun iki modeldeki agirligi
// Sunucu isi ve aktarim, l02'deki yerel olcumden alinmistir: veri isi 42 ms,
// kabuk ve belge aktarimi yerel baglantida 1 ms'nin altinda kaldigi icin 1 ms sayildi.
const VERI_ISI = 42;      // ms
const AKTARIM = 1;        // ms, her yanit icin
const BETIK_INDIRME = 117; // ms, 180 KB / 1.5 MB/s
const BETIK_YURUTME = 63;  // ms

// istemci modeli: belge turu + betik turu + veri turu, arada betik indirme ve yurutme
const istemci = (tur) =>
  tur + AKTARIM + tur + BETIK_INDIRME + BETIK_YURUTME + tur + VERI_ISI + AKTARIM;

// sunucu modeli: tek tur, sunucu veriyi okur ve isaretlemeyi hazir gonderir
const sunucu = (tur) => tur + VERI_ISI + AKTARIM;

console.log("ag turu".padEnd(10) + "istemci".padStart(10) + "sunucu".padStart(10) +
  "fark".padStart(10) + "oran".padStart(8));
for (const tur of [0, 20, 60, 150]) {
  const i = istemci(tur), s = sunucu(tur);
  console.log(`${tur} ms`.padEnd(10) + `${i} ms`.padStart(10) + `${s} ms`.padStart(10) +
    `${i - s} ms`.padStart(10) + (i / s).toFixed(1).padStart(8));
}
ag turu      istemci    sunucu      fark    oran
0 ms          224 ms     43 ms    181 ms     5.2
20 ms         284 ms     63 ms    221 ms     4.5
60 ms         404 ms    103 ms    301 ms     3.9
150 ms        674 ms    193 ms    481 ms     3.5

Tablo iki şeyi aynı anda söyler. Mutlak fark tur uzadıkça büyür: 150 milisaniyelik bir turda aradaki uçurum yarım saniyeye yaklaşır. Oran ise küçülür, çünkü tur her iki modele de eklenir ve payda da büyür. Sıfır turluk satır, betik indirme ile yürütmenin tek başına 180 milisaniye tuttuğunu gösterir: bu kalem ağ hiç olmasa bile durmaz.

Sunucunun Ödediği Bedel

Kazanılan sürenin bir faturası vardır ve bu fatura istek başınadır.

Hesaplama işi tekrarlanır. Boş kabuk her kullanıcı için aynı bayt dizisidir ve bir kez üretilip sonsuza kadar sunulabilir. Sunucuda üretilen belge her istekte yeniden kurulur: veri okunur, şablon işlenir, dizgi birleştirilir. İstek hacmi arttıkça bu iş doğrusal büyür ve sunucunun boyutlandırılmasını belirleyen kalem hâline gelir.

Yanıt önbelleklenmesi zorlaşır. Önbellek Stratejileri dersinde kurulan ayrım burada karşılığını bulur: boş kabuk uzun ömürle saklanabilen bir varlıktır, sunucuda üretilmiş belge ise değişken içeriktir. İçerik kişiye göre değişiyorsa yanıt hiçbir paylaşımlı katmanda saklanamaz; değişmiyorsa saklanabilir ama tazelik penceresi içeriğin değişme sıklığıyla sınırlıdır. Kişiselleştirmenin sayfanın tamamına mı yoksa küçük bir bölümüne mi dokunduğu, bu yüzden bir tasarım kararıdır.

Veri kaynağının gecikmesi doğrudan ilk bayt süresine yazılır. İstemci modelinde kullanıcı beklerken en azından bir kabuk görür; sunucu modelinde veri kaynağı yavaşladığında kullanıcı hiçbir şey görmez, çünkü ilk bayt henüz yollanmamıştır. Yanıtın tamamı tek parçada üretildiği sürece bu bağımlılık kırılamaz.

Hata alanı genişler. Veri kaynağı yanıt vermediğinde istemci modelinde kabuk yüklenir ve arayüz bir hata durumu gösterebilir; sunucu modelinde belgenin kendisi üretilemez. Bu yüzden sunucuda oluşturan bir uç, veri çağrılarına zaman aşımı koyar ve eksik veriyle de üretilebilecek bir yedek işaretleme tanımlar.

Gelen İşaretleme Etkileşimli Değildir

Ölçülen gövdede bir düğme vardır ama düğme çalışmaz. İşaretleme sunucuda üretilmiştir; olay dinleyicileri istemcide, betik yürüdükten sonra bağlanır. Belge ekrana geldiği an ile düğmenin yanıt verdiği an arasında bir aralık kalır.

Bu aralık modelin doğal sonucudur ve ölçülebilir iki büyüklüğü ayırır: ilk içerik boyaması belgenin gelmesiyle gerçekleşir, etkileşime hazır olma betiğin yürümesini bekler. Sunucuda oluşturma birinciyi öne çeker, ikinciyi olduğu yerde bırakır. Aradaki boşlukta kullanıcı içeriği görür, tıklar ve tepki alamaz.

Boşluğun uzunluğu betiğin boyutuna ve aygıtın gücüne bağlıdır. Beşinci ders bu boşluğu adlandırır ve daraltmanın yollarını ele alır.

Özet

  • Sunucuda oluşturmada işaretleme isteğe yanıt verilirken üretilir; belge içeriği taşır ve ilk içeriğe kadar tek ağ turu yeter.
  • Ölçüm, ilk bayt süresinin veri kaynağının işi kadar uzadığını, gövdenin büyüdüğünü ve ölçüm değerlerinin işaretlemede göründüğünü gösterir.
  • Geri döngü arayüzünde yapılan bir ölçüm ağ turunu yalıtır; iki modelin farkı tur maliyeti arttıkça mutlak olarak büyür, oransal olarak küçülür.
  • Bedel istek başınadır: hesaplama tekrarlanır, yanıtın önbelleklenebilirliği içeriğin değişkenliğiyle sınırlanır ve veri kaynağının gecikmesi doğrudan ilk bayta yazılır.
  • Sunucudan gelen işaretleme etkileşimli değildir; ilk içerik boyası ile etkileşime hazır olma arasında betiğin yürümesini bekleyen bir aralık kalır.

Sonraki Adım

Sunucuda oluşturma, her istekte aynı işi yapmayı göze alır. İstasyonun tanıtım sayfası ile geçmiş günlerin arşiv sayfaları için bu israftır: içerikleri isteğe göre değişmez, aynı girdiden hep aynı işaretleme çıkar. Aynı çıktıyı üreten bir hesabın istek başına tekrarlanması yerine bir kez yapılıp dosyaya yazılması mümkündür. Sonraki ders bu kaymayı inceler: iş, isteğin geldiği andan derleme anına taşındığında ilk bayt süresine ne olur, ve bedel nereye — derleme süresine mi, tazeliğe mi — yazılı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