İçeriğe geç
academia.sh

Ders 17 / 19

Önizleme Dağıtımları

Her değişiklik için ayrı bir ortam üretmek; içerikten türetilen belirlenimci dağıtım kimliği, blokları paylaşan içerik adresli depo, önizlemelerin yayınla yan yana sunulması, veri ve yan etki yalıtımı ile dağıtımların ömrü.

İçindekiler

Önceki iki ders yayına bakıyordu: çıktının nasıl sunulduğuna ve istek anında çalışan kodun nerede durduğuna. Bir değişikliğin yayına girmeden önce çalıştığının görülmesi ise ayrı bir sorundur ve Geliştirme Sunucusu dersinin son cümlesi nedenini vermişti — geliştirme kipinde çalışması, yayın kipinde çalıştığının kanıtı değildir.

Bu ders her değişiklik için ayrı bir ortam kurar: yayın kipiyle derlenmiş, kendi adresi olan, yayına dokunmayan ve dizinlenmeyen bir dağıtım.

Önizleme Dağıtımının Karşılaması Gerekenler

Bir önizleme dağıtımı altı koşulu birden sağladığında işe yarar.

Yayınla aynı hat. Aynı derleme adımları, aynı kip, aynı denetimler. Farklı bir hatla üretilen bir önizleme, kanıtlamak istediği şeyi kanıtlamaz.

Kararlı ve türetilmiş adres. Dağıtımın kimliği içeriğinden türetilir; aynı değişiklik her zaman aynı kimliği verir. Önbellek Kırma dersindeki belirlenimcilik koşulu burada bir kimliğe dönüşür.

Yayınla aynı anda yaşayabilme. Önizlemenin varlığı yayının davranışını hiçbir biçimde değiştirmez; ikisi aynı sunucudan aynı anda sunulabilir.

Veri ve yan etki yalıtımı. Önizleme yayın verisine yazmaz, dışa giden yan etkileri tetiklemez.

Dizinlenmeme ve erişim denetimi. Önizleme adresleri arama dizinlerine girmemeli, herkese açık olmamalıdır.

Sonlu ömür. Değişiklik kapandığında dağıtım kaldırılır.

Dağıtım Kimliği ve Paylaşılan Depo

Aşağıdaki betik üç dalı — yayın ve iki değişiklik — kendi ortamlarıyla derler, dosyaları içerik adresli bir depoya yazar ve her dağıtım için bir kimlik üretir.

// onizleme.mjs — degisiklik basina dagitim: icerik adresli kimlik ve paylasilan depo.
import { createHash } from "node:crypto";
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
import { join } from "node:path";

const karma = (g) => createHash("sha256").update(g).digest("hex");

// Yayindaki kaynak agac. {{AD}} yer tutuculari 07. dersteki ikameyle doldurulur.
const TABAN = {
  "belge.html":
    '<!doctype html><html lang="tr"><body>' +
    "<h1>Kuzey Yamac Olcum Istasyonu</h1>" +
    "<p>uc: {{OLCUM_UCU}}</p><p>dal: {{DAL}}</p></body></html>\n",
  "varlik/istasyon.css": ".olcum { font-variant-numeric: tabular-nums; }\n",
  "varlik/giris.js": 'import "./panel.js";\n',
  "varlik/panel.js": 'export const ciz = (o) => o.map((x) => x.sicaklik).join(" ");\n',
  "varlik/grafik.js": "export const eksen = (a, b) => [a, b];\n",
};

// Uc dal: yayin ve iki ayri degisiklik. Her dal kendi ortamiyla derlenir.
const DAL = {
  yayin: { degisiklik: {}, ortam: { OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1" } },
  "esik-uyarisi": {
    degisiklik: {
      "varlik/panel.js":
        'export const ciz = (o) =>\n' +
        '  o.map((x) => (x.sicaklik < -20 ? "!" : "") + x.sicaklik).join(" ");\n',
    },
    ortam: { OLCUM_UCU: "https://deneme.olcum.kuzey-yamac.example/v1" },
  },
  "tarih-bicimi": {
    degisiklik: {
      "varlik/grafik.js": "export const eksen = (a, b) => [a, b, \"UTC\"];\n",
      "varlik/istasyon.css":
        ".olcum { font-variant-numeric: tabular-nums; font-feature-settings: \"ss01\"; }\n",
    },
    ortam: { OLCUM_UCU: "https://deneme.olcum.kuzey-yamac.example/v1" },
  },
};

const DEPO = "depo";
const sayac = { yazilan: 0, yeniden: 0 };

function derle(dalAdi) {
  const { degisiklik, ortam } = DAL[dalAdi];
  const tam = { ...ortam, DAL: dalAdi };
  const bildirim = {};
  for (const yol of Object.keys(TABAN).sort()) {
    const kaynak = degisiklik[yol] ?? TABAN[yol];
    const govde = kaynak.replace(/\{\{([A-Z_]+)\}\}/g, (t, ad) => tam[ad] ?? t);
    const k = karma(govde);
    const hedef = join(DEPO, k);
    if (existsSync(hedef)) sayac.yeniden += 1;
    else { writeFileSync(hedef, govde); sayac.yazilan += 1; }
    bildirim[yol] = k;
  }
  // Dagitim kimligi: yol-karma ciftlerinin siralanmis listesinin karmasi.
  const kimlik = karma(Object.entries(bildirim).map(([y, k]) => y + " " + k).join("\n"))
    .slice(0, 12);
  mkdirSync(join("dagitim", kimlik), { recursive: true });
  writeFileSync(join("dagitim", kimlik, "bildirim.json"),
    JSON.stringify({ dal: dalAdi, dosyalar: bildirim }, null, 2) + "\n");
  return { kimlik, bildirim };
}

rmSync(DEPO, { recursive: true, force: true });
rmSync("dagitim", { recursive: true, force: true });
mkdirSync(DEPO, { recursive: true });

const sonuc = {};
for (const dal of Object.keys(DAL)) sonuc[dal] = derle(dal);

console.log("dal".padEnd(16) + "dagitim kimligi".padEnd(18) + "dosya");
for (const [dal, s] of Object.entries(sonuc))
  console.log(dal.padEnd(16) + s.kimlik.padEnd(18) + Object.keys(s.bildirim).length);

console.log("\ndepo: " + sayac.yazilan + " ayri blok yazildi, " +
  sayac.yeniden + " blok yeniden kullanildi (toplam " +
  (sayac.yazilan + sayac.yeniden) + " dosya girisi)");

// Ayni kaynaktan ikinci derleme ayni kimligi vermelidir.
const tekrar = Object.fromEntries(Object.keys(DAL).map((d) => [d, derle(d).kimlik]));
console.log("ikinci derleme ayni kimlikleri verdi: " +
  Object.entries(sonuc).every(([d, s]) => s.kimlik === tekrar[d]));

// Yayin dagitimini isaretle ve kimlikleri sunucunun okuyacagi dosyaya yaz.
writeFileSync("guncel.txt", sonuc.yayin.kimlik + "\n");
writeFileSync("kimlikler.txt",
  Object.entries(sonuc).map(([d, s]) => d + " " + s.kimlik).join("\n") + "\n");

console.log("\ndagitimlar arasi fark (yayin ile karsilastirma):");
for (const dal of ["esik-uyarisi", "tarih-bicimi"]) {
  const fark = Object.keys(TABAN).sort()
    .filter((y) => sonuc[dal].bildirim[y] !== sonuc.yayin.bildirim[y]);
  console.log("  " + dal.padEnd(16) + fark.length + "/" + Object.keys(TABAN).length +
    " dosya farkli: " + fark.join(", "));
}

Yan Yana Sunmak

Üç dağıtım aynı sunucudan sunulur. Yayın kökte, önizlemeler kimliklerinin altında durur.

// onizleme-sunucu.mjs — dagitimlari kimliklerine gore yan yana sunar.
// Kokte yayin dagitimi, /o/<kimlik>/ altinda onizleme dagitimlari durur.
import { createServer } from "node:http";
import { readFileSync, existsSync } from "node:fs";
import { join, extname } from "node:path";

const YAYIN = readFileSync("guncel.txt", "utf8").trim();
const TUR = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
};

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const parca = istek.url.split("?")[0].split("/").filter(Boolean);
  const onizleme = parca[0] === "o";
  const kimlik = onizleme ? parca[1] : YAYIN;
  const yol = (onizleme ? parca.slice(2) : parca).join("/") || "belge.html";

  const bildirimYolu = join("dagitim", kimlik, "bildirim.json");
  if (!existsSync(bildirimYolu)) {
    yanit.writeHead(404, { "Content-Type": "text/plain" }).end("dagitim yok\n");
    return;
  }
  const bildirim = JSON.parse(readFileSync(bildirimYolu, "utf8"));
  const blok = bildirim.dosyalar[yol];
  if (!blok) {
    yanit.writeHead(404, { "Content-Type": "text/plain" }).end("dosya yok\n");
    return;
  }
  const govde = readFileSync(join("depo", blok));
  const baslik = {
    "Content-Type": TUR[extname(yol)] ?? "application/octet-stream",
    "Content-Length": govde.length,
    "X-Dagitim": kimlik,
    "X-Dal": bildirim.dal,
  };
  // Onizleme dagitimlari dizinlenmemeli ve yayin adresiyle karistirilmamalidir.
  if (onizleme) baslik["X-Robots-Tag"] = "noindex, nofollow";
  yanit.writeHead(200, baslik).end(govde);
}).listen(8183, "127.0.0.1", () => console.log("sunucu: 127.0.0.1:8183"));

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

#!/usr/bin/env bash
# Uc dagitimi uretir, hepsini ayni sunucudan yan yana sunar.
node onizleme.mjs
ESIK=$(awk '$1 == "esik-uyarisi" { print $2 }' kimlikler.txt)
TARIH=$(awk '$1 == "tarih-bicimi" { print $2 }' kimlikler.txt)

node onizleme-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

echo "--- yayin (kok) ---"
curl -sS -D - -o /dev/null http://127.0.0.1:8183/ | grep -iE '^(x-dagitim|x-dal|x-robots-tag)'
curl -sS http://127.0.0.1:8183/varlik/panel.js

echo "--- onizleme: esik-uyarisi ---"
curl -sS -D - -o /dev/null "http://127.0.0.1:8183/o/$ESIK/" | grep -iE '^(x-dagitim|x-dal|x-robots-tag)'
curl -sS "http://127.0.0.1:8183/o/$ESIK/varlik/panel.js"
curl -sS "http://127.0.0.1:8183/o/$ESIK/"

echo "--- onizleme: tarih-bicimi ---"
curl -sS "http://127.0.0.1:8183/o/$TARIH/varlik/grafik.js"
curl -sS -o /dev/null -w 'olmayan dagitim kimligi : %{http_code}\n' \
  http://127.0.0.1:8183/o/000000000000/

kill "$sunucu"
dal             dagitim kimligi   dosya
yayin           4eebc27e9c5c      5
esik-uyarisi    eede80ec039c      5
tarih-bicimi    779bc992a4e2      5

depo: 10 ayri blok yazildi, 5 blok yeniden kullanildi (toplam 15 dosya girisi)
ikinci derleme ayni kimlikleri verdi: true

dagitimlar arasi fark (yayin ile karsilastirma):
  esik-uyarisi    2/5 dosya farkli: belge.html, varlik/panel.js
  tarih-bicimi    3/5 dosya farkli: belge.html, varlik/grafik.js, varlik/istasyon.css
--- yayin (kok) ---
X-Dagitim: 4eebc27e9c5c
X-Dal: yayin
export const ciz = (o) => o.map((x) => x.sicaklik).join(" ");
--- onizleme: esik-uyarisi ---
X-Dagitim: eede80ec039c
X-Dal: esik-uyarisi
X-Robots-Tag: noindex, nofollow
export const ciz = (o) =>
  o.map((x) => (x.sicaklik < -20 ? "!" : "") + x.sicaklik).join(" ");
<!doctype html><html lang="tr"><body><h1>Kuzey Yamac Olcum Istasyonu</h1><p>uc: https://deneme.olcum.kuzey-yamac.example/v1</p><p>dal: esik-uyarisi</p></body></html>
--- onizleme: tarih-bicimi ---
export const eksen = (a, b) => [a, b, "UTC"];
olmayan dagitim kimligi : 404

Çıktı dört şey söylüyor.

Kimlik içerikten gelir. Aynı kaynaktan ikinci kez üretilen kimlikler özdeştir. Bu, bir dağıtımın hangi kaynaktan geldiğini adres üzerinden doğrulanabilir kılar ve sonraki dersteki geri alma işleminin dayanağıdır: geri dönülecek sürüm bir ada değil, bir içeriğe karşılık gelir.

Ortam kimliğe girer. Eşik uyarısı dalında yalnız bir dosya değiştirildiği hâlde yayından iki dosya farklıdır; ikincisi belgedir ve farkın nedeni ölçüm ucunun değişmesidir. Derleme zamanı ikamesinin yedinci derste söylenen sonucu burada ölçülür: ortam değiştiğinde çıktı baytları da değişir, dolayısıyla önizlemede sınanan baytlar yayına giden baytlar değildir. Bu farkı sıfırlamanın tek yolu, ortama bağlı değerleri çalışma zamanı yapılandırmasına taşımaktır.

Depo blokları paylaşır. On beş dosya girişi on ayrı bloka indi. Oran, dosya sayısı büyüdükçe iyileşir: bir dağıtımın maliyeti bütün çıktısı değil, yalnız değişen dosyalarıdır. Yüzlerce dosyalık bir yayında tek satırlık bir değişiklik birkaç yeni blok yazdırır.

Önizleme yayından ayrı bir kimliktir. Yanıt başlıkları hangi dağıtımın sunulduğunu söyler, önizlemeler dizinlenmeye kapalı işaretlenir, tanımsız bir kimlik 404 alır. Kökteki yayın, önizlemelerin varlığından etkilenmez.

Veri ve Yan Etki Yalıtımı

Önizlemenin en kolay bölümü baytlardır; zor bölümü, o baytların çalıştığında neye dokunduğudur.

Kural tek cümledir: önizleme yayın verisine yazamaz. Uygulanışı ise bir kod kararı değil, bir yapılandırma kararıdır. Yedinci dersteki kapsam bildirimi burada ikinci işini görür: veri ucunun adresi, yazma anahtarının hangi hesaba ait olduğu ve dışa giden çağrıların nereye gittiği ortam değişkenleriyle belirlenir. Önizleme ortamı bu değişkenleri deneme karşılıklarına bağlar.

Ayrımın kodda if (onizleme) biçiminde bir dala dönüşmesi yanlıştır. O dal yayın çıktısında da bulunur ve koşulunun yanlış değerlendiği her durumda yalıtım kaybolur. Sınırı ortam çizerse, yayın çıktısında deneme ucuna giden bir yol hiç bulunmaz.

Üç yan etki sınıfı ayrı ayrı ele alınır. Yazma işlemleri ayrı bir veri kopyasına gider; kopya tohumlanmış küçük bir kümedir, yayın verisinin aynası değildir — aynası olsaydı kişisel veri önizleme adresinden görünür olurdu. Dışa giden bildirimler — e-posta, ödeme çağrısı, üçüncü taraf tetikleyicileri — önizlemede ya kapalıdır ya bir toplayıcıya yönlendirilir. Şema değişiklikleri önizlemenin kendi kopyasında uygulanır; paylaşılan bir şema üzerinde uygulanan bir göç, henüz yayına girmemiş bir değişikliği yayına sokar.

Erişim de yalıtımın parçasıdır. X-Robots-Tag başlığı yalnız dizinleyicilere yöneliktir; adresi bilen birini engellemez. Kimliğin tahmin edilemez olması bir erişim denetimi değildir — adres tarayıcı geçmişine, bağlantı önizlemelerine ve yönlendiren başlıklarına düşer. Erişim denetimi ayrı bir katmandır.

Ömür, Maliyet ve Temizlik

Değişiklik başına ortam, dağıtım sayısını değişiklik sayısıyla çarpar. Maliyetin iki kalemi vardır ve ikisi de sınırlanabilir.

Depolama paylaşılan blok deposuyla sınırlanır. Bir dağıtımı silmek, bildirimini silmektir; blokların silinmesi ancak başka hiçbir bildirimin onlara başvurmadığı doğrulandıktan sonra yapılır. Başvuru sayımı yapılmadan yapılan bir temizlik, yayın dağıtımının dosyalarını da silebilir.

Çalışan kod varsa, ikinci dersteki üç biçimden hangisinin seçildiği doğrudan maliyete yansır. Önizlemeler seyrek ve düzensiz kullanılır; bu, o dersin tablosunda istek başına örneğin en iyi göründüğü penceredir.

Ömür kuralı basittir: değişiklik kapandığında önizleme kaldırılır. Bir istisnası vardır — yayına giren her dağıtım korunur. Korunan dağıtımlar bir sürüm geçmişi oluşturur ve sonraki dersin geri alma işlemi tam olarak bu geçmişe döner.

Özet

  • Önizleme dağıtımı altı koşulu birden sağlamalıdır: yayınla aynı hat, türetilmiş adres, yayınla yan yana yaşayabilme, veri yalıtımı, dizinlenmeme ve sonlu ömür.
  • Dağıtım kimliği yol-karma çiftlerinin karmasıdır; aynı kaynak aynı kimliği verir ve kimlik içeriğin doğrulanabilir adı olur.
  • Ortam değişkenleri çıktının baytlarına girdiği için önizleme ile yayın baytları ayrışır; ayrımı sıfırlamanın yolu değerleri çalışma zamanı yapılandırmasına taşımaktır.
  • İçerik adresli depo blokları paylaştırır: örnekte on beş dosya girişi on bloka indi, bir dağıtımın maliyeti yalnız değişen dosyalarıdır.
  • Yalıtım bir kod dalıyla değil, ortam yapılandırmasıyla kurulur; yazma, dışa giden bildirim ve şema değişikliği ayrı ayrı ele alınır.
  • Dizinlenmeme başlığı erişim denetimi değildir; dağıtım silme işlemi blok başvuru sayımına bakmalı, yayına giren dağıtımlar ise korunmalıdır.

Sonraki Adım

Önizlemede sınanmış bir değişiklik, yayına girdiğinde bütün kullanıcılara aynı anda ulaşır. Bu, önizlemenin yakalayamadığı her kusurun aynı anda herkese ulaşması demektir: yalnız gerçek trafikte ortaya çıkan yük davranışları, yalnız gerçek veride bulunan uç durumlar ve yalnız belirli aygıtlarda görülen hatalar. Sonraki ders yayını bir anahtar çevirmesi olmaktan çıkarır; trafiğin küçük bir payını yeni sürüme yönlendirir, hata oranını bir eşiğe karşı izler ve eşik aşıldığında geri döner. Geri dönülecek yer, bu derste korunan dağıtımlardır ve dönüş süresi ölçülebilir bir büyüklüktü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