---
title: 'Sunucu Güvenliği ve Üretime Alma'
source: 'https://academia.sh/tr/kurslar/backend-uretim'
language: tr
updated: '2026-08-17T04:38:49+00:00'
license: 'CC BY-SA 4.0'
lessons: 16
outcomes:
    - 'Uygulama ve sunucu düzeyinde savunma katmanları kurar'
    - 'Sıfır kesintili yayın stratejisi uygular'
    - 'Ölçekleme kararlarını ölçüme dayandırır'
---

# Sunucu Güvenliği ve Üretime Alma

Uygulamanın dışarıya açıldığı nokta: güvenilmeyen veri sınırının girdi doğrulama ve kodlamayla uygulanması, enjeksiyon sınıfı açıkların kaynağındaki birleştirme kararı, kaynaklar arası erişimin sunucu tarafında doğru yapılandırılması, taşıma katmanı sertleştirme, gizli değerin saklanması, döndürülmesi ve erişiminin sınırlanması, bağımlılık riski, denetim kaydı, kötüye kullanım savunması ile ters vekil yapılandırması, süreç yöneticileri, iki ölçekleme ekseninin maliyet farkı, durumsuz tasarım, oturum taşımanın etkisi, sıfır kesintili yayın, geriye uyumlu göç sırası ve yük testi verisinden kaynak tahmini.

## Uygulama Güvenliği

Güvenilmeyen verinin uygulamada karşılandığı yer: doğrulama ile kodlamanın sınırda uygulanması, enjeksiyon sınıfı açıkların kaynağındaki dizge birleştirme kararı, kaynaklar arası erişimin sunucuda doğru yapılandırılması, güvenlik başlıkları ile taşıma katmanı sertleştirme, gizli değerin saklanması ve döndürülmesi, bağımlılık riskinin sürüm politikasıyla yönetilmesi, izlenebilirlik için denetim kaydı ve bot, kaba kuvvet ile numaralandırmaya karşı kötüye kullanım savunması.

### Girdi Doğrulama ve Kodlama

Source: https://academia.sh/tr/kurslar/backend-uretim/girdi-dogrulama-ve-kodlama

Güvenilmeyen veri sınırının gerçekleştirilmesi: yirmi iki isteklik bir kümede izin listesiyle engelleme listesinin yanlış geçme ve yanlış kalma sayıları, doğrulamanın uçta, serviste ve depoda durmasının yan etki maliyeti, tek kodlayıcının üç çıktı bağlamında bozduğu değer sayısı ve tek satırlık bir ayarın sessizce açtığı alan.

Gözlemlenebilirlik kursu ödünç sistemini görünür kıldı, dayanıklılık kursu arızaya
dayanmasını sağladı. İki kurs boyunca sisteme giden her istek geçerliydi: yük üreteci
sözleşmeye uyan gövdeler gönderdi, kaos denemeleri isteği değil altyapıyı bozdu. Sözleşmeye
**uymayan** bir isteğin sistem içinde nereye kadar gidebildiği hiç ölçülmedi.

Bu dersin konusu o sınırdır. Bölünmüş ödünç sisteminin üç dış ucu var; bu uçlara gelen her
gövde, aksi kanıtlanana kadar **güvenilmeyen veridir**. Açıkların kendisi anlatılmaz —
istemci tarafı Frontend Kalitesi, tarama tarafı Kalite ve Test kursunda kuruldu — burada
**savunmanın gerçekleştirimi** ölçülür.

#### Güvenilmeyen Veri Sınırı

Sözleşme önce yazılır: hangi uç, hangi alanı, hangi biçimde kabul ediyor. Her isteğin kabul
mü ret mi olacağı ödünç sisteminin kurallarından gelir ve elle etiketlenir.

**BG1 (varsayım):** etiketler eksiksiz ve doğrudur. Ölçülen sayılar bu etiket kümesine
görelidir ve kümede olmayan bir istek biçimi hakkında bir şey söylemez.

```js
// istekler.mjs — odunc sisteminin uc dis ucu, alan sozlesmesi ve elle etiketli istek kumesi
const O = "POST /odunc", K = "GET /katalog", N = "POST /uye/not";

export const SEMA = {
  [O]: { uyeNo: { t: "d", desen: /^U-\d{5}$/, zorunlu: 1 },
         isbn:  { t: "d", desen: /^\d{13}$/, zorunlu: 1 },
         gun:   { t: "s", enAz: 1, enCok: 30 } },
  [K]: { q:     { t: "d", enCok: 64, zorunlu: 1 },
         sira:  { t: "d", kume: ["baslik", "yazar", "yil"] },
         sayfa: { t: "s", enAz: 1, enCok: 500 } },
  [N]: { uyeNo: { t: "d", desen: /^U-\d{5}$/, zorunlu: 1 },
         not:   { t: "d", enCok: 120, zorunlu: 1 } },
};

// [uc, govde, mesru?] — mesru degeri elle verilmis karardir, olcumden gelmez.
export const ISTEKLER = [
  [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: 21 }, 1],
  [O, { uyeNo: "U-10432", isbn: "9789750718533" }, 1],
  [K, { q: "kayip zaman" }, 1],
  [K, { q: "O'Neill", sira: "yazar" }, 1],
  [K, { q: "kalem", sayfa: 3 }, 1],
  [N, { uyeNo: "U-10432", not: "kapak yipranmis" }, 1],
  [N, { uyeNo: "U-77120", not: "iadede 5 < 7 sayfa eksik" }, 1],
  [O, { uyeNo: "U-00001", isbn: "9786050000000", gun: 1 }, 1],
  [K, { q: "tarih -- 20. yuzyil" }, 1],
  [N, { uyeNo: "U-31005", not: "kitap; elden birakildi" }, 1],
  [O, { uyeNo: "U-10432", isbn: "978-975-07-1853-3" }, 1],   // tireli mesru bicim
  [O, { uyeNo: "u-10432", isbn: "9789750718533" }, 0],       // kucuk harf
  [O, { uyeNo: "U-10432", isbn: "978975071853" }, 0],        // on iki hane
  [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: 400 }, 0],
  [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: "21" }, 0],
  [O, { uyeNo: ["U-10432"], isbn: "9789750718533" }, 0],
  [O, { uyeNo: "U-10432", isbn: "9789750718533", ceza: 0 }, 0],
  [K, { q: "k".repeat(200) }, 0],
  [K, { q: "kalem", sira: "kitap.ic_not" }, 0],
  [K, { q: "kalem", sayfa: -2 }, 0],
  [N, { uyeNo: "U-10432" }, 0],
  [N, { uyeNo: "U-10432", not: "ok", yetki: "yonetici" }, 0],
].map(([uc, govde, m], i) => ({ no: i + 1, uc, govde, bekleniyor: m ? "kabul" : "ret" }));

export const sayim = (e) => ISTEKLER.filter((i) => i.bekleniyor === e).length;
```

Sözleşme dışı isteklerde zararlı yük yok; yanlış biçim, yanlış tip ve fazladan alan var.

#### İki Karar Biçimi

**İzin listesi** sözleşmede yazılı olan dışında hiçbir şeyi geçirmez. **Engelleme listesi**
adı konmuş kötü biçimleri reddeder, geri kalanı geçirir. `bilinmeyenAlan` ayarı üçüncü bir
satır üretecek.

```js
// dogrulayici.mjs — ayni istek kumesine bakan iki karar bicimi
import { SEMA } from "./istekler.mjs";

// Izin listesi: sozlesmede yazili olan disinda hicbir sey gecmez.
// bilinmeyenAlan = "reddet" | "gecir"  (ikincisi bu dersin sessiz ayari)
export function izinListesi(uc, govde, { bilinmeyenAlan = "reddet" } = {}) {
  const alanlar = SEMA[uc];
  if (!alanlar) return { karar: "ret", neden: "tanimsiz uc" };
  const ret = (n) => ({ karar: "ret", neden: n });
  for (const [ad, v] of Object.entries(govde)) {
    const k = alanlar[ad];
    if (!k) { if (bilinmeyenAlan === "reddet") return ret(`bilinmeyen alan ${ad}`); continue; }
    if (k.t === "d") {
      if (typeof v !== "string") return ret(`${ad} dizge degil`);
      if (k.desen && !k.desen.test(v)) return ret(`${ad} bicime uymuyor`);
      if (k.enCok && v.length > k.enCok) return ret(`${ad} cok uzun`);
      if (k.kume && !k.kume.includes(v)) return ret(`${ad} kume disinda`);
    } else {
      if (!Number.isInteger(v)) return ret(`${ad} tam sayi degil`);
      if (v < k.enAz || v > k.enCok) return ret(`${ad} aralik disinda`);
    }
  }
  for (const [ad, k] of Object.entries(alanlar))
    if (k.zorunlu && govde[ad] === undefined) return ret(`${ad} eksik`);
  return { karar: "kabul", neden: "" };
}

// Engelleme listesi: adi konmus kotu bicimler reddedilir, geri kalan her sey gecer.
const YASAK = ["<", ">", "'", '"', "--", ";", "..", "\\"];
export function engellemeListesi(uc, govde) {
  for (const [ad, v] of Object.entries(govde)) {
    const m = typeof v === "string" ? v : String(v ?? "");
    for (const y of YASAK) if (m.includes(y)) return { karar: "ret", neden: `${ad} yasak imge ${y}` };
    if (m.length > 256) return { karar: "ret", neden: `${ad} cok uzun` };
  }
  return { karar: "kabul", neden: "" };
}

// Yanlis gecme: reddedilmesi gereken istek kabul edildi.
// Yanlis kalma: kabul edilmesi gereken istek reddedildi.
export function olc(istekler, karar) {
  const r = { yanlisGecme: 0, yanlisKalma: 0, kacan: [], takilan: [] };
  for (const i of istekler) {
    const c = karar(i.uc, i.govde).karar;
    if (c === i.bekleniyor) continue;
    if (i.bekleniyor === "ret") { r.yanlisGecme++; r.kacan.push(i.no); }
    else { r.yanlisKalma++; r.takilan.push(i.no); }
  }
  return r;
}
```

```js
// karsilastir.mjs — ayni 22 istek, uc karar bicimi; ucuncusu ilk ikisinin sessiz ayari
import { ISTEKLER, SEMA, sayim } from "./istekler.mjs";
import { izinListesi, engellemeListesi, olc } from "./dogrulayici.mjs";

const artik = (i) => Object.keys(i.govde).filter((a) => !SEMA[i.uc][a]);   // sozlesmede olmayan alan
const alan = Object.values(SEMA).reduce((t, a) => t + Object.keys(a).length, 0);
console.log(`${Object.keys(SEMA).length} uc, ${alan} alan sozlesmesi, ${ISTEKLER.length} istek: ` +
  `${sayim("kabul")} mesru, ${sayim("ret")} sozlesme disi\n`);

const BICIM = [
  ["izin listesi", (uc, g) => izinListesi(uc, g)],
  ["izin listesi (gecir)", (uc, g) => izinListesi(uc, g, { bilinmeyenAlan: "gecir" })],
  ["engelleme listesi", engellemeListesi],
];

console.log(`${"karar bicimi".padEnd(22)}${"yanlis gecme".padStart(13)}${"yanlis kalma".padStart(14)}${"artik alanli kayit".padStart(20)}${"hata".padStart(6)}`);
for (const [ad, f] of BICIM) {
  const r = olc(ISTEKLER, f);
  let a = 0, hata = 0;
  for (const i of ISTEKLER) {
    try { if (f(i.uc, i.govde).karar === "kabul" && artik(i).length) a++; } catch { hata++; }
  }
  console.log(ad.padEnd(22) + String(r.yanlisGecme).padStart(13) + String(r.yanlisKalma).padStart(14) +
    String(a).padStart(20) + String(hata).padStart(6));
  console.log(`  gecen sozlesme disi: ${r.kacan.join(", ") || "yok"}`);
  console.log(`  takilan mesru      : ${r.takilan.map((n) => `${n} (${f(ISTEKLER[n - 1].uc, ISTEKLER[n - 1].govde).neden})`).join(", ") || "yok"}`);
}
```

```
3 uc, 8 alan sozlesmesi, 22 istek: 11 mesru, 11 sozlesme disi

karar bicimi           yanlis gecme  yanlis kalma  artik alanli kayit  hata
izin listesi                      0             1                   0     0
  gecen sozlesme disi: yok
  takilan mesru      : 11 (isbn bicime uymuyor)
izin listesi (gecir)              2             1                   2     0
  gecen sozlesme disi: 17, 22
  takilan mesru      : 11 (isbn bicime uymuyor)
engelleme listesi                11             4                   2     0
  gecen sozlesme disi: 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
  takilan mesru      : 4 (q yasak imge '), 7 (not yasak imge <), 9 (q yasak imge --), 10 (not yasak imge ;)
```

Engelleme listesi on bir sözleşme dışı isteğin **hepsini** geçirdi: liste yalnızca adını
bildiği imgeleri tanır, on iki haneli tanımlayıcıda ya da fazladan bir alanda o imgelerden
hiçbiri yok. Aynı liste dört meşru isteği reddetti — bir yazar adındaki kesme işareti, bir
nottaki küçüktür imgesi, arama metnindeki iki çizgi, bir noktalı virgül. **Engelleme
listesi iki sütunda birden kaybediyor.**

**BG2 (varsayım):** izin listesinin sözleşmesi, kümedeki meşru isteklerin biçimine bakarak
yazılmıştır; sıfır yanlış geçme kısmen bu yakınlığın sonucudur. Kalıntı on birinci istektir:
tireli tanımlayıcı meşrudur, sözleşme onu tanımıyor. **İzin listesinin bedeli budur** —
öngörülmemiş her meşru biçim bir yanlış kalmadır.

Ortadaki satır bu dersin ilk sessiz ayarıdır. `bilinmeyenAlan` ilkesi `gecir` yapıldığında
fırlatılan hata yine sıfırdır, ama iki istek kabul edilir ve depoya sözleşmede olmayan iki
alanla iner: bir ceza değeri ve bir yetki değeri. Bu alanları okuyan bir kod bugün yoktur.

#### Sınırın Yeri

Ödünç isteği üç adımdan geçer: uçta kaydı alınır, serviste katalog rezervi yapılır, depoya
yazılır. İlk iki adım geri alınması gereken yan etkilerdir.

**BG3 (varsayım):** yan etki adım sayısıyla ölçülür, saniyeyle değil; her adımın süresi bu
derste sabit sayılır.

```js
// sinir.mjs — ayni dogrulayici, bes ayri sinir; olculen sey yan etkinin nerede durdugu
import { ISTEKLER, SEMA } from "./istekler.mjs";
import { izinListesi } from "./dogrulayici.mjs";

const ADIMLAR = ["uc", "servis", "depo"];   // yan etki: istek kaydi, katalog rezervi, yazma

function kos(istek, yerlesim, kayitli) {
  let yanEtki = 0;
  for (const a of ADIMLAR) {
    if (a === yerlesim && kayitli.includes(istek.uc) &&
        izinListesi(istek.uc, istek.govde).karar === "ret") return { yanEtki, yazildi: false };
    yanEtki++;
  }
  return { yanEtki, yazildi: true };
}

const TUM = Object.keys(SEMA);
const EKSIK = TUM.filter((u) => u !== "POST /uye/not");   // bir uc kayit edilmeyi bekliyor
const bozuk = ISTEKLER.filter((i) => i.bekleniyor === "ret");
const mesru = ISTEKLER.filter((i) => i.bekleniyor === "kabul");

console.log(`${bozuk.length} sozlesme disi istek, ${mesru.length} mesru istek, ${ADIMLAR.length} adimli boru hatti`);
console.log(`\n${"yerlesim".padEnd(12)}${"kapsanan uc".padStart(12)}${"duran".padStart(7)}${"yazilan bozuk".padStart(15)}${"bosa yan etki".padStart(15)}${"mesru yazma".padStart(13)}`);

for (const [ad, yer, kayitli] of [["uc", "uc", TUM], ["uc (eksik)", "uc", EKSIK],
    ["servis", "servis", TUM], ["depo", "depo", TUM], ["yok", "-", []]]) {
  const b = bozuk.map((i) => kos(i, yer, kayitli)), m = mesru.map((i) => kos(i, yer, kayitli));
  console.log(ad.padEnd(12) + String(kayitli.length).padStart(12) +
    String(b.filter((r) => !r.yazildi).length).padStart(7) +
    String(b.filter((r) => r.yazildi).length).padStart(15) +
    String(b.reduce((t, r) => t + (r.yazildi ? 0 : r.yanEtki), 0)).padStart(15) +
    String(m.filter((r) => r.yazildi).length).padStart(13));
}

const gecen = bozuk.filter((i) => kos(i, "uc", EKSIK).yazildi).map((i) => i.no);
console.log(`\nkayit edilmeyen uc POST /uye/not -> depoya inen sozlesme disi kayit: ${gecen.length} (istek ${gecen.join(", ")})`);
console.log(`bu yerlesimde uygulamanin urettigi hata sayisi: 0`);
```

```
11 sozlesme disi istek, 11 mesru istek, 3 adimli boru hatti

yerlesim     kapsanan uc  duran  yazilan bozuk  bosa yan etki  mesru yazma
uc                     3     11              0              0           10
uc (eksik)             2      9              2              0           10
servis                 3     11              0             11           10
depo                   3     11              0             22           10
yok                    0      0             11              0           11

kayit edilmeyen uc POST /uye/not -> depoya inen sozlesme disi kayit: 2 (istek 21, 22)
bu yerlesimde uygulamanin urettigi hata sayisi: 0
```

Üç tam yerleşim de aynı sayıda isteği durduruyor; ayrıştıkları yer **ne kadar iş boşa
gittiği**. Uçta doğrulama sıfır yan etkiyle durur, serviste on bir istek kaydını boşa yazar,
depoda yirmi iki adımı boşa çalıştırır — bunların on biri katalog rezervidir, yani bir kopya
ayrılıp geri bırakılır.

İkinci satır ikinci sessiz ayardır. Doğrulayıcı uç başına kaydediliyorsa üç kayıt noktası
vardır ve dördüncü uç eklendiğinde dördüncü kayıt elle yazılmalıdır. Yazılmazsa uygulama
çalışır, hiçbir sınama kırılmaz ve iki sözleşme dışı kayıt sessizce depoya iner. Tek boru
hattı bu riski bir noktaya indirir.

`mesru yazma` sütunu son bir şey söylüyor: doğrulamalı her yerleşimde on bir meşru istekten
**onu** depoya iniyor. Yanlış kalmanın bedeli kaydedilmeyen bir ödünç işlemidir.

#### Çıktı Kodlama

Doğrulamayı geçen değer güvenli değildir; **sözleşmeye uygundur**. Üye notu serbest metin
kabul eder, çünkü okuyucu notunda tırnak, virgül ve küçüktür imgesi meşru geçer. Bu değer üç
ayrı yere yazılıyor: bir işaretleme gövdesine, bir öznitelik tırnağının içine ve virgülle
ayrılmış bir dışa aktarım satırına. Ölçü tek sorudur: **değer kaba konup geri okunduğunda
aynı mı çıkıyor?**

```js
// kodlama.mjs — dogrulamayi gecmis mesru degerler, uc ayri cikti baglami
// Olcu: deger kaba yerlestirilip kabin kendi kuraliyla geri okundugunda aynisi mi cikiyor?
const DEGERLER = ["kapak yipranmis", "iadede 5 < 7 sayfa eksik", 'uye "acele" notu dusmus',
  "raf 3, kat 2", "ilk satir\nikinci satir", 'bagis, "ikinci el" 5 kitap'];

const govde = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const oznitelik = (s) => govde(s).replace(/"/g, "&quot;").replace(/\n/g, "&#10;");
const ayrilmis = (s) => `"${s.replace(/"/g, '""')}"`;
const coz = (s) => s.replace(/&quot;/g, '"').replace(/&#10;/g, "\n")
  .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");

const ayir = (k) => {                       // tirnaga saygili alan ayirici
  const alan = []; let s = "", t = false;
  for (let i = 0; i < k.length; i++) {
    const c = k[i];
    if (t && c === '"' && k[i + 1] === '"') { s += '"'; i++; }
    else if (c === '"') t = !t;
    else if (!t && (c === "," || c === "\n")) { alan.push(s); s = ""; }
    else s += c;
  }
  return [...alan, s];
};

const BAGLAM = [
  ["govde", (x) => `<not>${x}</not>`, govde, (k) => {
    const son = k.indexOf("<", 5);          // alan, bir sonraki kap imgesine kadar okunur
    return k.startsWith("<not>") && k.slice(son) === "</not>" ? coz(k.slice(5, son)) : null;
  }],
  ["oznitelik", (x) => `<kayit baslik="${x}"/>`, oznitelik,
    (k) => { const m = /baslik="([^"]*)"/.exec(k); return m ? coz(m[1]) : null; }],
  ["ayrilmis", (x) => `U-10432,${x},2026-03-04`, ayrilmis,
    (k) => { const a = ayir(k); return a.length === 3 ? a[1] : null; }],
];

const dene = (kodlayici) => {
  const liste = [];
  for (const [ad, kap, dogru, oku] of BAGLAM)
    DEGERLER.forEach((d, i) => {
      if (oku(kap(kodlayici(d, dogru))) !== d) liste.push(`${ad}/${i + 1}`);
    });
  return liste;
};

console.log(`${DEGERLER.length} deger x ${BAGLAM.length} baglam = ${DEGERLER.length * BAGLAM.length} yerlestirme\n`);
console.log(`${"kodlayici".padEnd(24)}${"geri okunamayan".padStart(16)}  yer`);
for (const [ad, f] of [["kodlama yok", (d) => d], ["tek kodlayici (govde)", govde],
    ["baglama gore", (d, dogru) => dogru(d)]]) {
  const l = dene(f);
  console.log(`${ad.padEnd(24)}${String(l.length).padStart(16)}  ${l.join(" ") || "-"}`);
}
```

```
6 deger x 3 baglam = 18 yerlestirme

kodlayici                geri okunamayan  yer
kodlama yok                            7  govde/2 oznitelik/3 oznitelik/6 ayrilmis/3 ayrilmis/4 ayrilmis/5 ayrilmis/6
tek kodlayici (govde)                  7  oznitelik/3 oznitelik/6 ayrilmis/2 ayrilmis/3 ayrilmis/4 ayrilmis/5 ayrilmis/6
baglama gore                           0  -
```

Tek kodlayıcı hiçbir şey kazandırmadı: on sekiz yerleştirmenin yedisi hâlâ geri okunamıyor
ve **liste değişti**. Gövde bağlamı düzeldi, ayrılmış değer bağlamında ikinci değer bozuldu
— nottaki küçüktür imgesi kaçış dizisine çevrildiği için dışa aktarım satırında ham metin
olarak duruyor. Üçüncü sessiz sonuç budur: dosya açılır, satırlar okunur ve içindeki not
artık yazılan not değildir.

Doğru gerçekleştirim kodlamayı **veri girerken değil, yazarken** yapar; girişte kodlanan bir
değer ikinci bağlama gittiğinde iki kez kodlanır.

#### Özet

- Aynı yirmi iki istekte izin listesi 0 yanlış geçme ve 1 yanlış kalma, engelleme listesi
  11 yanlış geçme ve 4 yanlış kalma verdi. Engelleme listesinin geçirdiklerinde zararlı imge
  yoktu; yanlış biçim, yanlış tip ve fazladan alan vardı.
- İzin listesinin bedeli tek yanlış kalmadadır: tireli yazılmış meşru bir tanımlayıcı
  reddedildi, bir ödünç işlemi kaydedilmedi.
- Doğrulama uçta durduğunda 0, serviste 11, depoda 22 adım boşa çalıştı; durdurulan istek
  sayısı üçünde de aynıydı. Fark yalnız maliyettedir.
- Tek kodlayıcı üç bağlama yetmedi: 18 yerleştirmenin 7'si geri okunamadı ve bir bağlam
  düzelirken başkası bozuldu. Kodlama her çıktı bağlamında ayrı yapılır.
- Üç yanlış ayar da sessizdir, toplam fırlatılan hata sıfırdır: `bilinmeyenAlan` ilkesi
  2 artık alanı depoya indirdi, kaydı unutulan bir uç 2 sözleşme dışı kaydı yazdırdı, yanlış
  bağlamdaki kodlayıcı bir satırı bozdu. Doğrulayıcının kaydı uç başına yapılırsa 3 noktada
  tekrarlanır, tek boru hattında 1 noktada durur.

#### Sonraki Adım

Doğrulama sınırı gövdenin biçimini garanti eder. Garanti etmediği şey o değerin sistemin
içinde **nasıl kullanıldığıdır**. Sözleşmeye tamamen uyan bir arama metni, bir sıralama
alanı ya da bir bildirim şablonu; bir sorgu metnine, bir komut satırına veya bir şablon
gövdesine dizge birleştirmesiyle konduğunda artık veri değil, o dilin bir parçası olur.
Sonraki ders bu üç yeri aynı kökten gösterir ve tek kararı ölçer: değer metne
birleştirildiğinde ile ayrı bir işlenen olarak geçirildiğinde aynı istek kümesi kaç farklı
sonuç üretiyor.

### Enjeksiyon Sınıfı Açıklar

Source: https://academia.sh/tr/kurslar/backend-uretim/enjeksiyon-sinifi-aciklar

Sorgu, komut ve şablon enjeksiyonunun tek bir kökten ölçülmesi: on meşru arama girdisinin birleştirilmiş sorguda kaç beklenmeyen sonuç ürettiği, altı rapor etiketinin tek dizge olarak geçirildiğinde kaçının bozulduğu, beş not metninin şablon gövdesine yazıldığında kaçının değiştiği ve on iki çağrı yerinin kaçının birleştirme kullandığı.

Önceki ders sınırın biçimini kurdu: gövdenin alanları, tipleri ve aralıkları sözleşmeye
uyuyor mu. Sözleşmeyi geçen değer artık **doğru biçimdedir**, ama hâlâ bir metindir ve
sistemin içinde bir yere yazılacaktır. Bu ders o yazma anını ölçer.

Üç ayrı sınıf sayılır — sorgu, komut ve şablon — ve üçü de aynı karara iner: **değer, o
bağlamın metnine birleştirildi mi, yoksa ayrı bir işlenen olarak mı geçirildi?** Ölçüm
boyunca hiçbir kötü niyetli girdi kullanılmaz. Girdilerin hepsi kütüphane kataloğunda
gerçekten geçen metinlerdir: kesme işaretli yazar adları, süslü parantez içeren görevli
notları, boşluklu rapor etiketleri. Ölçülen şey **kaç meşru girdinin beklenmeyen sonuç
ürettiğidir**.

**BG4 (varsayım):** iki kurulusun aynı davranması beklenir. Ayrı işlenen yolu ölçüt kabul
edilir; birleşik yolun ondan farklı her sonucu — hata da, farklı satır da — beklenmeyen
sonuç sayılır.

#### Sorgu Bağlamı

Katalog arama ucu, ödünç sisteminin en çok çağrılan yeridir. Aynı arama iki kurulusla
yazılır ve on meşru girdiye ayrı ayrı sorulur.

```js
// veri.mjs — odunc katalogu ve gercek kataloglarda gecen mesru arama girdileri
import { DatabaseSync } from "node:sqlite";

export const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE kitap (isbn TEXT PRIMARY KEY, baslik TEXT, yazar TEXT, yil INTEGER)`);
const ekle = db.prepare(`INSERT INTO kitap VALUES (?, ?, ?, ?)`);
for (const s of [
  ["9789750718533", "Kayip Zaman", "M. Proust", 1913],
  ["9786050000000", "Rock 'n' Roll Tarihi", "J. O'Neill", 1998],
  ["9789750000001", "100% Pamuk", "A. Saenz", 2004],
  ["9789750000002", "L'Etranger", "A. Camus", 1942],
  ["9789750000003", "Kar_ Deseni", "H. Yilmaz", 2011],
  ["9789750000004", "Tarih -- 20. Yuzyil", "S. Aydin", 2019],
  ["9789750000005", 'Deneme "iki"', "N. Kaya", 2020],
  ["9789750000006", "Kalem ve Kagit", "B. Demir", 1995],
]) ekle.run(...s);

// Hicbiri saldiri degil; hepsi kutuphane katalogunda gecen mesru arama metinleri.
export const ARAMA = ["Kalem", "O'Neill", "100%", "Kar_", "--", 'iki"',
  "Rock 'n' Roll", "Camus", "L'Etranger", "Kaya'nin"];

// Siralama alani: sozlesme bir alan adi bekler.
export const SIRA = ["baslik", "yil", "yazar", "yil desc", "kitap.ic_not"];
```

```js
// sorgu.mjs — ayni arama, iki kurulus: ayri islenen ve dizge birlestirme
import { db, ARAMA, SIRA } from "./veri.mjs";

const dene = (f) => { try { return { ok: 1, v: f() }; } catch (e) { return { ok: 0, v: e.message.slice(0, 34) }; } };
const islenen = (q) => db.prepare(`SELECT isbn FROM kitap WHERE baslik LIKE ? OR yazar LIKE ?`)
  .all(`%${q}%`, `%${q}%`).map((r) => r.isbn);
const birlesik = (q) => db.prepare(
  `SELECT isbn FROM kitap WHERE baslik LIKE '%${q}%' OR yazar LIKE '%${q}%'`).all().map((r) => r.isbn);

console.log(`${ARAMA.length} mesru arama girdisi, iki kurulus\n`);
console.log(`${"girdi".padEnd(16)}${"islenen".padStart(9)}${"birlesik".padStart(10)}  fark`);
let sapan = 0, hatali = 0;
for (const q of ARAMA) {
  const a = dene(() => islenen(q)), b = dene(() => birlesik(q));
  const ayni = a.ok && b.ok && String(a.v) === String(b.v);
  if (!ayni) sapan++;
  if (!b.ok) hatali++;
  console.log(JSON.stringify(q).padEnd(16) + (a.ok ? String(a.v.length) : "hata").padStart(9) +
    (b.ok ? String(b.v.length) : "hata").padStart(10) + "  " + (ayni ? "-" : b.ok ? "farkli satir" : b.v));
}
console.log(`\nbirlesik kurulusta beklenmeyen sonuc: ${sapan}/${ARAMA.length} (${hatali} tanesi hata)`);

// Alan adi islenen olarak gecirilemez; burada birlestirme kacinilmazdir.
const IZIN = { baslik: "baslik", yil: "yil", yazar: "yazar" };
const sirala = (alan, izinli) => {
  if (izinli && !IZIN[alan]) return "ret";
  const s = izinli ? IZIN[alan] : alan;
  return dene(() => db.prepare(`SELECT isbn FROM kitap ORDER BY ${s}`).all()[0].isbn);
};
console.log(`\n${"siralama girdisi".padEnd(18)}${"birlestirme".padStart(18)}${"izin listesi".padStart(18)}`);
for (const alan of SIRA) {
  const b = sirala(alan, false), i = sirala(alan, true);
  const yaz = (r) => (r === "ret" ? "ret" : r.ok ? r.v : "hata");
  console.log(alan.padEnd(18) + yaz(b).padStart(18) + yaz(i).padStart(18));
}
```

```
10 mesru arama girdisi, iki kurulus

girdi             islenen  birlesik  fark
"Kalem"                 1         1  -
"O'Neill"               1      hata  near "Neill": syntax error
"100%"                  1         1  -
"Kar_"                  1         1  -
"--"                    1         1  -
"iki\""                 1         1  -
"Rock 'n' Roll"         1      hata  near "n": syntax error
"Camus"                 1         1  -
"L'Etranger"            1      hata  near "Etranger": syntax error
"Kaya'nin"              0      hata  near "nin": syntax error

birlesik kurulusta beklenmeyen sonuc: 4/10 (4 tanesi hata)

siralama girdisi         birlestirme      izin listesi
baslik                 9789750000001     9789750000001
yil                    9789750718533     9789750718533
yazar                  9789750000002     9789750000002
yil desc               9789750000005               ret
kitap.ic_not                    hata               ret
```

Dört girdi sapıyor ve dördünün de ortak özelliği bir kesme işareti taşımasıdır. Bu dört
okuyucunun araması **çalışmıyor** — ödünç sistemi bir yazar adını arayamıyor. Enjeksiyon
sınıfının en sık görülen üretim etkisi budur: açık değil, kırık işlev.

İkinci tablo asıl noktadır. Sıralama alanı bir işlenen olarak geçirilemez, çünkü sorgu
metninin kendisinin bir parçasıdır; orada birleştirme kaçınılmazdır. `yil desc` girdisi
birleştirmede **hata vermeden** kabul ediliyor ve farklı bir satır dönüyor. `kitap.ic_not`
hata veriyor. İzin listesi ikisini de reddediyor. **Birleştirmenin zorunlu olduğu tek yer,
sapmanın sessiz olduğu tek yerdir** — ve orada tek çare önceki dersin izin listesidir.

#### Komut Bağlamı

Rapor servisi, dışa aktarma işini ayrı bir sürece veriyor. Etiket iki yolla geçirilebilir:
ayrı bir argüman olarak ya da komut satırına birleştirilmiş bir dizge içinde.

```js
// yankila.mjs — rapor sureci: aldigi argumanlari oldugu gibi basar
console.log(JSON.stringify(process.argv.slice(2)));
```

```js
// komut.mjs — rapor etiketi alt surece iki yolla gecirilir: ayri arguman ve tek dizge
import { execFileSync, execSync } from "node:child_process";

// Hepsi kutuphane gorevlisinin yazdigi mesru rapor etiketleri.
const ETIKET = ["2026-03", "gunluk rapor", "bolum $raf", "O'Neill listesi", "ikinci  el", "kayip-bulunan"];

const oku = (f) => { try { return JSON.parse(f()); } catch { return null; } };
const islenen = (e) => oku(() => execFileSync("node", ["yankila.mjs", e], { encoding: "utf8" }));
const birlesik = (e) => oku(() => execSync(`node yankila.mjs ${e}`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }));

console.log(`${ETIKET.length} mesru rapor etiketi, iki gecirme yolu\n`);
console.log(`${"etiket".padEnd(19)}${"ayri arguman".padStart(22)}${"tek dizge".padStart(26)}`);
let bozuk = 0;
for (const e of ETIKET) {
  const a = islenen(e), b = birlesik(e);
  const ayni = b && b.length === 1 && b[0] === e;
  if (!ayni) bozuk++;
  console.log(e.padEnd(19) + JSON.stringify(a).padStart(22) + (b ? JSON.stringify(b) : "surec hata").padStart(26));
}
console.log(`\nayri arguman: 0/${ETIKET.length} bozuldu, tek dizge: ${bozuk}/${ETIKET.length} bozuldu`);
```

```
6 mesru rapor etiketi, iki gecirme yolu

etiket                       ayri arguman                 tek dizge
2026-03                       ["2026-03"]               ["2026-03"]
gunluk rapor             ["gunluk rapor"]        ["gunluk","rapor"]
bolum $raf                 ["bolum $raf"]                 ["bolum"]
O'Neill listesi       ["O'Neill listesi"]                surec hata
ikinci  el                 ["ikinci  el"]           ["ikinci","el"]
kayip-bulunan           ["kayip-bulunan"]         ["kayip-bulunan"]

ayri arguman: 0/6 bozuldu, tek dizge: 4/6 bozuldu
```

Aynı kök, farklı dil. Boşluklu etiket iki argümana bölünüyor, çift boşluk teke iniyor,
kesme işaretli etiket süreci hiç başlatmıyor. Üçüncü satır bu bölümün sessiz vakasıdır:
`bolum $raf` etiketi hatasız çalışıyor ve alt sürece **`bolum` olarak** varıyor. Rapor
üretilir, dosya yazılır, kimse bir şey fark etmez; yalnız etiketin yarısı yoktur.

#### Şablon Bağlamı

Bildirim metni bir şablondan üretiliyor. Görevlinin yazdığı not ya şablona **veri olarak**
verilir ya da şablonun gövdesine yazılıp sonra işlenir.

```js
// sablon.mjs — bildirim metni iki yolla kurulur: veri islenen olarak, veri sablonun icine
const SABLON = "Sayin {ad}, {isbn} son gunu {gun}. Not: {not}";
const YERLESTIR = (metin, veri) => metin.replace(/\{(\w+)\}/g, (_, k) => veri[k] ?? "");

// Hepsi gorevlinin elle yazdigi mesru not metinleri.
const NOTLAR = ["kapak yipranmis", "iade tarihi {gun} olarak yazilmis", "raf {yer} bulunamadi",
  "ucret 5 TL", "{ad} adina kayitli"];
const TEMEL = { ad: "A. Kaya", isbn: "9789750718533", gun: "2026-03-25" };

// Ayri islenen: sablon sabittir, veri bir kez yerine konur ve bir daha taranmaz.
const islenen = (not) => YERLESTIR(SABLON, { ...TEMEL, not });
// Birlestirme: veri once sablonun govdesine yazilir, sonra govde islenir.
const birlesik = (not) => YERLESTIR(SABLON.replace("{not}", not), { ...TEMEL, not });

const notAlani = (m) => m.split("Not: ")[1];
console.log(`${NOTLAR.length} mesru not metni, iki kurulus\n`);
console.log(`${"girilen not".padEnd(36)}${"birlesik kurulusun bastigi not".padEnd(40)}fark`);
let bozuk = 0;
for (const not of NOTLAR) {
  const b = birlesik(not), ayni = b === islenen(not);
  if (!ayni) bozuk++;
  console.log(not.padEnd(36) + notAlani(b).padEnd(40) + (ayni ? "-" : "degisti"));
}
console.log(`\nnot alani girildigi gibi cikmayan bildirim: birlesik ${bozuk}/${NOTLAR.length}, islenen 0/${NOTLAR.length}`);
```

```
5 mesru not metni, iki kurulus

girilen not                         birlesik kurulusun bastigi not          fark
kapak yipranmis                     kapak yipranmis                         -
iade tarihi {gun} olarak yazilmis   iade tarihi 2026-03-25 olarak yazilmis  degisti
raf {yer} bulunamadi                raf  bulunamadi                         degisti
ucret 5 TL                          ucret 5 TL                              -
{ad} adina kayitli                  A. Kaya adina kayitli                   degisti

not alani girildigi gibi cikmayan bildirim: birlesik 3/5, islenen 0/5
```

Beş nottan üçü, okuyucuya giden bildirimde başka bir metne dönüştü. Hiçbiri hata vermedi.
Bir tanesi bilinmeyen bir anahtar taşıdığı için **boşaldı**; kayıt duruyor, bildirimde yok.
Ayrı işlenen yolunda şablon sabittir ve veri bir kez yerine konup bir daha taranmaz — bu
yüzden beşi de girildiği gibi çıkıyor.

#### Birleştirmenin Sayımı

Karar tek bir yerde verilmez; her çağrı yerinde ayrı ayrı verilir. Bölünmüş sistemin
çağrı yerleri sayılıyor.

```js
// cagri.mjs — bolunmus sistemdeki cagri yerlerinin sayimi ve tek unutulmus yerin olcusu
import { db, ARAMA } from "./veri.mjs";

// [servis, cagri yeri, baglam, kurulus]
const CAGRI = [
  ["katalog", "ara", "sorgu", "islenen"],
  ["katalog", "sirala", "sorgu", "birlestirme + izin listesi"],
  ["katalog", "isbnGetir", "sorgu", "islenen"],
  ["odunc", "ver", "sorgu", "islenen"],
  ["odunc", "gecikmis", "sorgu", "islenen"],
  ["odunc", "arsivAra", "sorgu", "birlestirme"],          // gozden kacan tek yer
  ["uye", "notEkle", "sorgu", "islenen"],
  ["uye", "gecmis", "sorgu", "islenen"],
  ["bildirim", "bicimle", "sablon", "islenen"],
  ["bildirim", "topluGonder", "sablon", "islenen"],
  ["rapor", "disaAktar", "komut", "islenen"],
  ["rapor", "arsivle", "komut", "islenen"],
];

const say = (k) => CAGRI.filter(k).length;
console.log(`${CAGRI.length} cagri yeri, ${new Set(CAGRI.map((c) => c[0])).size} servis`);
for (const b of ["sorgu", "komut", "sablon"])
  console.log(`  ${b.padEnd(7)} ${say((c) => c[2] === b)} yer, ` +
    `${say((c) => c[2] === b && c[3].startsWith("birlestirme"))} tanesi birlestirme`);
console.log(`  korumasiz birlestirme: ${say((c) => c[3] === "birlestirme")} ` +
  `(${CAGRI.filter((c) => c[3] === "birlestirme").map((c) => c[0] + "." + c[1]).join(", ")})`);

// Gozden kacan yer gercekten kosuluyor: ayni arama girdileri, tek fark kurulus.
const arsivAra = (q) => db.prepare(`SELECT isbn FROM kitap WHERE baslik LIKE '%${q}%'`).all();
let sapan = 0;
const sapanlar = [];
for (const q of ARAMA) {
  try { arsivAra(q); } catch { sapan++; sapanlar.push(q); }
}
console.log(`\ntek unutulmus yerde ${ARAMA.length} girdiden ${sapan} tanesi beklenmeyen sonuc verdi`);
console.log(`sapanlarin ortak ozelligi: hepsinde kesme isareti var -> ${sapanlar.every((q) => q.includes("'"))}`);
console.log(`kesme isareti tasimayan ${ARAMA.filter((q) => !q.includes("'")).length} girdi ile kurulan bir sinama kumesi bu yeri hic gormez`);
```

```
12 cagri yeri, 5 servis
  sorgu   8 yer, 2 tanesi birlestirme
  komut   2 yer, 0 tanesi birlestirme
  sablon  2 yer, 0 tanesi birlestirme
  korumasiz birlestirme: 1 (odunc.arsivAra)

tek unutulmus yerde 10 girdiden 4 tanesi beklenmeyen sonuc verdi
sapanlarin ortak ozelligi: hepsinde kesme isareti var -> true
kesme isareti tasimayan 6 girdi ile kurulan bir sinama kumesi bu yeri hic gormez
```

On iki çağrı yerinden ikisi birleştirme kullanıyor; biri sıralama alanıdır ve izin listesiyle
korunmuştur, biri gözden kaçmıştır. Sistem çalışıyor, bütün uçlar yanıt veriyor, hiçbir
sınama kırmızı değil. Son satır nedenini söylüyor: sapma yalnız kesme işareti taşıyan
girdilerde ortaya çıkıyor ve sade adlarla kurulmuş bir sınama kümesi o yeri hiç görmez.

**Ayarın yeri ve tekrarı:** bu karar bir yapılandırma dosyasında durmaz; on iki çağrı
yerinin her birinde ayrı ayrı verilir. Tek yerden değiştirilebilir hâle gelmesi için sorgu,
komut ve şablon çağrılarının tek bir sarmalayıcıdan geçmesi gerekir — o zaman karar 12
noktadan 3 noktaya iner.

#### Özet

- Üç sınıf tek bir karara iniyor: değer bağlamın metnine mi birleştirildi, yoksa ayrı bir
  işlenen olarak mı geçirildi. Ölçüm boyunca hiçbir kötü niyetli girdi kullanılmadı.
- On meşru arama girdisinin 4'ü birleştirilmiş sorguda beklenmeyen sonuç verdi; dördü de
  kesme işareti taşıyordu ve dördünde de sonuç bir hataydı — açık değil, kırık arama.
- Sıralama alanı işlenen olarak geçirilemez; birleştirmenin zorunlu olduğu bu tek yerde
  sapma sessizdir: `yil desc` hatasız kabul edildi ve farklı bir satır döndü. İzin listesi
  reddetti.
- Altı rapor etiketinin 4'ü tek dizge olarak geçirildiğinde bozuldu; `bolum $raf` hatasız
  çalışıp alt sürece yarım vardı. Beş nottan 3'ü şablon gövdesine yazıldığında değişti,
  biri boşaldı. Ayrı işlenen yolunda üç bağlamda da bozulma sıfır.
- On iki çağrı yerinden 2'si birleştirme kullanıyor; biri izin listesiyle korunmuş, biri
  gözden kaçmış. Kaçan yer 10 girdiden 4'ünde sapıyor, kalan 6 girdiyle kurulmuş bir sınama
  kümesi onu hiç görmüyor. Karar 12 noktada tekrarlanıyor.

#### Sonraki Adım

Buraya kadarki iki ders isteğin **gövdesine** baktı: alanlar doğru biçimde mi, değerler
metne mi birleştiriliyor. İkisi de isteğin nereden geldiğini sormadı. Oysa ödünç sisteminin
uçlarına yalnız kendi arayüzü değil, tarayıcıda açık başka sayfalar da istek gönderebilir ve
bu isteklerin bir kısmı okuyucunun oturum bilgisini yanında taşır. Sonraki ders sunucunun bu
konudaki tek sözünü — hangi kökenden gelen isteğe ne kadar izin verdiğini — ölçer: ayarın
hangi katmanda durduğunu, joker bir değerin kaç kökene kapı açtığını ve kimlik bilgisi
taşıyan bir istekte yanlış ayarın neden hata vermeden sessizce başarısız olduğunu.

### Kaynaklar Arası Erişim Denetimi

Source: https://academia.sh/tr/kurslar/backend-uretim/kaynaklar-arasi-erisim-denetimi

Kaynaklar arası paylaşımın sunucu tarafındaki yapılandırması: yedi kökenin dört ayar altında kaç tanesinin yanıtı kullanabildiği, joker ayarın açtığı köken sayısı, kimlik bilgisi taşıyan istekte joker ayarın sunucuda 200 döndürüp istemcide sessizce atılması, ön denetim isteğinin gidiş-dönüş maliyeti ve politikanın kaç uçta kayıtlı olduğu.

Önceki iki ders isteğin gövdesine baktı. Bu ders isteğin **nereden geldiğine** bakıyor ve
sunucunun bu konuda söyleyebileceği tek şeyi ölçüyor: yanıtın başlıkları.

Kaynaklar arası paylaşımın istemci tarafı — tarayıcının hangi isteği neden engellediği,
politikanın sayfa üzerindeki etkisi — Frontend Kalitesi kursunda kuruldu ve burada
tekrarlanmaz. Bu derste ölçülen şey **sunucu tarafındaki yapılandırmadır**: ayar hangi
katmanda duruyor, joker bir değer kaç kökene kapı açıyor, kimlik bilgisi taşıyan bir istekte
yanlış ayar ne yapıyor ve ön denetim isteği kaça mal oluyor.

Sunucunun yaptığı iş küçüktür: gelen kökene bakar, birkaç yanıt başlığı ekler ya da eklemez.
Kararı **istemci** verir. Bu ayrım, dersin bütün ölçümlerinin dayandığı noktadır.

**BG5 (varsayım):** istemcinin kuralı bu derste kendi yazıldı. Sunucu ayarının doğruluğu,
o kurala göre yanıtın kullanılıp kullanılmadığıyla ölçülüyor; ölçüm gerçek bir istemcinin
değil, kuralın gerçekleştiriminin sonucudur.

#### Sunucunun Tek Sözü

```js
// politika.mjs — sunucunun kaynaklar arasi paylasim konusundaki tek sozu: yanit basliklari
export const AYAR = {
  kokenler: ["https://odunc.kutuphane", "https://panel.kutuphane"],   // "*" joker demektir
  yontemler: ["GET", "POST"],
  basliklar: ["content-type", "x-odunc-izlem"],
  kimlikBilgisi: false,
  onDenetimOmru: 600,
};

export function basliklar(istek, ayar) {
  const c = {};
  if (!istek.koken) return c;                       // ayni kokenli istek; soz gerekmez
  const joker = ayar.kokenler === "*";
  if (!joker && !ayar.kokenler.includes(istek.koken)) return c;   // suskunluk = ret
  c["access-control-allow-origin"] = joker ? "*" : istek.koken;
  if (ayar.kimlikBilgisi) c["access-control-allow-credentials"] = "true";
  if (!joker) c["vary"] = "origin";                 // yanit kokene gore degisiyor
  if (istek.yontem === "OPTIONS") {
    c["access-control-allow-methods"] = ayar.yontemler.join(",");
    c["access-control-allow-headers"] = ayar.basliklar.join(",");
    c["access-control-max-age"] = String(ayar.onDenetimOmru);
  }
  return c;
}
```

```js
// istemci.mjs — uyumlu bir istemcinin yanit basliklarina bakarak verdigi karar
// Kural burada kendi yazildi; sunucu ayarinin dogrulugu bu karara gore olculuyor.
export function karar(istek, b) {
  if (!istek.koken) return { kullanildi: true, neden: "ayni koken" };
  const izin = b["access-control-allow-origin"];
  if (!izin) return { kullanildi: false, neden: "izin basligi yok" };
  if (izin !== "*" && izin !== istek.koken) return { kullanildi: false, neden: "koken uyusmuyor" };
  if (istek.kimlikBilgisi) {
    if (izin === "*") return { kullanildi: false, neden: "joker + kimlik bilgisi" };
    if (b["access-control-allow-credentials"] !== "true")
      return { kullanildi: false, neden: "kimlik bilgisi izni yok" };
  }
  return { kullanildi: true, neden: "" };
}
```

Politika katmanının reddi bir hata değil, **suskunluktur**: tanımadığı bir kökene hiçbir
başlık eklemez. Sunucu yine 200 döner, gövde yine yazılır. Bu tasarım kararı dersin geri
kalanındaki bütün sessiz sonuçların kaynağıdır.

#### Joker Ayarın Açtığı Köken

Yedi köken alınıyor: ödünç sisteminin kendi arayüzü, iç panel, aynı adın şema ve kapı
farkları, bir alt ad, ilgisiz bir köken ve köken başlığı hiç taşımayan aynı kökenli istek.

```js
// koken.mjs — yedi koken, dort ayar; sunucunun dondugu ile istemcinin kullandigi
import { AYAR, basliklar } from "./politika.mjs";
import { karar } from "./istemci.mjs";

const KOKENLER = [
  null,                                // ayni koken (baslik yok)
  "https://odunc.kutuphane",           // kendi arayuzu
  "https://panel.kutuphane",           // ic panel
  "http://odunc.kutuphane",            // sema farkli
  "https://odunc.kutuphane:8443",      // kapi farkli
  "https://alt.odunc.kutuphane",       // alt ad
  "https://baska.site",                // ilgisiz
];

const AYARLAR = [
  ["liste, kimliksiz", { ...AYAR }],
  ["joker, kimliksiz", { ...AYAR, kokenler: "*" }],
  ["joker, kimlikli", { ...AYAR, kokenler: "*", kimlikBilgisi: true }],
  ["liste, kimlikli", { ...AYAR, kimlikBilgisi: true }],
];

const gec = (ayar, koken, kimlikBilgisi) => {
  const istek = { koken, yontem: "GET", kimlikBilgisi };
  return karar(istek, basliklar(istek, ayar)).kullanildi;
};

console.log(`${KOKENLER.length} koken (1 ayni koken + ${KOKENLER.length - 1} dis koken), ${AYARLAR.length} ayar`);
console.log(`\n${"ayar".padEnd(19)}${"sunucu 2xx".padStart(11)}${"istemci kullandi: kimliksiz".padStart(28)}${"kimlikli".padStart(10)}`);
for (const [ad, ayar] of AYARLAR) {
  const k0 = KOKENLER.filter((k) => gec(ayar, k, false)).length;
  const k1 = KOKENLER.filter((k) => gec(ayar, k, true)).length;
  console.log(ad.padEnd(19) + String(KOKENLER.length).padStart(11) + String(k0).padStart(28) + String(k1).padStart(10));
}

const jokerAcik = KOKENLER.filter((k) => k && gec({ ...AYAR, kokenler: "*" }, k, false));
const listeAcik = KOKENLER.filter((k) => k && gec(AYAR, k, false));
console.log(`\nliste ayarinin actigi dis koken: ${listeAcik.length} (${listeAcik.join(", ")})`);
console.log(`joker ayarinin bu kumede actigi dis koken: ${jokerAcik.length}; kume disinda sinir yok`);

console.log(`\n'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler:`);
for (const k of KOKENLER.slice(1, 4)) {
  const istek = { koken: k, yontem: "GET", kimlikBilgisi: true };
  const b = basliklar(istek, { ...AYAR, kokenler: "*", kimlikBilgisi: true });
  const c = karar(istek, b);
  console.log(`  ${k.padEnd(30)} sunucu 200 + ${Object.keys(b).length} baslik -> istemci: ${c.kullanildi ? "kullandi" : "atti (" + c.neden + ")"}`);
}
```

```
7 koken (1 ayni koken + 6 dis koken), 4 ayar

ayar                sunucu 2xx istemci kullandi: kimliksiz  kimlikli
liste, kimliksiz             7                           3         1
joker, kimliksiz             7                           7         1
joker, kimlikli              7                           7         1
liste, kimlikli              7                           3         3

liste ayarinin actigi dis koken: 2 (https://odunc.kutuphane, https://panel.kutuphane)
joker ayarinin bu kumede actigi dis koken: 6; kume disinda sinir yok

'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler:
  https://odunc.kutuphane        sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
  https://panel.kutuphane        sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
  http://odunc.kutuphane         sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
```

`sunucu 2xx` sütunu dört ayarda da yedidir. Sunucu hiçbir isteği reddetmiyor; ayar yalnız
istemcinin yanıtı kullanıp kullanmayacağını belirliyor. Liste ayarı iki dış kökene açıktır;
joker ayarı bu kümedeki altısına ve **kümenin dışında sınır yoktur** — şema farkı, kapı
farkı ve alt ad dahil, adı hiç yazılmamış her köken içeri girer. Joker ile listenin farkı
bu satırda 2'ye karşı 6'dır; gerçekte 2'ye karşı sınırsızdır.

Üçüncü satır bu dersin sessiz ayarıdır. `kimlikBilgisi` açılmış, sunucu iki başlık birden
gönderiyor, istek 200 dönüyor — ve istemci yanıtı **atıyor**. `joker, kimlikli` satırı
`joker, kimliksiz` satırıyla birebir aynı: ayarı açmak hiçbir şeyi değiştirmedi. Bir kayıt
yığınında bu iki ayar ayırt edilemez; iki durumda da 200 vardır, hata yoktur, gecikme
yoktur. Yalnız iç panel çalışmaz ve nedeni sunucu tarafında görünmez.

Doğru ayar son satırdır: köken listesi **ve** kimlik bilgisi izni birlikte. Üç kimlikli
istek ancak orada kullanılıyor.

#### Ön Denetimin Maliyeti

Politika katmanı gerçek bir süreçte ölçülüyor. Özel bir başlık taşıyan istek, gönderilmeden
önce bir **ön denetim** isteği gerektirir; ön denetimin sonucu bir ömür boyunca saklanabilir.

```js
// sunucu.mjs — odunc uclarini dinleyen yerel surec; politika katmani uclarin onunde durur
import http from "node:http";
import { basliklar } from "./politika.mjs";

export const UCLAR = ["/katalog", "/odunc", "/uye", "/rapor", "/arsiv"];

export function baslat(ayar, kapsanan = UCLAR) {
  const sayac = { istek: 0, onDenetim: 0, bayt: 0 };
  const s = http.createServer((q, y) => {
    sayac.istek++;
    if (q.method === "OPTIONS") sayac.onDenetim++;
    const istek = { koken: q.headers.origin ?? null, yontem: q.method };
    const b = kapsanan.includes(q.url) ? basliklar(istek, ayar) : {};
    const govde = q.method === "OPTIONS" ? "" : JSON.stringify({ uc: q.url, durum: "ok" });
    sayac.bayt += govde.length +
      Object.entries(b).reduce((t, [k, v]) => t + k.length + v.length + 4, 0);
    y.writeHead(q.method === "OPTIONS" ? 204 : 200, { "content-type": "application/json", ...b });
    y.end(govde);
  });
  return new Promise((c) => s.listen(0, "127.0.0.1", () =>
    c({ port: s.address().port, sayac, kapat: () => s.close() })));
}
```

```js
// olcum.mjs — gercek istekler: on denetim maliyeti ve katman kapsaminin olcusu
import { baslat, UCLAR } from "./sunucu.mjs";
import { AYAR } from "./politika.mjs";
import { karar } from "./istemci.mjs";

const KOKEN = "https://panel.kutuphane";
const N = 6;

// Uyumlu istemci: ozel baslikli istek once on denetim ister, sonuc omru boyunca onbelleklenir.
async function gonder(port, uc, { ozel, onbellek, kimlikBilgisi = false }) {
  const kok = `http://127.0.0.1:${port}`;
  if (ozel && !(onbellek && onbellek.has(uc))) {
    const o = await fetch(kok + uc, { method: "OPTIONS", headers: { origin: KOKEN } });
    if (onbellek) onbellek.set(uc, Number(o.headers.get("access-control-max-age") ?? 0));
  }
  const basliklar = { origin: KOKEN, ...(ozel ? { "x-odunc-izlem": "1" } : {}) };
  const y = await fetch(kok + uc, { headers: basliklar });
  const b = Object.fromEntries(y.headers);
  return { durum: y.status, karar: karar({ koken: KOKEN, kimlikBilgisi }, b) };
}

const { port, sayac, kapat } = await baslat(AYAR);

const kosum = async (ad, secenek) => {
  const once = { ...sayac };
  for (let i = 0; i < N; i++) await gonder(port, "/katalog", secenek);
  console.log(`${ad.padEnd(28)}${String(sayac.istek - once.istek).padStart(8)}` +
    `${String(sayac.onDenetim - once.onDenetim).padStart(12)}${String(sayac.bayt - once.bayt).padStart(9)}`);
};

console.log(`${N} gercek istek, tek uc\n`);
console.log(`${"istek bicimi".padEnd(28)}${"gidis-donus".padStart(8)}${"on denetim".padStart(12)}${"bayt".padStart(9)}`);
await kosum("basit (on denetim gerekmez)", { ozel: false });
await kosum("ozel baslikli, onbelleksiz", { ozel: true });
await kosum("ozel baslikli, onbellekli", { ozel: true, onbellek: new Map() });
console.log(`on denetim omru: ${AYAR.onDenetimOmru} sn -> omur boyunca uc basina 1 on denetim`);
kapat();

// Katman kapsami: politika bes ucun dordune kaydedilmis.
const EKSIK = UCLAR.filter((u) => u !== "/arsiv");
const iki = await baslat({ ...AYAR, kimlikBilgisi: true }, EKSIK);
console.log(`\npolitika kaydi ${EKSIK.length}/${UCLAR.length} ucta\n`);
console.log(`${"uc".padEnd(10)}${"sunucu durumu".padStart(14)}${"istemci".padStart(9)}  neden`);
let atilan = 0;
for (const uc of UCLAR) {
  const r = await gonder(iki.port, uc, { ozel: false, kimlikBilgisi: true });
  if (!r.karar.kullanildi) atilan++;
  console.log(uc.padEnd(10) + String(r.durum).padStart(14) +
    (r.karar.kullanildi ? "kullandi" : "atti").padStart(9) + "  " + (r.karar.neden || "-"));
}
console.log(`\n${UCLAR.length} ucun ${atilan} tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0`);
iki.kapat();
```

```
6 gercek istek, tek uc

istek bicimi                gidis-donus  on denetim     bayt
basit (on denetim gerekmez)        6           0      588
ozel baslikli, onbelleksiz        12           6     1758
ozel baslikli, onbellekli          7           1      783
on denetim omru: 600 sn -> omur boyunca uc basina 1 on denetim

politika kaydi 4/5 ucta

uc         sunucu durumu  istemci  neden
/katalog             200 kullandi  -
/odunc               200 kullandi  -
/uye                 200 kullandi  -
/rapor               200 kullandi  -
/arsiv               200     atti  izin basligi yok

5 ucun 1 tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0
```

Ön denetimin maliyeti koşumdan bağımsız bir sayıdır: önbelleksiz altı özel istek **on iki**
gidiş-dönüş yapıyor, yani iki katı. Ömür ayarı devreye girdiğinde sayı yediye düşüyor —
uç başına bir ön denetim. Politika baytı da üçe katlanıyor: 588'e karşı 1758.

Bu maliyet yalnız özel başlıklı isteklere aittir. İlk satır bunu gösteriyor: özel başlık
kullanmayan altı istek hiç ön denetim üretmiyor. **Ön denetimden kaçınmanın yolu ayarı
gevşetmek değil, isteğin biçimini sadeleştirmektir.**

#### Ayarın Yeri

Son tablo katman sorusudur. Politika beş ucun dördünde kayıtlı; `/arsiv` ucu unutulmuş.
O uç istekleri **kabul ediyor**, gövdeyi yazıyor, 200 dönüyor. Yanıt istemcide atılıyor ve
sunucu tarafında bunun hiçbir izi yok: hata sayısı sıfır, durum kodu diğer dört uçla aynı.

Ayarın kaç yerde tekrarlandığı doğrudan bu riski belirliyor. Politika her uç işleyicisine
ayrı kaydediliyorsa beş kayıt noktası vardır ve altıncı uç eklendiğinde altıncı kayıt elle
yazılmalıdır. Politika uçların önünde tek bir katman olarak duruyorsa kayıt noktası birdir
ve yeni uç kendiliğinden kapsanır — `sunucu.mjs` içindeki `kapsanan` listesi tam da bu iki
seçenek arasındaki farktır.

#### Özet

- Sunucunun kaynaklar arası paylaşımdaki tek sözü yanıt başlıklarıdır; reddi bir hata değil
  suskunluktur. Dört ayarın dördünde de sunucu yedi isteğin yedisine 2xx döndü.
- Köken listesi 2 dış kökene açıktır; joker ayar ölçülen kümedeki 6 kökene ve küme dışında
  sınırsız sayıda kökene açıktır — şema farkı, kapı farkı ve alt ad dahil.
- Joker ayara kimlik bilgisi izni eklemek hiçbir şeyi değiştirmedi: `joker, kimlikli` satırı
  `joker, kimliksiz` ile birebir aynı çıktı. Sunucu iki başlık gönderdi, 200 döndü, istemci
  yanıtı attı. Doğru ayar köken listesi ile kimlik bilgisi iznini birlikte kullanır.
- Ön denetimin maliyeti koşumdan bağımsızdır: önbelleksiz 6 özel istek 12 gidiş-dönüş ve
  1758 bayt, ömür ayarıyla 7 gidiş-dönüş ve 783 bayt, özel başlıksız 6 istek 0 ön denetim.
- Politika 5 ucun 4'üne kaydedilince atlanan uçtan gelen yanıt istemcide atıldı ve sunucuda
  0 hata göründü. Uç başına kayıt 5 noktada tekrarlanır; uçların önündeki tek katman
  1 noktada durur ve yeni ucu kendiliğinden kapsar.

#### Sonraki Adım

Kaynaklar arası paylaşım ayarı bir yanıt başlığı kümesinden ibaretti ve ölçüm gösterdi ki
asıl soru başlığın **hangi katmanda** üretildiğidir. Aynı soru, yanıtın taşıdığı öteki
başlıklar için de geçerlidir: bağlantının nasıl kurulacağını, içeriğin nasıl
yorumlanacağını ve tarayıcının neyi yükleyebileceğini söyleyen başlıklar da aynı biçimde
uç uç dağılabilir ya da tek yerden konabilir. Sonraki ders bu başlıkları sayar: kaç uçta
tekrarlanıyorlar, tek katmana taşındığında kaç dosyaya dokunuluyor, eksik bir başlık hangi
davranışı sessizce açık bırakıyor ve şifreli bağlantının sonlandığı nokta bu resmin
neresinde duruyor.

### Güvenlik Başlıkları ve TLS

Source: https://academia.sh/tr/kurslar/backend-uretim/guvenlik-basliklari-ve-tls

Taşıma katmanı sertleştirmesinin sunucu tarafındaki ölçüsü: altı güvenlik başlığının on uçta kaç kayıt noktasında durduğu, tek katmana taşımanın dokunduğu on bir dosya, eksik başlık ile yazılmış ama etkisiz değerin hata üretmeden düşürdüğü yükümlülük sayısı ve aktarılan protokol başlığının üç ayar altında ürettiği yanlış geçme ile yanlış kalma.

Önceki ders sunucunun kaynaklar arası paylaşımdaki tek sözünün yanıt başlıkları olduğunu ve asıl
sorunun başlığın **hangi katmanda üretildiği** olduğunu gösterdi. Bu ders aynı soruyu yanıtın
taşıdığı öteki başlıklara taşıyor ve üç şey ölçüyor: bu başlıkların kaç uçta tekrarlandığı ve tek
katmana taşımanın kaç dosyaya dokunduğu; eksik ya da yanlış yazılmış bir başlığın hata üretmeden
ne düşürdüğü; uygulamanın "bu bağlantı şifreli" varsayımını nereden okuduğu ve o kaynak taklit
edilebildiğinde ne olduğu.

#### Başlığın Sözleşmesi

Bir güvenlik başlığı, sunucunun uyumlu bir istemciye verdiği tek cümlelik bir yükümlülüktür;
yazılmadığında ortada bir hata değil, bir yükümlülüğün yokluğu vardır. Ölçülen şey cümlenin
kendisidir: yazılmış mı, yazılmışsa değeri o cümleyi gerçekten kuruyor mu.

```js
// sertlestirme.mjs — baslik kumesi
const tanim = (deger, eksikse, kural) => ({ deger, eksikse, etkili: (v) => kural.test(v.trim()) });

export const KUME = {
  "strict-transport-security":
    tanim("max-age=31536000; includeSubDomains", "ilk istek sifresiz denenebilir", /max-age=\s*[1-9]\d*/),
  "content-security-policy":
    tanim("default-src 'self'; frame-ancestors 'none'", "kaynak ve cerceve kisiti yok", /default-src(?![^;]*\*)/),
  "x-content-type-options":
    tanim("nosniff", "icerik turu tahmin edilebilir", /^nosniff$/i),
  "referrer-policy":
    tanim("no-referrer", "adres ve sorgu dizesi disariya tasinir", /^(no-referrer|same-origin|strict-origin)$/i),
  "x-frame-options":
    tanim("DENY", "gomulme kisiti yok", /^(deny|sameorigin)$/i),
  "permissions-policy":
    tanim("geolocation=(), camera=()", "yetenek kisiti yok", /=\(\)/),
};

export function denetle(b) {
  const eksik = [], etkisiz = [];
  for (const [ad, k] of Object.entries(KUME)) {
    if (b[ad] === undefined) eksik.push(ad);
    else if (!k.etkili(b[ad])) etkisiz.push(ad);
  }
  return { eksik, etkisiz, saglam: Object.keys(KUME).length - eksik.length - etkisiz.length };
}
```

`etkili` alanı dersin ikinci ölçüsünü taşıyor: bir başlık yok, var ve etkili ya da **var ama
etkisiz** olabilir. Üçüncüsü ayrı sayılır, çünkü yanıta bakıldığında ikinciyle karışır — başlık
listede görünür, adı doğrudur, yalnız değeri hiçbir yükümlülük kurmaz.

**BG6 (varsayım):** istemcinin yükümlülüğü sözleşme olarak yazıldı, tarayıcı davranışı olarak
değil; web tarafı Frontend Kalitesi kursunda kuruldu. Ölçülen şey yanıtın kendisidir: `node:http`
ile okunan başlıklar.

#### Kaç Uçta Tekrarlanıyor

Ödünç sistemi dört servise bölünmüş ve dışarıya on uç veriyor. Başlıklar bugün her uç
işleyicisinin içinde ayrı ayrı yazılıyor.

```js
// sistem.mjs — uclar ve iki sapma
export const SERVIS = {
  katalog: ["ara", "kitap", "oneri"],
  odunc: ["al", "uzat", "iade"],
  uye: ["profil", "ceza"],
  rapor: ["gunluk", "aylik"],
};
export const UCLAR = Object.entries(SERVIS).flatMap(([s, us]) => us.map((u) => `/${s}/${u}`));

export const EKSIK = { "/rapor/aylik": 2 };   // son iki baslik yazilmamis
export const YANLIS = { "/odunc/uzat": ["x-content-type-options", "no-sniff"] };
```

```js
// dagilim.mjs — kayit noktasi ve tasima
import { mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from "node:fs";
import { KUME } from "./sertlestirme.mjs";
import { UCLAR, EKSIK, YANLIS } from "./sistem.mjs";

const D = new URL("./uc/", import.meta.url).pathname;
rmSync(D, { recursive: true, force: true });
mkdirSync(D);

// her uc kendi yaziyor
for (const uc of UCLAR) {
  const [yh, yv] = YANLIS[uc] ?? [];
  const satir = Object.entries(KUME)
    .slice(0, Object.keys(KUME).length - (EKSIK[uc] ?? 0))
    .map(([h, k]) => `  y.setHeader(${JSON.stringify(h)}, ${JSON.stringify(h === yh ? yv : k.deger)});`);
  writeFileSync(D + uc.slice(1).replace("/", "-") + ".mjs",
    `export function isle(q, y) {\n${satir.join("\n")}\n  y.end(JSON.stringify({ uc: ${JSON.stringify(uc)} }));\n}\n`);
}

const tara = () => readdirSync(D).map((f) => {
  const eslesme = [...readFileSync(D + f, "utf8").matchAll(/setHeader\("([^"]+)",\s"([^"]*)"\)/g)];
  return { f, kayit: eslesme.length, saglam: eslesme.filter(([, h, v]) => KUME[h]?.etkili(v)).length };
});

const once = tara();
const tam = Object.keys(KUME).length;
console.log(`${once.length} uc dosyasi, ${tam} baslik`);
console.log(`kayit noktasi: ${once.length}   kayit satiri: ${once.reduce((t, d) => t + d.kayit, 0)}`);
console.log(`kumesi tam ve etkili olan dosya: ${once.filter((d) => d.saglam === tam).length}/${once.length}`);
for (const d of once.filter((d) => d.saglam !== tam))
  console.log(`  ${d.f.padEnd(16)} kayit ${d.kayit}, etkili ${d.saglam}`);

// tasima: satirlar silinir, katman yazilir
let dokunulan = 0;
for (const { f } of once) {
  const eski = readFileSync(D + f, "utf8"), yeni = eski.replace(/^\s*y\.setHeader\(.*\n/gm, "");
  if (yeni !== eski) { writeFileSync(D + f, yeni); dokunulan++; }
}
const katman = Object.entries(KUME)
  .map(([h, k]) => `  y.setHeader(${JSON.stringify(h)}, ${JSON.stringify(k.deger)});`).join("\n");
writeFileSync(D + "katman.mjs", `export function sertlestir(q, y) {\n${katman}\n}\n`);
dokunulan++;

const sonra = tara().filter((d) => d.f !== "katman.mjs");
console.log(`\ntasimada dokunulan dosya: ${dokunulan} (${once.length} uc + 1 katman)`);
console.log(`tasima sonrasi kayit noktasi: 1   uclarda kalan kayit satiri: ${sonra.reduce((t, d) => t + d.kayit, 0)}`);
console.log(`yeni bir uc eklendiginde yazilacak baslik satiri: uc basina ${tam}, tek katmanda 0`);
```

```
10 uc dosyasi, 6 baslik
kayit noktasi: 10   kayit satiri: 58
kumesi tam ve etkili olan dosya: 8/10
  odunc-uzat.mjs   kayit 6, etkili 5
  rapor-aylik.mjs  kayit 4, etkili 4

tasimada dokunulan dosya: 11 (10 uc + 1 katman)
tasima sonrasi kayit noktasi: 1   uclarda kalan kayit satiri: 0
yeni bir uc eklendiginde yazilacak baslik satiri: uc basina 6, tek katmanda 0
```

On uç, on kayıt noktası. Altmış olması gereken kayıt satırı elli sekiz: `/rapor/aylik` iki
başlık geride kalmış. `/odunc/uzat` ucunda ise değer `no-sniff` yazılmış — başlık orada, adı
doğru, değeri tanımsız bir belirteç. On dosyanın sekizi tam. İki sapmanın da nedeni kayıt noktası
sayısıdır: altı başlık on ayrı yerde yazıldığında sapma için on fırsat vardır ve hiçbiri kendini
göstermez.

Taşımanın maliyeti tek seferliktir: **on bir dosya**. Sonrasında kayıt noktası bir, on birinci uç
eklendiğinde yazılacak başlık satırı sıfır; uç başına kayıtta ise her yeni uç altı satır ister.

#### Eksik Başlık ve Etkisiz Değer

Kaç yerde yazıldığı dosyalarda ölçüldü; yanıtta ne göründüğü ayrı bir sorudur ve gerçek bir
süreçten okunur.

```js
// olcum.mjs — yanit basliklari
import http from "node:http";
import { KUME, denetle } from "./sertlestirme.mjs";
import { UCLAR, EKSIK, YANLIS } from "./sistem.mjs";

const tam = () => Object.fromEntries(Object.entries(KUME).map(([h, k]) => [h, k.deger]));

const AYARLAR = [
  ["uc basina kayit", (uc) => {
    const b = tam(), [yh, yv] = YANLIS[uc] ?? [];
    for (const h of Object.keys(b).slice(Object.keys(b).length - (EKSIK[uc] ?? 0))) delete b[h];
    if (yh) b[yh] = yv;
    return b;
  }],
  ["tek katman", () => tam()],
  ["tek katman, yanlis deger", () => ({ ...tam(), "strict-transport-security": "max-age=0" })],
];

const baslat = (uret) => new Promise((c) => {
  const s = http.createServer((q, y) => {
    y.writeHead(200, { "content-type": "application/json", ...uret(q.url) });
    y.end(JSON.stringify({ uc: q.url }));
  });
  s.listen(0, "127.0.0.1", () => c(s));
});

const iste = (port, uc) => new Promise((c) =>
  http.get({ host: "127.0.0.1", port, path: uc }, (y) => { y.resume(); c(y); }));

console.log(`${UCLAR.length} uc, ${Object.keys(KUME).length} baslik, ayar basina ${UCLAR.length} istek\n`);
console.log(`${"ayar".padEnd(25)}${"200".padStart(5)}${"hata".padStart(6)}${"bayt".padStart(7)}` +
  `${"sozlesmesi tam uc".padStart(19)}${"eksik".padStart(7)}${"etkisiz".padStart(9)}`);

const ayrinti = [];
for (const [ad, uret] of AYARLAR) {
  const s = await baslat(uret);
  let [iki, bayt, saglamUc, eksik, etkisiz] = [0, 0, 0, 0, 0];
  for (const uc of UCLAR) {
    const y = await iste(s.address().port, uc);
    if (y.statusCode === 200) iki++;
    for (const h of Object.keys(KUME)) if (y.headers[h]) bayt += h.length + y.headers[h].length + 4;
    const d = denetle(y.headers);
    if (d.saglam === Object.keys(KUME).length) saglamUc++;
    else ayrinti.push([ad, uc, d]);
    eksik += d.eksik.length; etkisiz += d.etkisiz.length;
  }
  s.close();
  console.log(ad.padEnd(25) + String(iki).padStart(5) + "0".padStart(6) + String(bayt).padStart(7) +
    `${saglamUc}/${UCLAR.length}`.padStart(19) + String(eksik).padStart(7) + String(etkisiz).padStart(9));
}

console.log(`\nsozlesmesi eksilen uclar ve sessiz sonuc:`);
for (const [ad, uc, d] of ayrinti.slice(0, 4)) {
  const not = ["eksik", "etkisiz"].flatMap((t) => d[t].map((h) => `${t} ${h} -> ${KUME[h].eksikse}`));
  console.log(`  ${ad.padEnd(25)}${uc.padEnd(15)}${not.join("; ")}`);
}
console.log(`  ... "tek katman, yanlis deger" ayarinda ayni satir ${UCLAR.length} ucun ${UCLAR.length} tanesinde tekrarlaniyor`);
```

```
10 uc, 6 baslik, ayar basina 10 istek

ayar                       200  hata   bayt  sozlesmesi tam uc  eksik  etkisiz
uc basina kayit             10     0   2591               8/10      2        1
tek katman                  10     0   2660              10/10      0        0
tek katman, yanlis deger    10     0   2400               0/10      0       10

sozlesmesi eksilen uclar ve sessiz sonuc:
  uc basina kayit          /odunc/uzat    etkisiz x-content-type-options -> icerik turu tahmin edilebilir
  uc basina kayit          /rapor/aylik   eksik x-frame-options -> gomulme kisiti yok; eksik permissions-policy -> yetenek kisiti yok
  tek katman, yanlis deger /katalog/ara   etkisiz strict-transport-security -> ilk istek sifresiz denenebilir
  tek katman, yanlis deger /katalog/kitap etkisiz strict-transport-security -> ilk istek sifresiz denenebilir
  ... "tek katman, yanlis deger" ayarinda ayni satir 10 ucun 10 tanesinde tekrarlaniyor
```

Otuz isteğin otuzu 200 döndü; hata sayısı üç ayarda da sıfır. Tek nicel fark sertleştirme
baytındadır: 2591, 2660, 2400 — ve **en az bayt üreten ayar, sözleşmesi on uçtan sıfırında tam
olan ayardır.** Yanıt boyunu izleyen bir ölçüm bu üç ayarı doğruluk sırasına koyamaz.

`uc basina kayit` dosyalardaki sapmayı yanıta taşıyor: iki eksik yükümlülük, bir etkisiz değer,
sekiz sağlam uç. `tek katman` sapmayı sıfırlıyor. Üçüncü satır ise tek katmanın öbür yüzüdür:
başlık on ucun onunda var, adı doğru, biçimi geçerli, ama `max-age` sıfır olduğu için hiçbir
yükümlülük kurmuyor — tek yerdeki tek karakterlik fark sözleşmesi tam uç sayısını 10/10'dan
0/10'a düşürdü, durum kodu ve gövde değişmeden.

Bu, kayıt noktasını teke indirme kararını geri almaz; ölçünün nereye konacağını söyler. Tek
katman sapmayı bitirir ve değer riskini yoğunlaştırır; ikisini birden yakalayan tek şey
`denetle` gibi bir denetimin yapılandırmanın yanında değil **yanıtın üzerinde** çalışmasıdır.

#### Şifreli Bağlantı Varsayımı

Kümedeki ilk başlık ötekilerden bir yönüyle ayrılır: yalnız şifreli bir bağlantı üzerinde
anlamlıdır ve uygulama bağlantının şifreli olduğunu doğrudan göremez. TLS sonlandırma
uygulamanın kendisinde değil, önündeki ters vekilde yapılıyor; sonlandırmanın hangi katmanda
durduğu ve maliyeti yük dengeleme kursunda ölçüldü, burada tekrarlanmıyor. Uygulamaya kalan tek
bilgi vekilin aktardığı bir başlıktır.

**BG7 (varsayım):** yerel bağlantılar şifresizdir; vekil üzerinden gelen istek şifreli, doğrudan
ulaşan şifresiz sayılır. Ölçülen şey uygulamanın bu gerçeği ne kadar doğru tahmin ettiğidir.
**BG8 (varsayım):** üçüncü ayardaki yerel yuva, "uygulamaya ağdan yalnız vekil aracılığıyla
erişilir" kararının ölçülebilir karşılığıdır.

```js
// tasima.mjs — sifreli baglanti varsayimi
import http from "node:http";
import { tmpdir } from "node:os";
import { rmSync } from "node:fs";
import { KUME } from "./sertlestirme.mjs";

const BASLIK = "x-forwarded-proto";
const yerel = (port) => ({ host: "127.0.0.1", port });
const ac = (s, adres) => new Promise((c) => s.listen(adres, () => c(s)));

// uygulama: sifresizi yonlendirir
const uygulama = (kural) => (q, y) => {
  const p = (q.headers[BASLIK] ?? "").split(",").map((s) => s.trim()).filter(Boolean)
    .at(kural === "ilk" ? 0 : -1);
  if (p !== "https") return y.writeHead(308, { location: "https://odunc.kutuphane" + q.url }).end();
  y.writeHead(200, { "strict-transport-security": KUME["strict-transport-security"].deger });
  y.end(JSON.stringify({ uc: q.url }));
};

// vekil: sonlandirir ve aktarir
function vekil(hedef, bicim) {
  const s = http.createServer((q, y) => {
    const gelen = q.headers[BASLIK], zincir = bicim === "ekleyen" && gelen ? `${gelen}, https` : "https";
    const i = http.request({ ...hedef, path: q.url, headers: { ...q.headers, [BASLIK]: zincir } },
      (c) => { y.writeHead(c.statusCode, c.headers); c.pipe(y); });
    i.end();
  });
  return ac(s, yerel(0));
}

const dinle = (kural, adres) => {
  if (typeof adres === "string") rmSync(adres, { force: true });
  return ac(http.createServer(uygulama(kural)), adres);
};

const iste = (hedef, gonderilen) => new Promise((c) => {
  const i = http.get({ ...hedef, path: "/odunc/al", headers: gonderilen ? { [BASLIK]: gonderilen } : {} },
    (y) => { y.resume(); c(y); });
  i.on("error", () => c({ statusCode: 0 }));
});

// kapali oldugu bilinen bir TCP adresi
const kapali = await ac(http.createServer(), yerel(0));
const kapaliPort = kapali.address().port;
kapali.close();

const ISTEKLER = ["vekil", "dogrudan"].flatMap((y) => [null, "https", "http"].map((g) => [y, g]));
const yuva = tmpdir() + "/odunc.sock";

const AYARLAR = [
  ["ekleyen vekil, ilk deger", "ilk", "ekleyen", null],
  ["ekleyen vekil, son deger", "son", "ekleyen", null],
  ["ustune yazan vekil, yalniz yuva", "son", "yazan", yuva],
];

console.log(`${ISTEKLER.length} istek: 3'u vekil uzerinden (gercek: sifreli), 3'u dogrudan (gercek: sifresiz)\n`);
console.log(`${"ayar".padEnd(34)}${"yanlis gecme".padStart(13)}${"yanlis kalma".padStart(14)}` +
  `${"tasima basligi/6".padStart(18)}${"ulasilamayan/3".padStart(16)}`);

for (const [ad, kural, bicim, adres] of AYARLAR) {
  const app = await dinle(kural, adres ?? yerel(0));
  const v = await vekil(adres ? { socketPath: adres } : yerel(app.address().port), bicim);
  let [gecme, kalma, tb, kopuk] = [0, 0, 0, 0];
  for (const [yol, gonderilen] of ISTEKLER) {
    const sifreli = yol === "vekil";
    const y = await iste(yerel(sifreli ? v.address().port
      : adres ? kapaliPort : app.address().port), gonderilen);
    if (y.statusCode === 0) { kopuk++; continue; }
    if (y.headers["strict-transport-security"]) tb++;
    if (!sifreli && y.statusCode === 200) gecme++;
    if (sifreli && y.statusCode === 308) kalma++;
  }
  console.log(ad.padEnd(34) + String(gecme).padStart(13) + String(kalma).padStart(14) +
    String(tb).padStart(18) + String(kopuk).padStart(16));
  v.close(); app.close();
}

// yanlis kalmanin bedeli
const app = await dinle("ilk", yerel(0));
const v = await vekil(yerel(app.address().port), "ekleyen");
const durumlar = [];
for (let i = 0; i < 5; i++) durumlar.push((await iste(yerel(v.address().port), "http")).statusCode);
console.log(`\n"ilk deger" ayarinda sifreli ama ters baslik tasiyan istek: 5 deneme -> ${durumlar.join(", ")}`);
console.log(`uygulamanin dondugu 5xx sayisi: 0; kayitta hata yok`);
v.close(); app.close();
```

```
6 istek: 3'u vekil uzerinden (gercek: sifreli), 3'u dogrudan (gercek: sifresiz)

ayar                               yanlis gecme  yanlis kalma  tasima basligi/6  ulasilamayan/3
ekleyen vekil, ilk deger                      1             1                 3               0
ekleyen vekil, son deger                      1             0                 4               0
ustune yazan vekil, yalniz yuva               0             0                 3               3

"ilk deger" ayarinda sifreli ama ters baslik tasiyan istek: 5 deneme -> 308, 308, 308, 308, 308
uygulamanin dondugu 5xx sayisi: 0; kayitta hata yok
```

Üç ayar aynı altı isteği görüyor. İlk ayar zincirin **ilk** değerini okuyor ve o değeri istemci
yazabiliyor: şifresiz gelen taklitçi istek kabul ediliyor (bir yanlış geçme), gerçekten şifreli
olan bir istek geri çevriliyor (bir yanlış kalma). İkinci ayar zincirin **son** değerini, yani
uygulamaya en yakın katmanın yazdığını okuyor; yanlış kalma sıfırlanıyor ama yanlış geçme
duruyor — doğrudan gelen istekte zincirde tek değer vardır ve onu istemci yazmıştır, "son değer"
kuralı orada ilk değeri okumakla aynı şeydir.

Üçüncü ayar iki şeyi birden değiştiriyor: vekil istemcinin gönderdiğini aktarmıyor, üstüne
yazıyor; uygulama ağdan değil yalnız yerel bir yuvadan dinliyor. Doğrudan üç istek bağlantı
kuramıyor, iki hata biçimi de sıfırlanıyor, taşıma başlığı tam olarak hak eden üç yanıta
yazılıyor. Sonuç bir başlık kuralından çok bir **yol** kuralıdır: aktarılan başlığa güvenmek, o
başlığı yazan katmanın tek giriş noktası olmasına güvenmektir.

Yanlış kalmanın bedeli de sessizdir. İlk ayarda ters başlık taşıyan şifreli istek beş denemenin
beşinde 308 döndü; yönlendirmeyi izleyen istemci aynı isteği aynı başlıkla yeniden üretir ve aynı
yanıtı alır. Uygulamanın döndüğü 5xx sayısı sıfırdır — kayıtta hata arayan biri bu döngüyü
bulamaz. Aynı karar önceki bölümü de bağlıyor: taşıma başlığı yalnız uygulama bağlantıyı şifreli
saydığında yazılıyor, ilk ayarda o üç yanıttan biri şifresiz bir bağlantıya gitti.

#### Özet

- Uç başına yazımda 10 kayıt noktası ve 58 kayıt satırı vardır; iki sapma 2 dosyada durdu. Tek
  katmana taşıma 11 dosyaya dokundu, kayıt noktasını 1'e, yeni uç için yazılacak satırı 0'a indirdi.
- Üç ayarda 30 isteğin 30'u 200 döndü, hata 0; sertleştirme baytı 2591 / 2660 / 2400 ve **en az
  bayt üreten ayar sözleşmesi 0/10 uçta tam olandır** — yanıt boyu doğruluk sırası vermez.
- Yazılmış ama etkisiz değer eksik başlıktan ayrı sayılır: tek katmandaki tek yanlış değer
  sözleşmesi tam uç sayısını 10/10'dan 0/10'a düşürdü, durum kodu ve gövde değişmeden. Denetim bu
  yüzden yapılandırmanın değil **yanıtın** üzerinde çalışır.
- Aktarılan protokol başlığında ilk değer kuralı 1 yanlış geçme ve 1 yanlış kalma üretti; son
  değer kuralı yalnız yanlış kalmayı sıfırladı, çünkü doğrudan gelen istekte zincirdeki tek değeri
  istemci yazar.
- Üstüne yazan vekil ve yalnız yerel yuvadan dinleme ikisini de sıfırladı: 3 doğrudan istek
  bağlantı kuramadı, başlık yalnız hak eden 3 yanıta yazıldı. Yanlış kalmanın izi yoktur — ters
  başlık taşıyan şifreli istek 5 denemenin 5'inde 308 döndü, 5xx sayısı 0.

#### Sonraki Adım

Taşıma tarafı bu dersle sertleşti: başlıklar tek katmanda toplandı, değerleri yanıt üzerinden
denetlenebilir hâle geldi ve şifreli bağlantı varsayımı tek bir yola bağlandı. Ama bu derste
kullanılan her değer kodun içine düz metin olarak yazıldı. Vekilin uygulamaya ulaştığı yol,
denetimin beklediği değerler ve şifreli bağlantının dayandığı anahtarlar aynı yerde durur:
kaynak dosyalarda. Sonraki ders bu değerleri sayar — kaçı kodda kalmış, biri değiştirildiğinde
kaç servise dokunuluyor ve döndürme sırasında eski ile yeni değerin birlikte geçerli olduğu
pencere kaç saniye sürüyor.

### Gizli Değer Yönetimi

Source: https://academia.sh/tr/kurslar/backend-uretim/gizli-deger-yonetimi

Üretimdeki gizli değerin üç ölçüsü: döndürme sırasında iki anahtarın birlikte geçerli olduğu pencerede kaç isteğin hangi sürümle doğrulandığı, temizleme adımı atlandığında sızan değerin ne kadar geçerli kaldığı ve kodda kalan sabit değeri arayan taramanın yanlış geçme ile yanlış kalma sayıları.

Önceki ders taşımayı sertleştirdi: dışarıdan gelen istek artık şifreli bir kanaldan giriyor ve
tarayıcıya ne yapabileceğini söyleyen başlıklar sunucu tarafında konuyor. Kanalın iki ucu
kapatıldığında geriye kanalın taşıdığı şey kalır. Ödünç sisteminin yedi servisi birbirini
çağırırken kim olduklarını bir **imza anahtarıyla** kanıtlar; katalog kimliği, ödeme anahtarı ve
bildirim belirteci de aynı sınıftandır. Bunlar **gizli değerdir** ve taşıma katmanı onları ancak
ağ üzerinde korur — durdukları yerde değil.

Bu değerlerin **test ortamına** dağıtımı Gizli Değer ve Veri Yönetimi dersinde ölçülmüştü: hangi
değerin ortama girdiği, ne kadar kapsam aldığı ve bir koşumun artefaktlarında kaç noktadan sızdığı
orada sayıldı. Üretim tarafının sorusu başkadır. Üretimde değer zaten girmiştir, kapsamı zaten
geniştir ve kimse onu koşum sonunda geri almaz. Burada ölçülen üç nicelik şudur: değer **kaç yerde
duruyor**, **döndürülürken** kaç istek kırılıyor ve sızdığında **ne kadar süre** işe yarıyor.

**BG10 — yedi servis tek bir imza anahtarı halkasını paylaşır.** Bileti veren de doğrulayan da
aynı halkadan okur; halka sürümlüdür ve depo imzalayan sürüm ile doğrulayan sürüm kümesini ayrı
tutar. **BG11 — bir servis bileti 120 saniye geçerlidir** ve servis bileti bitene kadar tazelemez.
**BG12 — yeni değer servislere sırayla, 45 saniye aralıklarla ulaşır**; yedi servisin sonuncusu
döndürme anından 270 saniye sonra okur.

#### Değerin Durduğu Yer

Depo, doğrulamanın hangi sürümleri kabul edeceğini **zamana bağlı** bir küme olarak verir. Döndürme
anından önce yalnız eski sürüm, pencere boyunca ikisi, pencere kapandıktan sonra yalnız yeni sürüm.

```js
// kasa.mjs — gizli deger deposu: surumlu anahtar halkasi, imzalama ve dogrulama kumesi
import { createHmac, timingSafeEqual } from 'node:crypto';

export const BILET_OMRU = 120;        // BG11: bir servis bileti 120 saniye gecerli
export const DONDURME_ANI = 300;      // yeni surumun uretildigi an
export const DAGITIM_ARALIGI = 45;    // BG12: servisler 45 saniye arayla yeni degeri okur
export const UFUK = 900;
export const SERVISLER = ['katalog', 'odunc', 'uyelik', 'bildirim', 'ceza', 'arama', 'raporlama'];

// Depo iki kumeyi ayri tutar: imzalayan tek surum, dogrulayan bir veya iki surum.
export function kasaAc(pencere) {
  const anahtar = { 1: 'v1-halka-4c9a1d', 2: 'v2-halka-77e5b0' };
  const kapanis = pencere === Infinity ? Infinity : DONDURME_ANI + pencere;
  return {
    dogrulayan: (t) => (t < DONDURME_ANI ? [1] : t < kapanis ? [1, 2] : [2]),
    imzala: (surum, govde) => createHmac('sha256', anahtar[surum]).update(govde).digest('base64url'),
    dogrula(t, govde, imza) {
      for (const surum of this.dogrulayan(t)) {
        const beklenen = Buffer.from(this.imzala(surum, govde));
        const gelen = Buffer.from(imza);
        if (beklenen.length === gelen.length && timingSafeEqual(beklenen, gelen)) return surum;
      }
      return 0;
    },
  };
}

// Degerin kac yerde durdugu: her servis hem surecinde hem zamanlanmis isinde okuyor.
export const YERLESIM = {
  dagitik: { yer: SERVISLER.length * 2, okuma: 0 },
  merkezi: { yer: 1, okuma: SERVISLER.length },
};
```

Son satırlar bu kursun üçüncü sorusunu karşılıyor: ayar kaç yerde tekrarlanıyor. Değer her servisin
sürecinde ve her servisin zamanlanmış işinde ayrı ayrı duruyorsa on dört yerdedir; çalışma anında
tek bir depodan okunuyorsa bir yerdedir ve yedi okuma noktası vardır. Fark, döndürmenin maliyetinde
görünür.

#### Döndürme Penceresi

Aşağıdaki koşum gerçek bir doğrulama servisi ayağa kaldırır ve 900 saniyelik bir zaman çizgisini
beş saniyelik adımlarla dolaşır. Zaman sanaldır: her istek taşıdığı saati başlıkta bildirir, böylece
sayılar duvar saatine bağlı olmaz. Pencere genişliği taranır ve her genişlik iki kez koşar —
biri servislerin biletlerini olağan sırayla tazelediği hâl, öteki her servisin son eski bileti yeni
değeri okumasından bir saniye önce tazelediği **en kötü hizalama**.

```js
// dondurme.mjs — ortusme penceresi tarama: hangi anahtarla dogrulandi, kac istek reddedildi
import { createServer } from 'node:http';
import { kasaAc, BILET_OMRU, DONDURME_ANI, DAGITIM_ARALIGI, UFUK, SERVISLER, YERLESIM } from './kasa.mjs';

let kasa = kasaAc(0);
const sunucu = createServer((istek, yanit) => {
  const t = Number(istek.headers['x-saat']);
  const govde = istek.headers['x-bilet'];
  const verildi = Number(govde.split('.')[1]);
  const surum = kasa.dogrula(t, govde, istek.headers['x-imza'] ?? '');
  const [kod, sonuc] = surum === 0 ? [401, 'imza'] : t >= verildi + BILET_OMRU ? [401, 'sure'] : [200, surum];
  yanit.writeHead(kod, { 'content-type': 'application/json' });
  yanit.end(JSON.stringify({ sonuc }));
});
sunucu.listen(8951, '127.0.0.1');
const TABAN = 'http://127.0.0.1:8951/odunc';

// Servis i, DONDURME_ANI + i*DAGITIM_ARALIGI aninda yeni degeri okur; bileti bitene kadar tutar.
const okumaAni = (i) => DONDURME_ANI + i * DAGITIM_ARALIGI;
const SON_OKUMA = okumaAni(SERVISLER.length - 1);

// hizala: her servisin son v1 bileti, yeni degeri okumasindan bir saniye once tazelenir (en kotu hal).
async function kosum(pencere, hizala = false) {
  kasa = kasaAc(pencere);
  const bilet = SERVISLER.map(() => null);
  const hizalandi = SERVISLER.map(() => false);
  const sayac = { 1: 0, 2: 0, imza: 0, sure: 0 };
  let sonRet = null;
  for (let t = 0; t <= UFUK; t += 5) {
    const i = (t / 5) % SERVISLER.length;
    const kotu = hizala && !hizalandi[i] && bilet[i] !== null && t >= okumaAni(i);
    if (bilet[i] === null || t >= bilet[i].verildi + BILET_OMRU || kotu) {
      const verildi = kotu ? okumaAni(i) - 1 : t;
      const surum = verildi < okumaAni(i) ? 1 : 2;
      const govde = `${SERVISLER[i]}.${verildi}`;
      bilet[i] = { surum, govde, imza: kasa.imzala(surum, govde), verildi };
      hizalandi[i] ||= kotu;
    }
    const y = await fetch(TABAN, { headers: { 'x-saat': String(t), 'x-bilet': bilet[i].govde, 'x-imza': bilet[i].imza } });
    const { sonuc } = await y.json();
    sayac[sonuc] += 1;
    if (y.status === 401) sonRet = t;
  }
  // Sizmis eski surumle ufuktaki deneme: deger hala kapiyi aciyor mu.
  const govde = `arama.${UFUK}`;
  const sizan = await fetch(TABAN, { headers: { 'x-saat': String(UFUK), 'x-bilet': govde, 'x-imza': kasa.imzala(1, govde) } });
  return { ...sayac, sonRet, sizan: sizan.status };
}

const s = (x, n) => String(x).padStart(n);
console.log(`${'pencere'.padEnd(12)}${s('v1', 6)}${s('v2', 6)}${s('red', 6)}${s('son red', 9)}`
  + `${s('kotu red', 10)}${s('sizan v1', 10)}`);
for (const pencere of [0, 100, 200, 300, 400, Infinity]) {
  const r = await kosum(pencere);
  const k = await kosum(pencere, true);
  const ad = pencere === Infinity ? 'temizlemesiz' : `${pencere} sn`;
  console.log(`${ad.padEnd(12)}${s(r[1], 6)}${s(r[2], 6)}${s(r.imza + r.sure, 6)}`
    + `${s(r.sonRet ?? '-', 9)}${s(k.imza + k.sure, 10)}${s(r.sizan, 10)}`);
}
sunucu.close();

const gerekli = SON_OKUMA - DONDURME_ANI + BILET_OMRU;
console.log(`\nson okuma ${SON_OKUMA} sn, bilet omru ${BILET_OMRU} sn -> aritmetik alt sinir ${gerekli} sn`);
for (const [ad, y] of Object.entries(YERLESIM)) {
  console.log(`${ad}: deger ${y.yer} yerde duruyor, calisma aninda ${y.okuma} okuma, `
    + `yilda 4 dondurme ${y.yer * 4} elle adim eder`);
}
const gun = 24 * 60;
for (const donem of [90, 30, 7]) {
  console.log(`dondurme donemi ${donem} gun -> sizan anahtarin ortalama gecerliligi ${donem / 2} gun, `
    + `en kotu ${donem} gun; ayni anda sizan bilet ${BILET_OMRU / 60} dakika (${(donem * gun / (BILET_OMRU / 60)).toFixed(0)} kat kisa)`);
}
```

```
pencere         v1    v2   red  son red  kotu red  sizan v1
0 sn            60    85    36      555        51       401
100 sn          80    85    16      555        31       401
200 sn          90    85     6      555        15       401
300 sn          96    85     0        -         3       401
400 sn          96    85     0        -         0       401
temizlemesiz    96    85     0        -         0       200

son okuma 570 sn, bilet omru 120 sn -> aritmetik alt sinir 390 sn
dagitik: deger 14 yerde duruyor, calisma aninda 0 okuma, yilda 4 dondurme 56 elle adim eder
merkezi: deger 1 yerde duruyor, calisma aninda 7 okuma, yilda 4 dondurme 4 elle adim eder
dondurme donemi 90 gun -> sizan anahtarin ortalama gecerliligi 45 gun, en kotu 90 gun; ayni anda sizan bilet 2 dakika (64800 kat kisa)
dondurme donemi 30 gun -> sizan anahtarin ortalama gecerliligi 15 gun, en kotu 30 gun; ayni anda sizan bilet 2 dakika (21600 kat kisa)
dondurme donemi 7 gun -> sizan anahtarin ortalama gecerliligi 3.5 gun, en kotu 7 gun; ayni anda sizan bilet 2 dakika (5040 kat kisa)
```

#### Pencerenin Okunması

İlk satır penceresiz döndürmedir ve bu kursun kuralının en açık örneğidir. Yeni sürüm üretilir
üretilmez doğrulama eskisini kabul etmeyi bırakır; **hiçbir yerde yapılandırma hatası yoktur**,
depo çalışır, imza doğrudur, servisler ayaktadır. Yine de 181 isteğin 36'sı reddedilir. Reddedilen
istekler döndürme anında değil, döndürmeden **255 saniye sonrasına kadar** sürer: son ret 555'inci
saniyededir. Sebep, elindeki bileti daha bitmeden atmayan servistir; en son okuyan servisin bileti
570'inci saniyeye kadar eski sürümle imzalanmıştır.

Pencere genişledikçe ret sayısı 36'dan 16'ya, 6'ya ve sıfıra iner. Sıfırın hangi genişlikte
geldiğine dikkat etmek gerekir: bu koşumda 300 saniye yetmiştir, ama aynı genişlik en kötü
hizalamada üç isteği reddeder. Koşumdan bağımsız nicelik tablonun altındaki satırdır — son okuma
anı ile döndürme anı arasındaki fark, artı bilet ömrü: 270 + 120 = **390 saniye**. Ölçülen değer
(300) doğru ayarı vermez; yalnızca o koşumun hizalamasını verir. Pencereyi ölçüme göre seçen bir
ekip, çizelge birkaç saniye kaydığında yeniden istek düşürür ve düşen istekler döndürmeden dört
dakika sonra geldiği için sebeple bağdaştırılmaz.

Son satır ise ölçünün tersinden yalan söylediği yerdir. **Temizlemesiz** düzende doğrulama iki
sürümü de süresiz kabul eder. Düşen istek sıfırdır, imza hatası sıfırdır, gösterge tablosu
temizdir; döndürme "başarılı" görünür. Sızan eski değerle 900'üncü saniyede yapılan deneme yine de
**200** alır — öteki beş satırda 401 alır. Yeni sürümün üretilmesi tek başına eskisini geçersiz
kılmaz; geçersiz kılan şey **kapatma adımıdır** ve o adım atlandığında hiçbir ölçü kötüleşmez.
Döndürmenin amacı budur ve döndürme tam olarak bu adımda sessizce başarısız olur.

#### Sızan Değerin Ömrü

Bir değerin sızdığı fark edildiğinde sorulan soru "ne kadar süre işe yaradı" değil, "ne kadar süre
işe yarayacak" sorusudur ve yanıtı döndürme dönemine bağlıdır. Sızma anı dönem içinde herhangi bir
yere düşebileceğinden bekleneni dönemin yarısı, en kötüsü dönemin tamamıdır: doksan günlük dönemde
kırk beş gün, otuz günlükte on beş gün, yedi günlükte üç buçuk gün. Bu aritmetik, döndürmeyi
sıklaştırmanın neyi satın aldığını gösterir — kapıyı kapatmayı değil, **kapının açık kalacağı
süreyi**.

Aynı satırların ikinci yarısı asıl kaldıracı gösteriyor. Sızan şey anahtarın kendisi değil de
anahtarla üretilmiş bir biletse ömrü 120 saniyedir; doksan günlük dönemin altmış dört bin katı
kısa. Uzun ömürlü değeri az sayıda yerde tutup ondan kısa ömürlü bilet türetmek, döndürme
sıklığından bağımsız olarak açık kalma süresini dört büyüklük sırası kısaltır. Yerleşim satırları
bunun bedelini yazıyor: değer on dört yerde duruyorsa yılda dört döndürme elli altı elle adım eder
ve her adım kaçırılabilir; tek yerde duruyorsa dört adım eder ve yedi servis onu çalışma anında
okur.

#### Kodda Kalan Değer

Yukarıdaki bütün aritmetik, değerin **yapılandırmada** durduğunu varsayar. Kodda duran bir değer
döndürüldüğünde geçersiz olmaz — kod değişene ve yayınlanana kadar eski değer imzalamaya devam
eder, dolayısıyla son okuma anı yayın süresi kadar geriye kayar ve gerekli pencere onunla birlikte
büyür. Bu yüzden kodda kalan sabit değeri aramak, döndürme düzeneğinin bir parçasıdır.

```js
// tarama.mjs — kodda kalan sabit degerin taranmasi: yanlis gecme ve yanlis kalma
// [dosya, satir, gercekten gizli mi]
const kaynak = [
  ['odunc/bilet.mjs', "const IMZA_ANAHTARI = 'v2-halka-77e5b0';", true],
  ['rapor/aktar.mjs', "const DEPO_PAROLASI = 'k3M-pW9-zQ4-vB7-nR2';", true],
  ['bildirim/gonder.mjs', "const ayar = { ust: 'T-4f8a1c9d2b6e0357a9c4f1b8' };", true],
  ['arama/dizin.mjs', "const yenile = 'Zq8Rv3Nw6Lp1Ke5Ty9Xu2Bd7Hm4';", true],
  ['uyelik/sinama.mjs', "const SINAMA_BELIRTECI = 'sb-9d31-77af-2c60';", true],
  ['katalog/kayit.mjs', "const KATALOG_KIMLIGI = 'ac-kutuphane-govde-2';", false],
  ['odunc/ayar.mjs', "const ANAHTAR_ADI = 'imza-anahtari-v2';", false],
  ['arayuz/ikon.mjs', "const IKON = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAI';", false],
  ['ceza/ayar.mjs', "const gizliDizin = './veri/gecici/ceza';", false],
  ['bildirim/ornek.mjs', "const BELIRTEC_YERI = 'DEGISTIR-BENI-KURULUMDA';", false],
  ['uyelik/kimlik.mjs', "const uyeKimligi = '9f2c1a4e-77b0-4d31-8c6a-2e5b90f14d73';", false],
];

const deger = (satir) => (satir.match(/'([^']+)'/) ?? [, ''])[1];
const ad = (satir) => (satir.match(/(?:const|let)\s+(\w+)|(\w+)\s*:/) ?? [, ''])[1] ?? '';

function entropi(d) {
  const say = {};
  for (const c of d) say[c] = (say[c] ?? 0) + 1;
  return -Object.values(say).reduce((a, n) => a + (n / d.length) * Math.log2(n / d.length), 0);
}

const AD_KALIBI = /anahtar|gizli|parola|belirtec|imza|sifre/i;
const IZINLI = [/^ac-/, /^DEGISTIR-BENI/];

const kurallar = {
  'ad kalibi': (s) => AD_KALIBI.test(ad(s)) && deger(s).length >= 8,
  'entropi': (s) => deger(s).length >= 20 && entropi(deger(s)) >= 3.6,
  'birlesim': (s) => kurallar['ad kalibi'](s) || kurallar['entropi'](s),
  'birlesim + izin listesi': (s) => kurallar.birlesim(s)
    && !IZINLI.some((r) => r.test(deger(s))) && !deger(s).includes('/'),
};

const s = (x, n) => String(x).padStart(n);
console.log(`${'kural'.padEnd(24)}${s('bulgu', 7)}${s('yakalanan', 11)}${s('yanlis gecme', 14)}${s('yanlis kalma', 14)}`);
for (const [kad, kural] of Object.entries(kurallar)) {
  const bulgu = kaynak.filter(([, satir]) => kural(satir));
  const dogru = bulgu.filter(([, , gizli]) => gizli).length;
  console.log(`${kad.padEnd(24)}${s(bulgu.length, 7)}${s(`${dogru}/5`, 11)}`
    + `${s(5 - dogru, 14)}${s(bulgu.length - dogru, 14)}`);
}

console.log('\nad kalibinin kacirdigi gercek degerler:');
for (const [dosya, satir, gizli] of kaynak) {
  if (gizli && !kurallar['ad kalibi'](satir)) {
    console.log(`  ${dosya.padEnd(22)} ad=${ad(satir).padEnd(8)} `
      + `uzunluk=${s(deger(satir).length, 2)} entropi=${entropi(deger(satir)).toFixed(2)}`);
  }
}
console.log('entropi kuralinin yanlis kaldigi zararsiz dizgeler:');
for (const [dosya, satir, gizli] of kaynak) {
  if (!gizli && kurallar.entropi(satir)) {
    console.log(`  ${dosya.padEnd(22)} uzunluk=${s(deger(satir).length, 2)} `
      + `entropi=${entropi(deger(satir)).toFixed(2)} izinli=${IZINLI.some((r) => r.test(deger(satir)))}`);
  }
}
```

```
kural                     bulgu  yakalanan  yanlis gecme  yanlis kalma
ad kalibi                     6        3/5             2             3
entropi                       5        2/5             3             3
birlesim                     10        5/5             0             5
birlesim + izin listesi       7        5/5             0             2

ad kalibinin kacirdigi gercek degerler:
  bildirim/gonder.mjs    ad=ayar     uzunluk=26 entropi=4.09
  arama/dizin.mjs        ad=yenile   uzunluk=27 entropi=4.75
entropi kuralinin yanlis kaldigi zararsiz dizgeler:
  katalog/kayit.mjs      uzunluk=20 entropi=3.78 izinli=true
  bildirim/ornek.mjs     uzunluk=23 entropi=3.76 izinli=true
  uyelik/kimlik.mjs      uzunluk=36 entropi=4.00 izinli=false
```

Ad kalıbı tek başına beş gerçek değerin üçünü bulur. Kaçırdığı ikisi listenin sonunda adıyla
duruyor: değişkenin adı `ayar` ve `yenile`. Adlandırma sözleşmesine dayanan bir tarama, sözleşmeye
uymayan satırı göremez ve gizli bir değeri koda sabitleyen kişi zaten sözleşmeyi izlememiştir.
Entropi kuralı bu ikisini yakalar ama kendi başına daha kötüdür: beşte iki. Kısa ve düzenli
biçimdeki değerler — on dokuz karakterlik parola, kısa çizgilerle bölünmüş bilet — eşiğin altında
kalır.

İkisinin birleşimi beş değerin beşini bulur ve bedeli beş yanlış kalmadır. Yanlış kalanların üçü
kural yazılarak elenebilir: açık katalog kimliğinin öneki, kurulumda değiştirilecek yer tutucu ve
eğik çizgi taşıyan dizin yolu. Kalan iki yanlış kalma elenemez, çünkü biri anahtarın **adını**
tutan bir sabittir, öteki bir üye kimliğidir; ikisi de gizli değere birebir benzer. Buradaki takas
kursun her yerinde aynıdır: yanlış geçmenin bedeli kodda kalan ve döndürülemeyen bir değerdir,
yanlış kalmanın bedeli ise iki satırlık elle incelemedir. Eşiği ikinci maliyete göre ayarlamak,
birinciyi ödemeyi göze almak demektir.

#### Özet

- Üretimdeki gizli değerin üç ölçüsü kaç yerde durduğu, döndürülürken kaç istek kırdığı ve
  sızdığında ne kadar süre işe yaradığıdır; test ortamındaki ölçü olan sızma yüzeyi burada
  zaten en geniş hâlindedir.
- Penceresiz döndürme 181 isteğin 36'sını reddetti ve retler döndürmeden 255 saniye sonrasına
  kadar sürdü; hiçbir bileşen hata vermedi.
- Gerekli pencere ölçümden değil aritmetikten okunur: son okuma anı eksi döndürme anı artı bilet
  ömrü, bu düzende 390 saniye. Bu koşumda 300 saniye de sıfır ret verdi, en kötü hizalamada üç.
- Temizleme adımı atlandığında düşen istek sıfır kalır, gösterge temiz görünür ve sızan eski
  anahtar 900'üncü saniyede hâlâ 200 alır; döndürme yalnız kapatma adımıyla tamamlanır.
- Anahtarı az yerde tutup ondan 120 saniyelik bilet türetmek, sızan değerin ömrünü doksan günlük
  dönemin altmış dört bin katı kısaltır; değerin on dört yerde durması yılda elli altı elle adım,
  tek yerde durması dört adım eder.
- Sabit değer taramasında ad kalıbı beşte üç, entropi beşte iki yakaladı; birleşimi beşte beş
  yakaladı ve beş yanlış kalma üretti — üçü kuralla elenebilir, ikisi elenemez.

#### Sonraki Adım

Bu dersin bütün ölçümleri kendi yazdığımız kodun içinde kaldı: anahtarı biz ürettik, biletin ömrünü
biz seçtik, taramanın eşiğini biz koyduk. Oysa isteği karşılayan kodun büyük bölümü bizim değildir.
Ödünç servisinin süreci ayağa kalktığında yüzlerce dosya yüklenir ve bunların çok azı ekibin
yazdığı satırlardır; her biri bir sürüm numarasına bağlıdır ve o numara bir gün değişir. Sonraki
ders bu bağımlılığı bir yapılandırma kararı olarak ele alır — yükseltmeyi ne sıklıkta yapmanın
biriken riskle kırılan sürüm arasında nasıl bir takas kurduğunu, üç ayrı politikayı aynı bağımlılık
ağacında koşturarak sayar.

### Bağımlılık Riski

Source: https://academia.sh/tr/kurslar/backend-uretim/bagimlilik-riski

Sürüm politikası bir yapılandırma kararıdır: beş politika aynı bağımlılık ağacında koşturulur ve biriken açık günü ile kırılan yükseltme sayısı sayılır; gevşek sürüm aralığının sessiz sonucu, aynı kaynak sürümünün iki dağıtımı arasında ayrışan paket sayısıyla ölçülür.

Önceki dersin bütün ölçümleri kendi yazdığımız kodun içindeydi. Ödünç servisinin süreci ayağa
kalktığında yüklenen dosyaların çoğu ise ekibin yazdığı satırlar değildir; ağaçta yirmi dört paket
vardır ve bunların yalnız sekizinin adı bildirim dosyalarında geçer. Kalan on altısı **aktarımlı**
gelir: bir paketin bağımlılığının bağımlılığıdır, kimse onu seçmemiştir ve sürümünü kimse
yazmamıştır.

Bu bağımlılıkların **taranması** Bağımlılık Taraması dersinde ölçülmüştü; oradaki soru aracın neyi
bulduğuydu. Burada aracın bulduğu varsayılır ve bir sonraki soru sorulur: bulgu geldiğinde ne
yapılacağı hangi kurala bağlıdır. O kural **sürüm politikasıdır** ve bir yapılandırma kararıdır —
yazıldığı yer bir bildirim dosyasındaki sürüm kısıtıdır, sonucu ise ekibin hiç görmediği bir
takasta ortaya çıkar: yükseltmeyi sıklaştırmak biriken riski azaltır ve kırılan sürüm sayısını
artırır.

**Gerçek paket adı, sürüm numarası ve zayıflık kaydı kullanılmaz.** Ağaç, yayın çizelgesi ve kayıt
kümesi aşağıda kendi üreticimizle üretilir; tohum görünürdür ve koşum yinelenebilir.

#### Ağaç ve Kayıt Kümesi

**BG13 — ağaç 24 paketten oluşur; sekizi doğrudan, on altısı aktarımlıdır ve ufuk 360 gündür.**
Her paketin bir yayın aralığı ve bir kırıcılık oranı vardır: yayınlarının ne kadarı bizim kodumuzu
kırar. **BG15 — bir zayıflık kaydının bize ulaşması 0 ile 90 gün arasında sürer.** Kayıt, zayıflığın
var olmaya başladığı günde değil, bildirildiği günde elimize geçer; açık penceresi ise var olduğu
günden kapandığı güne kadar sayılır.

```js
// agac.mjs — kendi bagimlilik agacimiz, kendi yayin cizelgemiz, kendi kayit kumemiz
export const UFUK = 360;              // gun
export const TOHUM = 8617;
let durum = TOHUM;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648;
const sec = (dizi) => dizi[Math.floor(rast() * dizi.length)];

// derinlik 1: bildirim dosyamizda adi gecen paket; 2 ve 3: aktarimli, adi gecmez.
const DERINLIK = [...Array(8).fill(1), ...Array(10).fill(2), ...Array(6).fill(3)];
const ARALIK = [14, 30, 45, 60, 90];          // yayin araligi (gun)
const KIRICILIK = [0, 0.08, 0.18, 0.35];      // yayinin bizi kirma orani

export const paketler = DERINLIK.map((derinlik, i) => {
  const aralik = sec(ARALIK);
  const kiricilik = sec(KIRICILIK);
  // aktarimli pakette duzeltme, ust paket yeni surum cikarana kadar kurulamaz.
  const ustGecikme = derinlik === 1 ? 0 : Math.round(sec(ARALIK) * rast());
  const yayinlar = [{ gun: 0, kirici: false }];
  for (let g = Math.round(rast() * aralik); g <= UFUK; g += aralik) {
    if (g > 0) yayinlar.push({ gun: g, kirici: rast() < kiricilik });
  }
  return { ad: `p${String(i + 1).padStart(2, '0')}`, derinlik, aralik, ustGecikme, yayinlar };
});

// Kayit: bir pakette var olan zayiflik. gun: var olmaya basladigi gun; duzelten: onu kapatan
// yayin; bilinen: kaydin bize ulastigi gun. erisilebilir: kod yolumuz o yuzeye dokunuyor.
export const kayitlar = [];
for (let k = 0; k < 11; k++) {
  const p = Math.floor(rast() * paketler.length);
  const gun = 20 + Math.floor(rast() * (UFUK - 80));
  const duzelten = paketler[p].yayinlar.findIndex((y) => y.gun >= gun);
  if (duzelten <= 0) continue;
  const bildirim = Math.round(rast() * 90);      // BG15: kayit gecikmesi 0-90 gun
  kayitlar.push({ paket: p, gun, duzelten, bilinen: gun + bildirim, bildirim, erisilebilir: rast() < 0.55 });
}

// Kisit, her servisin kendi bildirim dosyasinda ayri duruyor.
export const SERVISLER = ['katalog', 'odunc', 'uyelik', 'bildirim', 'ceza', 'arama', 'raporlama'];
export const kisitYeri = paketler.map((p) => (p.derinlik === 1 ? 1 + Math.floor(rast() * SERVISLER.length) : 0));
export const hazirGun = (p, i) => paketler[p].yayinlar[i].gun + paketler[p].ustGecikme;
```

#### Beş Politika

**BG14 — kod yayını on dört günde bir yapılır.** Politikalar yalnız bir noktada ayrılır: ağacın
hangi günlerde ve hangi paketler için değiştiği. Beşincisi bir politika değil, bir kısıt
biçimidir — sürüm aralığı gevşek bırakıldığında ağaç, kimse bir dosyaya dokunmadan, yayın anında
kendiliğinden değişir.

```js
// politika.mjs — bes surum politikasi ayni agacta: acik penceresi ile kirilan yukseltme
import { paketler, kayitlar, kisitYeri, hazirGun, SERVISLER, UFUK, TOHUM } from './agac.mjs';

const DAGITIM = 14;   // BG14: yayin gunu; gevsek aralik yalniz bu gunlerde agaci degistirir

const politikalar = {
  'sabit': { gun: () => false },
  'hemen': { gun: () => true },
  'aylik': { gun: (g) => g % 30 === 0 },
  'yalniz guvenlik': { gun: () => true, secici: true },
  'gevsek aralik': { gun: (g) => g % DAGITIM === 0, dosyasiz: true },
};

function kosum(pol) {
  const kurulu = paketler.map(() => 0);
  const acik = kayitlar.map(() => 0);
  let yukseltme = 0, kirilan = 0, mudahale = 0, kirikGun = 0, enCok = 0;
  const anlik = {};
  for (let gun = 1; gun <= UFUK; gun++) {
    let gunlukYukseltme = 0, gunlukKirik = 0;
    if (pol.gun(gun)) {
      for (let p = 0; p < paketler.length; p++) {
        // Secici politika yalnizca kaydi bize ulasmis paketi yukseltir.
        if (pol.secici && !kayitlar.some((k) => k.paket === p && gun >= k.bilinen && kurulu[p] < k.duzelten)) continue;
        let hedef = kurulu[p];
        while (hedef + 1 < paketler[p].yayinlar.length && hazirGun(p, hedef + 1) <= gun) hedef += 1;
        if (hedef === kurulu[p]) continue;
        for (let i = kurulu[p] + 1; i <= hedef; i++) if (paketler[p].yayinlar[i].kirici) gunlukKirik += 1;
        gunlukYukseltme += 1;
        kurulu[p] = hedef;
      }
    }
    yukseltme += gunlukYukseltme;
    kirilan += gunlukKirik;
    if (gunlukYukseltme > 0) mudahale += 1;
    if (gunlukKirik > 0) kirikGun += 1;
    enCok = Math.max(enCok, gunlukKirik);
    kayitlar.forEach((k, i) => { if (gun >= k.gun && kurulu[k.paket] < k.duzelten) acik[i] += 1; });
    if (gun === 180 || gun === 194) anlik[gun] = [...kurulu];
  }
  const erisilebilir = kayitlar.map((k, i) => (k.erisilebilir ? acik[i] : 0));
  return {
    mudahale, yukseltme, kirilan, kirikGun, enCok,
    acikGun: erisilebilir.reduce((a, x) => a + x, 0),
    tumGun: acik.reduce((a, x) => a + x, 0),
    enUzun: Math.max(...erisilebilir),
    dosyasiz: pol.dosyasiz ? yukseltme : 0,
    fark: anlik[180].filter((v, i) => v !== anlik[194][i]).length,
  };
}

const s = (x, n) => String(x).padStart(n);
console.log(`tohum ${TOHUM}: ${paketler.length} paket, ${kayitlar.length} kayit, ufuk ${UFUK} gun`);
console.log(`${'politika'.padEnd(17)}${s('mudahale', 10)}${s('yukseltme', 11)}${s('kirilan', 9)}`
  + `${s('kirik gun', 11)}${s('en cok', 8)}${s('acik gun', 10)}${s('en uzun', 9)}`);
const sonuc = {};
for (const [ad, pol] of Object.entries(politikalar)) {
  const r = (sonuc[ad] = kosum(pol));
  console.log(`${ad.padEnd(17)}${s(r.mudahale, 10)}${s(r.yukseltme, 11)}${s(r.kirilan, 9)}`
    + `${s(r.kirikGun, 11)}${s(r.enCok, 8)}${s(r.acikGun, 10)}${s(r.enUzun, 9)}`);
}

const g = sonuc['gevsek aralik'];
console.log(`\ngevsek aralikta ${g.dosyasiz} yukseltmenin tamami bildirim dosyasina dokunulmadan geldi;`
  + ` ayni kaynak surumunun 180. ve 194. gun dagitimlari ${g.fark} pakette ayrisiyor`);
console.log(`hemen politikasinda ayni ayrisma ${sonuc.hemen.fark} pakette, sabit politikasinda ${sonuc.sabit.fark} pakette`);

const dogrudan = kayitlar.filter((k) => paketler[k.paket].derinlik === 1);
const gecikme = kayitlar.reduce((a, k) => a + paketler[k.paket].ustGecikme, 0);
const bildirim = kayitlar.reduce((a, k) => a + k.bildirim, 0) / kayitlar.length;
console.log(`kayitlarin ${dogrudan.length} tanesi dogrudan pakette, ${kayitlar.length - dogrudan.length} tanesi aktarimli;`
  + ` erisilebilir olan ${kayitlar.filter((k) => k.erisilebilir).length}`);
console.log(`aktarimli kayitlarin ust paket beklemesi toplam ${gecikme} gun; ortalama kayit gecikmesi ${bildirim.toFixed(1)} gun`);
const sirala = (alan) => Object.keys(sonuc).sort((a, b) => sonuc[a][alan] - sonuc[b][alan]).join(' < ');
console.log(`erisilebilirlik suzgeci kaldirilinca acik gun `
  + Object.entries(sonuc).map(([ad, r]) => `${ad} ${r.tumGun}`).join(', '));
console.log(`siralama suzgecli: ${sirala('acikGun')}`);
console.log(`siralama suzgecsiz: ${sirala('tumGun')}`);
console.log(`derinlik 1 paket ${paketler.filter((p) => p.derinlik === 1).length}, `
  + `bildirim dosyalarindaki toplam kisit ${kisitYeri.reduce((a, x) => a + x, 0)} `
  + `(${SERVISLER.length} servis); tek paketin dokundugu dosya sayisi en cok ${Math.max(...kisitYeri)}`);
```

```
tohum 8617: 24 paket, 11 kayit, ufuk 360 gun
politika           mudahale  yukseltme  kirilan  kirik gun  en cok  acik gun  en uzun
sabit                     0          0        0          0       0       860      269
hemen                   237        330       58         54       3       113       43
aylik                    12        214       58         12       8       195       65
yalniz guvenlik          14         15       12          7       6       178       67
gevsek aralik            25        320       56         24       6       147       49

gevsek aralikta 320 yukseltmenin tamami bildirim dosyasina dokunulmadan geldi; ayni kaynak surumunun 180. ve 194. gun dagitimlari 14 pakette ayrisiyor
hemen politikasinda ayni ayrisma 13 pakette, sabit politikasinda 0 pakette
kayitlarin 7 tanesi dogrudan pakette, 4 tanesi aktarimli; erisilebilir olan 5
aktarimli kayitlarin ust paket beklemesi toplam 51 gun; ortalama kayit gecikmesi 42.8 gun
erisilebilirlik suzgeci kaldirilinca acik gun sabit 2181, hemen 349, aylik 520, yalniz guvenlik 534, gevsek aralik 422
siralama suzgecli: hemen < gevsek aralik < yalniz guvenlik < aylik < sabit
siralama suzgecsiz: hemen < gevsek aralik < aylik < yalniz guvenlik < sabit
derinlik 1 paket 8, bildirim dosyalarindaki toplam kisit 39 (7 servis); tek paketin dokundugu dosya sayisi en cok 7
```

#### Takasın Okunması

İlk satır yükseltmemenin bedelidir ve kırılan sürüm sütununda sıfır yazar. Ağaç ilk günkü hâliyle
kalır, hiçbir yayın kimseyi uyandırmaz, hiçbir sınama kırılmaz — ve erişilebilir kayıtlar toplam
**860 gün** açık kalır, en uzunu tek başına 269 gün. Bu satır kursun kuralının en pahalı örneğidir:
sabitlenmiş bir ağaç en sessiz yapılandırmadır ve sessizliği risk birikimiyle ödenir.

İkinci satır öteki uçtur. Günlük yükseltme açık günü 860'tan **113'e** indirir, yaklaşık yedi buçuk
kat. Bedeli 237 müdahale günü ve 58 kırılan sürümdür. Kırılmaların dağılımına bakmak gerekir: 54
ayrı güne yayılmışlardır ve tek bir günde en çok üçü birden gelmiştir. Bir kırılmanın hangi
yükseltmeden geldiğini bulmak bu dağılımda ucuzdur.

Üçüncü satır sezginin yanıldığı yerdir. Aylık toplu yükseltme kırılan sürüm sayısını **hiç
azaltmaz** — yine 58. Toplu yükseltme kırılmaları yok etmez, yalnızca on iki güne toplar ve tek bir
günde sekiz kırılma birden gelir. Kazanılan şey müdahale sayısıdır (237 yerine 12), kaybedilen şey
hem açık gündür (113 yerine 195, seksen iki gün fazla) hem de kırılmanın kaynağını bulma kolaylığı.
Yükseltmeyi biriktirmek, kırılma maliyetini azaltmaz; onu tek bir güne yığar.

Dördüncü satır yalnız kaydı olan paketi yükseltir ve müdahaleyi on beş yükseltmeye indirir. Kırılan
sürüm on ikiye düşer, çünkü ağacın çoğuna hiç dokunulmaz. Buna karşılık açık gün 178'dir — günlük
yükseltmeden altmış beş gün fazla — ve en uzun tek pencere 67 günle bütün politikaların en
kötüsüdür. Sebep tablonun altındaki satırdadır: ortalama kayıt gecikmesi **42,8 gündür**. Kayda
göre hareket eden bir politika, kaydın gelmesini beklemek zorundadır; kaydı beklemeyen politika
düzeltmeyi zaten kurmuş olur. Yalnız güvenlik politikası ucuzdur ve tavanı kendi elinde değildir.

#### Politikanın Dokunamadığı Günler

Açık günlerin bir bölümü hiçbir politikanın erişemediği yerdedir. On bir kaydın dördü aktarımlı
pakettedir; düzeltme yayımlansa bile üst paket yeni bir sürüm çıkarana kadar kurulamaz ve bu
bekleme toplam **51 gündür**. Bu günler beş satırın hepsinde aynıdır — sürüm kısıtı bildirim
dosyasında olmayan bir paketi bağlamaz. Kayıt gecikmesinin 42,8 günü ile birlikte, tabloda görünen
farkların bir bölümü politikanın değil çizelgenin ürünüdür.

Erişilebilirlik süzgeci ise bir uyarı taşıyor. Süzgeç kaldırıldığında sayılar iki üç katına çıkıyor
(hemen 113 yerine 349, sabit 860 yerine 2181) ve uçlar yerinde kalıyor; ama ortadaki iki politika
yer değiştiriyor. Süzgeçliyken aylık politika yalnız güvenlikten kötü görünür, süzgeçsizken iyi.
Kod yolumuzun gerçekten dokunduğu yüzeyi ayırmak bu yüzden bir ayrıntı değil, sıralamayı çeviren
bir karardır.

#### Aralığın Durduğu Yer

Son satır kursun kuralına aittir. Gevşek sürüm aralığı ölçülerde iyi görünür: açık gün 147, kırılan
sürüm 56, müdahale 25. Oysa o 320 yükseltmenin tamamı **bildirim dosyasına dokunulmadan** gelmiştir.
Bunun tek ölçülebilir sonucu tablonun altındaki satırdır: aynı kaynak sürümünün 180'inci ve
194'üncü gün dağıtımları yirmi dört paketin **on dördünde** ayrışır. İki dağıtım aynı kodu taşır,
farklı bir ağaçla çalışır ve aradaki farkın hiçbir kaydı yoktur. Kodu geri almak ağacı geri almaz.

Günlük yükseltme politikasında aynı ayrışma on üç pakettedir; sayı neredeyse aynıdır. Fark, ayrışmanın
kaydıdır: orada her yükseltme bir dosya değişikliğidir, burada hiçbiri değildir. Sabit politikada
ayrışma sıfırdır ve tam da bu yüzden 860 açık gün ödenir.

Kısıtın durduğu yer son satırda sayılıyor: sekiz doğrudan paket, yedi servisin bildirim
dosyalarında toplam **39 kısıt** olarak yazılıdır ve tek bir paket yedi ayrı dosyada birden
görünebiliyor. Tek bir kayda karşılık verirken yedi dosyanın yedisini de bulmak gerekir; altısını
bulup birini atlayan bir yükseltme hata vermez, yalnızca bir servisi eski sürümde bırakır.

#### Özet

- Hiç yükseltmemek sıfır kırılma ve 860 açık gün üretti; en uzun tek pencere 269 gün sürdü. Sessiz
  yapılandırmanın bedeli burada birikmiş risktir.
- Günlük yükseltme açık günü 113'e indirdi; bedeli 237 müdahale günü ve 54 güne yayılmış 58 kırılan
  sürümdür.
- Aylık toplu yükseltme kırılan sürüm sayısını azaltmadı (yine 58); onları on iki güne yığdı, tek
  günde sekize çıkardı ve açık günü 195'e yükseltti.
- Yalnız güvenlik politikası on beş yükseltmeyle çalıştı ama 42,8 günlük ortalama kayıt gecikmesi
  yüzünden en uzun pencereyi 67 güne çıkardı; tavanı kendi elinde değildir.
- Aktarımlı paketlerin üst sürüm beklemesi 51 gün, hiçbir politikayla kapanmıyor; erişilebilirlik
  süzgeci ise ortadaki iki politikanın sırasını değiştiriyor.
- Gevşek aralıkta 320 yükseltmenin tamamı dosyaya dokunmadan geldi ve aynı kaynak sürümünün iki
  dağıtımı on dört pakette ayrıştı; ölçüler iyi görünürken ağacın kaydı yoktur.

#### Sonraki Adım

İki derste iki yapılandırma kararı ölçüldü ve ikisi de bir soruyu ortada bıraktı. Bir anahtar
döndürüldüğünde bunu kimin, ne zaman, hangi sürümle yaptığı; bir paket yükseltildiğinde ağacın
hangi gün değiştiği; gevşek aralıkta ayrışan on dört paketin hangi dağıtımda geldiği — bu soruların
hiçbirinin yanıtı sistemin içinde durmuyor. Uygulamanın kendi kayıtları isteği ve hatayı yazar,
kararı yazmaz. Sonraki ders bu boşluğu ele alır: hangi olayın kaydedileceğini, kaydın sonradan
değiştirilmediğinin nasıl doğrulanacağını ve kaydın kendisinin ne kadar kişisel veri taşıdığını
sayar.

### Denetim Kaydı

Source: https://academia.sh/tr/kurslar/backend-uretim/denetim-kaydi

İzlenebilirlik gereksiniminin dört ölçüsü: hangi karar noktasının kaydedildiği ve buna bağlı olarak kaç sorunun yanıtlanabildiği, zincirleme özetin yakaladığı değişiklik ile yakalayamadığı kuyruk kesme, kaydın kendi taşıdığı kişisel veri ve saklama süresinin hacme etkisi.

Önceki iki ders iki yapılandırma kararını ölçtü ve ikisi de bir soruyu ortada bıraktı: kim, ne
zaman. Uygulamanın kendi kayıtları isteği ve hatayı yazar; hangi görevlinin hangi üyenin cezasını
sildiğini yazmaz. **Denetim kaydı** bu boşluğu doldurur ve Vaka Çalışmaları kursundaki
**denetim izinden** amacıyla ayrılır: orada iz, durumu yeniden kurmaya yarayan bir veri yapısı
kararıydı; burada kayıt, bir kararın sorumlusunu **sonradan gösterebilmek** için tutulur ve bu
yüzden kendisinin de değiştirilmemiş olması gerekir.

İzlenebilirlik dört soruyla ölçülür. Hangi olay kaydediliyor — yani sistemin karar noktalarının
kaçı yazılıyor. Kayıt sonradan değiştirilmemiş olduğunu kanıtlayabiliyor mu. Kaydın kendisi ne
taşıyor. Ve ne kadar süre saklanıyor. Dördü de yapılandırma kararıdır ve yanlış ayarlandıklarında
sistem hata vermez.

#### Karar Noktaları

**BG16 — ödünç sisteminin on iki karar noktası vardır** ve günlük hacimleri aşağıdaki tabloda
durur; toplamı 3.012 olaydır. **BG17 — her kayıt dört alan taşır:** tür, aktör, hedef, sonuç. Depo
`node:sqlite` üzerindedir ve her kaydın özeti bir öncekinin özetini kapsar.

```js
// kayit.mjs — denetim kaydi deposu: karar noktalari, zincirleme ozet, dogrulama
import { createHash } from 'node:crypto';
import { DatabaseSync } from 'node:sqlite';

export const TOHUM = 4231;
let durum = TOHUM;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648;
const sec = (n) => Math.floor(rast() * n);

// Karar noktalari: [tur, gunluk sayi, durum degistirir mi, aktor sinifi, hedef sinifi]
export const TURLER = [
  ['GIRIS', 1200, false, 'uye', 'kendi'], ['GIRIS_RET', 60, false, 'uye', 'kendi'],
  ['GORUNTULE', 900, false, 'gorevli', 'uye'], ['ODUNC', 400, true, 'uye', 'katalog'],
  ['IADE', 380, true, 'uye', 'katalog'], ['KATALOG_DUZENLE', 45, true, 'gorevli', 'katalog'],
  ['CEZA_SIL', 12, true, 'gorevli', 'uye'], ['DISA_AKTARIM', 4, false, 'gorevli', 'uye'],
  ['YETKI_DEGISIKLIGI', 5, true, 'gorevli', 'uye'], ['UYE_SIL', 3, true, 'gorevli', 'uye'],
  ['DAGITIM', 2, true, 'gorevli', 'sistem'], ['DONDURME', 1, true, 'gorevli', 'sistem'],
];
export const UYE = 40, GOREVLI = 6, KATALOG = 60;

export function gunuUret() {
  const olaylar = [];
  for (const [tur, sayi, yazar, aktorSinifi, hedefSinifi] of TURLER) {
    for (let i = 0; i < sayi; i++) {
      const aktor = aktorSinifi === 'uye' ? `U-${1 + sec(UYE)}` : `G-${1 + sec(GOREVLI)}`;
      const hedef = hedefSinifi === 'kendi' ? aktor
        : hedefSinifi === 'uye' ? `U-${1 + sec(UYE)}`
          : hedefSinifi === 'katalog' ? `K-${800 + sec(KATALOG)}` : 'sistem';
      olaylar.push({ tur, aktor, hedef, yazar, sira: rast(), sonuc: tur === 'GIRIS_RET' ? 'ret' : 'kabul' });
    }
  }
  return olaylar.sort((a, b) => a.sira - b.sira);
}

// Zincirleme ozet: her kaydin ozeti bir oncekinin ozetini de kapsar.
export const ozetle = (sira, once, govde) =>
  createHash('sha256').update(`${sira}|${once}|${govde}`).digest('hex').slice(0, 16);

export function depoAc(olaylar, takma = null) {
  const db = new DatabaseSync(':memory:');
  db.exec('create table kayit (sira integer primary key, tur text, aktor text, hedef text, sonuc text, govde text, ozet text)');
  const ekle = db.prepare('insert into kayit values (?, ?, ?, ?, ?, ?, ?)');
  let once = 'baslangic';
  olaylar.forEach((o, i) => {
    const [aktor, hedef] = takma ? [takma(o.aktor), takma(o.hedef)] : [o.aktor, o.hedef];
    const govde = JSON.stringify({ tur: o.tur, aktor, hedef, sonuc: o.sonuc });
    once = ozetle(i + 1, once, govde);
    ekle.run(i + 1, o.tur, aktor, hedef, o.sonuc, govde, once);
  });
  return db;
}

// Dogrulama zinciri bastan yurur; ilk uyusmazligin sirasini dondurur, yoksa 0.
export function dogrula(db) {
  let once = 'baslangic';
  for (const k of db.prepare('select * from kayit order by sira').all()) {
    once = ozetle(k.sira, once, k.govde);
    if (once !== k.ozet) return k.sira;
  }
  return 0;
}
```

#### Üç Kayıt Politikası

Kaydedilecek olayları seçmenin üç yaygın biçimi vardır ve üçü de savunulabilir görünür: yalnız
durumu değiştiren işlemleri yazmak, yalnız reddedilen yetki isteklerini yazmak, bütün karar
noktalarını yazmak. Aralarındaki farkı ölçmenin yolu hacim değil, **yanıtlanabilen soru
sayısıdır**. Aşağıdaki koşum sekiz hesap sorabilme sorusunu her üç kayda da gerçek sorgu olarak
sorar; soruların hedefleri kaydın kendisinden okunur.

```js
// olcum.mjs — uc kayit politikasi, zincirin yakaladigi ve yakalamadigi, kaydin kendi gizliligi
import { createHash } from 'node:crypto';
import { gunuUret, depoAc, dogrula, ozetle, TURLER, TOHUM } from './kayit.mjs';

const olaylar = gunuUret();
const tam = depoAc(olaylar);
const tek = (s) => tam.prepare(s).get();
const uyeCeza = tek("select hedef from kayit where tur='CEZA_SIL'").hedef;
const silinen = tek("select hedef from kayit where tur='UYE_SIL'").hedef;
const katalog = tek("select hedef from kayit where tur='KATALOG_DUZENLE'").hedef;
const gorevli = tek("select aktor from kayit where tur='GORUNTULE' group by aktor order by count(*) desc").aktor;

const sorular = [
  [`${uyeCeza} uyesinin cezasini kim sildi`, `select aktor from kayit where tur='CEZA_SIL' and hedef='${uyeCeza}'`],
  [`${gorevli} kac ayri uyenin kaydina bakti`, `select distinct hedef from kayit where tur='GORUNTULE' and aktor='${gorevli}'`],
  [`${katalog} kaydini kim duzenledi`, `select aktor from kayit where tur='KATALOG_DUZENLE' and hedef='${katalog}'`],
  ['hangi hesap dortten cok basarisiz giris aldi', "select hedef from kayit where tur='GIRIS_RET' group by hedef having count(*) > 4"],
  ['imza anahtarini kim dondurdu', "select aktor from kayit where tur='DONDURME'"],
  ['dagitimi kim baslatti', "select aktor from kayit where tur='DAGITIM'"],
  [`${uyeCeza} uyesinin verisine kimler bakti`, `select distinct aktor from kayit where hedef='${uyeCeza}' and tur in ('GORUNTULE','DISA_AKTARIM')`],
  [`silinen ${silinen} uyesinin kaydina kim eristi`, `select distinct aktor from kayit where hedef='${silinen}' and tur='GORUNTULE'`],
];

const politikalar = {
  'yalniz yazma': (o) => o.yazar,
  'yalniz yetki reddi': (o) => o.sonuc === 'ret',
  'butun kararlar': () => true,
};

const s = (x, n) => String(x).padStart(n);
console.log(`tohum ${TOHUM}: ${TURLER.length} karar noktasi, ${olaylar.length} olay/gun`);
console.log(`${'politika'.padEnd(20)}${s('kayit', 7)}${s('bayt', 8)}${s('yanitlanan soru', 17)}${s('yanitsiz soru', 18)}`);
const yanit = {};
for (const [ad, suzgec] of Object.entries(politikalar)) {
  const db = depoAc(olaylar.filter(suzgec));
  const bayt = db.prepare('select sum(length(govde)) as b, count(*) as n from kayit').get();
  yanit[ad] = sorular.map(([, sql]) => db.prepare(sql).all().length > 0);
  const say = yanit[ad].filter(Boolean).length;
  console.log(`${ad.padEnd(20)}${s(bayt.n, 7)}${s(bayt.b, 8)}${s(`${say}/8`, 17)}`
    + `${s(yanit[ad].map((v, i) => (v ? '' : i + 1)).filter(Boolean).join(',') || '-', 18)}`);
}
console.log(sorular.map(([ad], i) => `  ${i + 1}. ${ad}`).join('\n'));

// Zincir: govdeyi degistirmek, ozeti de tazelemek, kuyrugu kesmek.
const bozuk = depoAc(olaylar);
const kurban = 500;
const eski = bozuk.prepare('select govde, ozet from kayit where sira=?').get(kurban);
const yeni = eski.govde.replace('"sonuc":"kabul"', '"sonuc":"ret"');
bozuk.prepare('update kayit set govde=? where sira=?').run(yeni, kurban);
console.log(`\nzincir temizken dogrulama: ${dogrula(tam)}`);
console.log(`${kurban}. kaydin govdesi degistirildi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`);
const onceki = bozuk.prepare('select ozet from kayit where sira=?').get(kurban - 1).ozet;
bozuk.prepare('update kayit set ozet=? where sira=?').run(ozetle(kurban, onceki, yeni), kurban);
console.log(`ozet de tazelendi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`);

const kesik = depoAc(olaylar);
kesik.prepare('delete from kayit where sira > ?').run(olaylar.length - 25);
console.log(`son 25 kayit silindi -> dogrulama ${dogrula(kesik)} (0 = uyusmazlik yok)`);
for (const capa of [Infinity, 500, 100, 10]) {
  const sessiz = capa === Infinity ? olaylar.length : olaylar.length % capa;
  const ad = capa === Infinity ? 'capa yok       ' : `capa her ${s(capa, 3)} kayitta`;
  console.log(`${ad} -> bu kosumda ${s(sessiz, 4)} kayit sessizce silinebilir, en kotu ${capa === Infinity ? sessiz : capa - 1}`);
}

// Kaydin kendi gizliligi.
const kisisel = tam.prepare("select count(*) as n from kayit where aktor like 'U-%' or hedef like 'U-%'").get().n;
const gecmis = tam.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all();
const tuzla = (tuz) => (d) => (d.startsWith('U-') ? `T-${createHash('sha256').update(tuz + d).digest('hex').slice(0, 8)}` : d);
const takma = depoAc(olaylar, tuzla('donem-1'));
const takmaGecmis = takma.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all();
const donem2 = new Set(gecmis.map((g) => tuzla('donem-2')(g.aktor)));
const ortak = takmaGecmis.filter((g) => donem2.has(g.aktor)).length;
console.log(`\nkisi tanimlayan kayit ${kisisel}/${olaylar.length} (%${(100 * kisisel / olaylar.length).toFixed(1)})`);
console.log(`odunc gecmisi kurulabilen uye: acik kimlikle ${gecmis.length}, en uzun liste ${gecmis[0].n} kayit`);
console.log(`ayni olcum takma adli kayitta: ${takmaGecmis.length} uye, en uzun liste ${takmaGecmis[0].n} kayit`);
console.log(`donem tuzu degistiginde iki donemin ortak takma adi: ${ortak}`);

const bayt = tam.prepare('select sum(length(govde)) as b from kayit').get().b;
for (const gun of [30, 180, 3650]) {
  console.log(`saklama ${s(gun, 4)} gun -> ${s(olaylar.length * gun, 9)} kayit, `
    + `${s((bayt * gun / 1048576).toFixed(1), 7)} MiB, kisi-gun ${gecmis.length * gun}`);
}
```

```
tohum 4231: 12 karar noktasi, 3012 olay/gun
politika              kayit    bayt  yanitlanan soru     yanitsiz soru
yalniz yazma            848   52494              4/8           2,4,7,8
yalniz yetki reddi       60    3752              1/8     1,2,3,5,6,7,8
butun kararlar         3012  186509              8/8                 -
  1. U-12 uyesinin cezasini kim sildi
  2. G-1 kac ayri uyenin kaydina bakti
  3. K-834 kaydini kim duzenledi
  4. hangi hesap dortten cok basarisiz giris aldi
  5. imza anahtarini kim dondurdu
  6. dagitimi kim baslatti
  7. U-12 uyesinin verisine kimler bakti
  8. silinen U-27 uyesinin kaydina kim eristi

zincir temizken dogrulama: 0
500. kaydin govdesi degistirildi -> ilk uyusmazlik 500. sirada
ozet de tazelendi -> ilk uyusmazlik 501. sirada
son 25 kayit silindi -> dogrulama 0 (0 = uyusmazlik yok)
capa yok        -> bu kosumda 3012 kayit sessizce silinebilir, en kotu 3012
capa her 500 kayitta -> bu kosumda   12 kayit sessizce silinebilir, en kotu 499
capa her 100 kayitta -> bu kosumda   12 kayit sessizce silinebilir, en kotu 99
capa her  10 kayitta -> bu kosumda    2 kayit sessizce silinebilir, en kotu 9

kisi tanimlayan kayit 2964/3012 (%98.4)
odunc gecmisi kurulabilen uye: acik kimlikle 40, en uzun liste 18 kayit
ayni olcum takma adli kayitta: 40 uye, en uzun liste 18 kayit
donem tuzu degistiginde iki donemin ortak takma adi: 0
saklama   30 gun ->     90360 kayit,     5.3 MiB, kisi-gun 1200
saklama  180 gun ->    542160 kayit,    32.0 MiB, kisi-gun 7200
saklama 3650 gun ->  10993800 kayit,   649.2 MiB, kisi-gun 146000
```

#### Sekiz Sorunun Yanıtı

Yalnız yazma işlemlerini kaydeden politika günlük 3.012 olayın 848'ini tutar ve sekiz sorunun
dördünü yanıtlar. Yanıtsız kalan dördü aynı sınıftandır: hepsi bir **okumayı** sorar. Bir görevlinin
kaç üyenin kaydına baktığı, bir üyenin verisine kimlerin eriştiği, silinen bir üyenin kaydına
silinmeden önce kimin girdiği — bunların hiçbiri durumu değiştirmez, dolayısıyla hiçbiri yazılmaz.
Kişisel veriye erişimin izlenebilirliği sorulduğunda bu politika yapısal olarak yanıtsızdır ve
yapılandırmasında bunu belirten bir işaret yoktur.

Yalnız reddedilen istekleri kaydeden politika 60 kayıtla en ucuzudur ve sekizde bir yanıt verir.
Yanıtladığı tek soru başarısız girişlerle ilgilidir. Bu politikanın sessiz varsayımı şudur:
kötüye kullanım reddedilmiş isteklerde görünür. Oysa yetkisi olan bir görevlinin dokuz yüz kayıt
görüntülemesi hiç reddedilmez ve bu kayıtta hiç görünmez.

Bütün karar noktalarını yazmak sekizde sekiz verir ve bedeli yaklaşık üç buçuk katı hacimdir
(186.509 bayta karşılık 52.494). Hacim farkının nereden geldiği önemlidir: 3.012 olayın
2.100'ü giriş ve
görüntülemedir; yani izlenebilirliğin pahalı kısmı tam olarak yalnız yazma politikasının atladığı
kısımdır. Karar noktası saymak bu yüzden bir hacim tartışmasından önce gelir — hangi soruların
yanıtlanabilir olacağı, hangi olayın yazıldığıyla belirlenir.

#### Zincirin Yakalamadığı

Kayıt, sorumluluğu göstermek için tutuluyorsa sonradan değiştirilmediğinin gösterilebilmesi
gerekir. Zincirleme özet bunu kısmen verir. Beş yüzüncü kaydın gövdesindeki sonuç alanı `kabul`
yerine `ret` yapıldığında doğrulama tam o sırada durur. Değiştiren kişi o kaydın özetini de
yeniden hesaplarsa uyuşmazlık bir sonraki kayda kayar (501): her özet bir öncekini kapsadığı için
tek bir kaydı düzeltmek ondan sonraki bütün kayıtları yeniden yazmayı gerektirir.

Kuyruk kesme farklıdır. Son yirmi beş kayıt silindiğinde doğrulama **sıfır** döndürür — yani hiçbir
uyuşmazlık yoktur. Kalan zincir kusursuzdur, çünkü zincir yalnızca kendisinin **iç tutarlılığını**
gösterir; kaç kaydın var olması gerektiğini bilmez. Bir kaydın silindiğini anlamanın tek yolu,
zincirin bir noktadaki özetini kaydın dışına, başka bir sorumluluğun altına yazmaktır. Çapa
dönemi bu boşluğun ölçüsüdür: çapa yoksa günün 3.012 kaydının tamamı sessizce silinebilir, beş
yüzde bir çapayla en kötü 499, onda bir çapayla en kötü 9 kayıt. Yanlış yapılandırma burada
kaydın **olmaması**dır ve olmayan kayıt hiçbir doğrulamayı kırmaz.

#### Kaydın Kendi Riski

Bu günün kayıtlarının **%98,4'ü** bir kişiyi tanımlıyor. Denetim kaydı, tanımı gereği kimin ne
yaptığını yazar; dolayısıyla toplandığı anda sistemin en yoğun kişisel veri kümesi hâline gelir.
Ödünç kayıtlarından kırk üyenin tamamının okuma geçmişi kurulabiliyor, en uzun liste on sekiz
kayıt. Kütüphane sisteminde bu, üyelerin ne okuduğunun listesidir ve ödünç servisinin kendi
veritabanında iade sonrası silinmiş olsa bile denetim kaydında durmaya devam eder.

Üye kimliğini tuzlu bir özetle takma adlandırmak bu ölçüyü **hiç değiştirmiyor**: yine kırk üye,
yine on sekiz kayıtlık en uzun liste. Takma ad kimliği gizler, **bağlantıyı** gizlemez; aynı takma
ad aynı kişinin bütün kayıtlarını birbirine bağlamaya devam eder. Bağlantıyı kesen tek ayar tuzun
dönem başına değişmesidir ve ölçüsü son satırdadır: iki dönemin ortak takma adı sıfırdır. Bedeli de
oradadır — dönem tuzu atıldıktan sonra yedinci soru eski dönemler için sorulamaz, çünkü sorunun
hedefi olan üyenin o dönemdeki takma adı yeniden hesaplanamaz.

Saklama süresi son ayardır ve iki ölçüyü birden büyütür. Otuz gün 5,3 MiB ve 1.200 kişi-gün eder;
on yıl 649,2 MiB ve 146.000 kişi-gün. Hacim tarafı ucuzdur — on yıllık kayıt bir gigabaytın
altındadır ve bu yüzden saklama süresini uzatmak hiçbir yerde bir uyarı üretmez. Artan şey
maliyetin öteki yarısıdır: on yıl saklanan bir denetim kaydı, on yıllık okuma geçmişini bir arada
tutan bir veri kümesidir ve sızdığında sızan şey isteklerin listesi değil, kişilerin listesidir.

#### Özet

- Denetim kaydı, denetim izinden amacıyla ayrılır: iz durumu yeniden kurar, kayıt bir kararın
  sorumlusunu gösterir ve bu yüzden kendi değiştirilemezliğini de kanıtlamak zorundadır.
- Yalnız yazma işlemlerini kaydetmek sekiz sorunun dördünü yanıtsız bıraktı ve yanıtsız kalanların
  hepsi bir okumayı soruyordu; yalnız reddedilen istekleri kaydetmek sekizde bir verdi.
- Bütün karar noktalarını yazmak sekizde sekiz verdi ve hacmi dört katına çıkardı; farkın tamamı
  giriş ile görüntüleme kayıtlarından geliyor.
- Zincirleme özet gövde değişikliğini 500. sırada, özeti tazelenmiş değişikliği 501. sırada
  yakaladı; son yirmi beş kaydın silinmesini hiç yakalamadı. Çapa dönemi bu sessiz boşluğun
  ölçüsüdür: çapasız 3.012, beş yüzde bir çapayla en kötü 499 kayıt.
- Kayıtların %98,4'ü bir kişiyi tanımlıyor ve kırk üyenin tamamının okuma geçmişi kurulabiliyor;
  takma adlandırma bu sayıyı değiştirmedi, yalnız dönemsel tuz iki dönem arasındaki bağı sıfırladı.
- Saklama süresini on yıla çıkarmak 649,2 MiB ve 146.000 kişi-gün eder; hacim uyarı üretmeyecek
  kadar küçüktür, kişi-gün ise saklama kararının asıl bedelidir.

#### Sonraki Adım

Denetim kaydı sonradan sorar ve iyi yanıt verir, ama hiçbir şeyi durdurmaz. Bu günün kaydında bin
iki yüz giriş, altmış başarısız giriş ve dokuz yüz görüntüleme var; kayıt bunların kimden geldiğini
yazar, birinin bu sayıya **ulaşmaması gerektiğini** söylemez. Kırk üyelik bir sistemde altmış
başarısız girişin tek bir hesapta toplanması ile kırk hesaba dağılması aynı kayıt hacmini üretir ve
ikisi bambaşka şeylerdir. Sonraki ders bu ayrımı ele alır: sınırın kime uygulandığını, yanıtın
kendisinin bir hesabın var olup olmadığını nasıl ele verdiğini ve kilitlemenin doğru hesabı ne
kadar dışarıda bıraktığını sayar.

### Kötüye Kullanım Savunması

Source: https://academia.sh/tr/kurslar/backend-uretim/kotuye-kullanim-savunmasi

Kötüye kullanım savunmasının iki yapılandırma kararı: sınırın hangi anahtara uygulandığı (istemci, hesap, kaynak) ve yanıtın bir kaydın varlığını ele verip vermediği; kilitlemenin durdurduğu deneme ile dışarıda bıraktığı doğru üye aynı koşumda sayılır.

Denetim kaydı sonradan sorar ve iyi yanıt verir, ama hiçbir şeyi durdurmaz. Ödünç sisteminin
dışarıya açık uçlarına gelen isteklerin bir bölümü ne bir açığı zorlar ne bir kural ihlal eder;
yalnızca **çok fazladır** — bir hesabın parolasını arayan denemeler, üye kimliklerini sırayla
yoklayan istekler, katalogun tamamını çeken bir istemci. Bunları durduran düzenek hız
sınırlamadır ve algoritması ile yerleşimi bu müfredatın önceki kurslarında ölçüldü; burada ikisi
de **girdi olarak alınır**. Bu dersin ölçtüğü iki karar başkadır: sınırın **kime** uygulandığı ve
uygulamanın **ne yanıt verdiği**.

#### Sınır Kime Uygulanıyor

**BG18 — trafik dört bileşenden oluşur:** kırk üyenin olağan isteği (on ikisi kütüphane şubesinin
ortak çıkış adresini paylaşır), tek bir hesabı hedefleyen iki yüz dağıtık deneme, tek bir adresten
gelen üç yüz kimlik yoklaması ve kimliği doğrulanmış tek bir hesaptan gelen iki bin kayıt çekme.
Sınır değeri ve algoritma beş düzende de aynıdır; değişen tek şey sayacın hangi alana bağlandığıdır.

```js
// anahtar.mjs — sinir kime uygulaniyor: istemci, hesap, hedef
const SINIR = 20;          // pencere basina istek; algoritma ve yerlesim girdi olarak alinir

const istek = [];
const ekle = (n, y) => { for (let i = 0; i < n; i++) istek.push(y(i)); };
// Iyi trafik: 40 uye, her biri 1 giris (kimliksiz) + 5 kimlikli istek; 12'si sube adresini paylasiyor.
for (let u = 1; u <= 40; u++) {
  const adres = u <= 12 ? 'A-sube' : `A-uye-${u}`;
  ekle(1, () => ({ adres, hesap: null, hedef: `U-${u}`, kotu: false }));
  ekle(5, () => ({ adres, hesap: `U-${u}`, hedef: `U-${u}`, kotu: false }));
}
// Dagitik kaba kuvvet: 200 ayri adres, tek hesap.
ekle(200, (i) => ({ adres: `A-dagitik-${i}`, hesap: null, hedef: 'U-17', kotu: true }));
// Numaralandirma: tek adres, 300 ayri hedef.
ekle(300, (i) => ({ adres: 'A-tarama', hesap: null, hedef: `U-${1000 + i}`, kotu: true }));
// Kazima: kimlikli tek hesap, 2000 ayri kaynak.
ekle(2000, (i) => ({ adres: 'A-kazima', hesap: 'U-39', hedef: `K-${i}`, kotu: true }));

// Bir duzen, istegi bir veya birkac anahtara ve her anahtar icin bir sinira baglar.
const duzenler = {
  'istemci': (r) => [['a', r.adres, SINIR]],
  'hesap': (r) => (r.hesap ? [['h', r.hesap, SINIR]] : []),
  'hedef': (r) => [['t', r.hedef, SINIR]],
  'ucu birden': (r) => [['a', r.adres, SINIR], ['t', r.hedef, SINIR], ...(r.hesap ? [['h', r.hesap, SINIR]] : [])],
  'katmanli': (r) => (r.hesap ? [['h', r.hesap, SINIR]] : [['a', r.adres, SINIR], ['t', r.hedef, SINIR]]),
};

function olc(duzen, normalize = (d) => d, akis = istek) {
  const sayac = new Map();
  let durdurulanKotu = 0, gecenKotu = 0, yanlisKalma = 0;
  for (const r of akis) {
    let dusur = false;
    for (const [tur, ham, sinir] of duzen(r)) {
      const anahtar = `${tur}:${normalize(ham)}`;
      const n = (sayac.get(anahtar) ?? 0) + 1;
      sayac.set(anahtar, n);
      if (n > sinir) dusur = true;
    }
    if (r.kotu) dusur ? durdurulanKotu++ : gecenKotu++;
    else if (dusur) yanlisKalma++;
  }
  return { durdurulanKotu, gecenKotu, yanlisKalma };
}

const s = (x, n) => String(x).padStart(n);
const kotu = istek.filter((r) => r.kotu).length;
console.log(`iyi istek ${istek.length - kotu}, kotu istek ${kotu}, sinir ${SINIR}`);
console.log(`${'anahtar'.padEnd(13)}${s('durdurulan', 12)}${s('gecen', 7)}${s('yanlis kalma', 14)}`);
for (const [ad, duzen] of Object.entries(duzenler)) {
  const r = olc(duzen);
  console.log(`${ad.padEnd(13)}${s(r.durdurulanKotu, 12)}${s(r.gecenKotu, 7)}${s(r.yanlisKalma, 14)}`);
}

// Ayni 200 deneme, hedef adi on ayri yazimla verilirse.
const yazimlar = ['U-17', 'u-17', ' U-17', 'U-017', 'U-17 ', 'u-017', 'U_17', 'u_17', 'U-17\t', 'U-0017'];
const yayilmis = istek.map((r) => (r.kotu && r.hedef === 'U-17'
  ? { ...r, hedef: yazimlar[istek.indexOf(r) % yazimlar.length] } : r));
const duzelt = (d) => d.trim().replace(/_/g, '-').toUpperCase().replace(/^U-0+/, 'U-');
console.log('\nayni 200 deneme, hedef adi on ayri yazimla verilirse (katmanli duzen):');
for (const [ad, n] of [['ham anahtar', (d) => d], ['normalize edilmis', duzelt]]) {
  const r = olc(duzenler.katmanli, n, yayilmis);
  console.log(`  ${ad.padEnd(18)} durdurulan ${r.durdurulanKotu}, gecen ${r.gecenKotu}, yanlis kalma ${r.yanlisKalma}`);
}
```

```
iyi istek 240, kotu istek 2500, sinir 20
anahtar        durdurulan  gecen  yanlis kalma
istemci              2260    240            52
hesap                1985    515             0
hedef                 186   2314             0
ucu birden           2451     49            52
katmanli             2446     54             0

ayni 200 deneme, hedef adi on ayri yazimla verilirse (katmanli duzen):
  ham anahtar        durdurulan 2266, gecen 234, yanlis kalma 0
  normalize edilmis  durdurulan 2446, gecen 54, yanlis kalma 0
```

İlk üç satır her anahtarın tam olarak bir kötüye kullanım biçimini gördüğünü söylüyor. İstemci
anahtarı yoklamayı ve kazımayı durdurur (2.260), dağıtık denemeyi hiç görmez ve şubenin ortak
adresinden gelen elli iki doğru isteği düşürür. Hesap anahtarının yanlış kalması sıfırdır ama
kimliksiz gelen beş yüz isteğe hiç uygulanmaz. Hedef anahtarı dağıtık denemeyi yakalayan tek
anahtardır ve yalnız yüz seksen altı istek durdurur.

Son iki satır kararın kendisidir. Üç anahtarı birden uygulamak 2.451 istek durdurur ve şubenin
elli iki isteğini yine düşürür. Katmanlı düzen beş istek daha az durdurur (2.446) ve yanlış
kalmayı **sıfıra** indirir: kimliği doğrulanmış istek en dar anahtara, yani hesaba bağlanır;
kimliksiz istek hem adrese hem hedefe bağlanır. Aynı sınır değeri, aynı algoritma, aynı yerleşim
— fark yalnızca sayacın hangi alana bakacağı kararındadır.

Son bloktaki iki satır bu kararın sessiz yanlışıdır. Anahtar, istekle gelen bir tanımlayıcıdan
türetiliyorsa ve **normalize edilmiyorsa**, aynı hesabı on ayrı yazımla yoklayan iki yüz deneme on
ayrı sayaca dağılır: durdurulan 2.446'dan 2.266'ya iner, yani yüz seksen deneme sınırın altından
geçer. Hiçbir yerde hata yoktur; sayaç doğru sayar, sınır doğru uygulanır, yalnızca on ayrı şeyi
sayar.

#### Yanıtın Kendisi

Sınır, çok fazla isteği durdurur. Numaralandırmanın sorunu ise **az sayıda isteğin** de yeterli
olmasıdır: kırk yoklama, kırk kimliğin hangisinin var olduğunu söylüyorsa sınır hiç devreye
girmeden bilgi dışarı çıkmış olur. Bunu belirleyen şey yanıtın biçimidir. Aşağıdaki koşum aynı ucun
üç sürümünü gerçekten ayağa kaldırır ve kırk kimliği yoklar; kimlikler dönüşümlü sırayla gider.

```js
// yanit.mjs — yanit bicimi numaralandirmayi mumkun kiliyor mu: imza farki ve sure farki
import { createServer } from 'node:http';
import { createHash } from 'node:crypto';

const TUR = 60000;                 // dogrulayicinin yaptigi sabit is: ozet turu
const ESIK = 5;                    // ms; iki kumeyi ayirmak icin sabit esik (bkz. metin)
const isYap = () => { let d = 'x'; for (let i = 0; i < TUR; i++) d = createHash('sha256').update(d).digest('hex'); };
const uyeler = new Set(Array.from({ length: 20 }, (_, i) => `U-${i + 1}`));
const ALINDI = '{"durum":"istek alindi"}';

const surumler = {
  'ayri yanit': (v) => { if (v) isYap(); return v ? [202, '{"durum":"kod gonderildi"}'] : [404, '{"hata":"kayit yok"}']; },
  'ayni yanit': (v) => { if (v) isYap(); return [202, ALINDI]; },
  'ayni yanit + sabit is': () => { isYap(); return [202, ALINDI]; },
};

const sunucu = createServer((istek, yanit) => {
  const [kod, govde] = surumler[istek.headers['x-surum']](uyeler.has(istek.headers['x-kimlik']));
  yanit.writeHead(kod, { 'content-type': 'application/json' });
  yanit.end(govde);
});
sunucu.listen(8952, '127.0.0.1');
const TABAN = 'http://127.0.0.1:8952/parola-sifirlama';

// Deneme sirasi donusumlu: var olan ve olmayan kimlikler sirayla, boylece surukleme iki kumeyi
// birden etkiler ve sure farki yalnizca dalin isinden gelir.
const kimlikler = Array.from({ length: 20 }, (_, i) => [`U-${i + 1}`, `U-${i + 21}`]).flat();
const dene = async (surum, kimlik) => {
  const t = performance.now();
  const y = await fetch(TABAN, { method: 'POST', headers: { 'x-surum': surum, 'x-kimlik': kimlik } });
  const govde = await y.text();
  return { sure: performance.now() - t, imza: `${y.status}|${govde}`, gercek: uyeler.has(kimlik) };
};
for (let i = 0; i < 5; i++) await dene('ayni yanit + sabit is', 'U-1');   // isinma

const s = (x, n) => String(x).padStart(n);
console.log(`${'surum'.padEnd(23)}${s('imza var/yok', 14)}${s('imza ayrik', 12)}`
  + `${s('sureyle ayirt', 15)}${s('ozet turu var/yok', 19)}`);
for (const surum of Object.keys(surumler)) {
  const olcum = [];
  for (const k of kimlikler) olcum.push(await dene(surum, k));
  const imza = (kume) => new Set(olcum.filter((o) => o.gercek === kume).map((o) => o.imza));
  const [iVar, iYok] = [imza(true), imza(false)];
  const ayrik = [...iVar].every((x) => !iYok.has(x));
  const dogru = olcum.filter((o) => (o.sure > ESIK) === o.gercek).length;
  const is = surum === 'ayni yanit + sabit is' ? `${TUR}/${TUR}` : `${TUR}/0`;
  console.log(`${surum.padEnd(23)}${s(`${iVar.size}/${iYok.size}`, 14)}${s(String(ayrik), 12)}`
    + `${s(`${dogru}/40`, 15)}${s(is, 19)}`);
}
sunucu.close();
console.log(`\nsure esigi ${ESIK} ms sabittir; 20/40 rastgele tahmin duzeyidir`);
console.log(`ozet turu sayisi kosumdan bagimsizdir, sureler degildir`);
```

```
surum                    imza var/yok  imza ayrik  sureyle ayirt  ozet turu var/yok
ayri yanit                        1/1        true          40/40            60000/0
ayni yanit                        1/1       false          40/40            60000/0
ayni yanit + sabit is             1/1       false          20/40        60000/60000

sure esigi 5 ms sabittir; 20/40 rastgele tahmin duzeyidir
ozet turu sayisi kosumdan bagimsizdir, sureler degildir
```

İlk sürüm yanıtı ikiye ayırır: var olan kimliğe 202 ve "kod gönderildi", olmayana 404 ve "kayıt
yok". İmza kümeleri ayrıktır, dolayısıyla kırk yoklamanın kırkı doğru sınıflanır. Bu, bir açık
değil bir **yanıt biçimi kararıdır** ve genellikle kullanıcıya yardım etme amacıyla verilir.

İkinci sürüm iki dala da aynı gövdeyi ve aynı durum kodunu döndürür; imza ayrımı kapanır. Süre
ayrımı ise açık kalır ve kırk yoklamanın kırkı yine doğru sınıflanır, çünkü yalnız var olan
kimlikte doğrulayıcı çalışır. Koşumdan bağımsız nicelik son sütundadır: bir dalda altmış bin özet
turu, ötekinde sıfır. Bu koşumda ortancalar yaklaşık 18,5 milisaniyeye karşılık 1,4 milisaniyedir;
oranın kendisi makineye bağlıdır, varlığı bağlı değildir.

Üçüncü sürüm işi iki dalda da yapar ve süreyle ayırt etme rastgele tahmin düzeyine iner (20/40).
Yanıtı eşitlemek tek başına yetmez; **yapılan iş de eşitlenmelidir**. Bu, ilk iki sürümün hiçbir
hata üretmediği anlamına gelir — üçü de çalışır, üçü de doğru yanıt verir ve aralarındaki fark
yalnızca yoklayarak ölçülür.

#### Kilitlemenin Yanlış Kalması

Kaba kuvvete karşı ilk akla gelen önlem, üst üste birkaç hatadan sonra hesabı kilitlemektir.
**BG19 — bir giriş oturumundaki hatalı deneme sayısı** yukarıdaki dağılımdan gelir ve kırk üyenin
otuz günü üretilir. **BG20 — saldırgan üç saniyede bir deneme yapar**, ölçüm penceresi dokuz yüz
saniyedir.

```js
// kilit.mjs — kaba kuvvette kilitlemenin yanlis kalma maliyeti
const TOHUM = 9137;
let durum = TOHUM;
const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648;

const PENCERE = 900;        // sn: olcum penceresi
const HIZ = 3;              // sn: saldirganin iki denemesi arasi
const ESIK = 5;             // ust uste hatali deneme
const KILIT = 900;          // sn: kilit suresi
const UYE = 40, GUN = 30;

// BG19: bir giris oturumunda basarisiz deneme dagilimi (dogru uyenin kendi hatasi)
const DAGILIM = [[0, 0.72], [1, 0.15], [2, 0.06], [3, 0.03], [4, 0.02], [6, 0.015], [8, 0.005]];
const oturumlar = [];
for (let i = 0; i < UYE * GUN; i++) {
  let x = rast(), hata = 0;
  for (const [n, p] of DAGILIM) { if ((x -= p) < 0) { hata = n; break; } }
  oturumlar.push(hata);
}
const yanlisKalma = oturumlar.filter((h) => h >= ESIK).length;

// Artan beklemede n. denemeden once 2^(n-1) sn beklenir; pencereye kac deneme sigar.
let artan = 0;
for (let t = 0, n = 1; t + 2 ** (n - 1) <= PENCERE; n++) { t += 2 ** (n - 1); artan = n; }

const politikalar = [
  ['sinir yok', PENCERE / HIZ, 0, 0, 0, 0],
  ['kilit, 15 dk', ESIK, yanlisKalma, 0, UYE, KILIT],
  ['kilit, kalici', ESIK, yanlisKalma, yanlisKalma, UYE, -1],
  ['artan bekleme', artan, 0, 0, 0, 2 ** (artan - 1)],
];

const s = (x, n) => String(x).padStart(n);
console.log(`tohum ${TOHUM}: ${oturumlar.length} giris oturumu (${UYE} uye x ${GUN} gun), pencere ${PENCERE} sn`);
console.log(`${'politika'.padEnd(15)}${s('gecen deneme', 14)}${s('kilitlenen dogru', 18)}`
  + `${s('gorevli acma', 14)}${s('hedefli kilit', 15)}${s('dogru uye beklemesi', 21)}`);
for (const [ad, gecen, kilitli, acma, hedefli, bekleme] of politikalar) {
  console.log(`${ad.padEnd(15)}${s(gecen, 14)}${s(kilitli, 18)}${s(acma, 14)}${s(hedefli, 15)}`
    + `${s(bekleme === -1 ? 'gorevli acar' : `${bekleme} sn`, 21)}`);
}
console.log(`\nhedefli kilitleme ${UYE * ESIK} istekle ${UYE} uyeyi disarida birakir`);
console.log(`ust uste ${ESIK} ve uzeri hata iceren oturum ${yanlisKalma}/${oturumlar.length}`
  + ` (%${(100 * yanlisKalma / oturumlar.length).toFixed(1)}); tamami dogru uyedir`);
```

```
tohum 9137: 1200 giris oturumu (40 uye x 30 gun), pencere 900 sn
politika         gecen deneme  kilitlenen dogru  gorevli acma  hedefli kilit  dogru uye beklemesi
sinir yok                 300                 0             0              0                 0 sn
kilit, 15 dk                5                30             0             40               900 sn
kilit, kalici               5                30            30             40         gorevli acar
artan bekleme               9                 0             0              0               256 sn

hedefli kilitleme 200 istekle 40 uyeyi disarida birakir
ust uste 5 ve uzeri hata iceren oturum 30/1200 (%2.5); tamami dogru uyedir
```

Kilitleme işini yapar: dokuz yüz saniyede geçen deneme sayısı üç yüzden beşe iner. Bedeli iki
yerde birden görünür. Birincisi ayda otuz doğru üye oturumudur — bin iki yüz oturumun yüzde
2,5'i — ve bunların hiçbiri saldırgan değildir; kalıcı kilitte otuzu da bir görevli işine dönüşür.
İkincisi daha ağırdır: kilit, hesap adını bilen birine bir **kapatma düğmesi** verir. Kırk hesabın
her birine beşer deneme, yani iki yüz istek, kırk üyenin tamamını dışarıda bırakır. Kırk üyeyi
dışarıda bırakmak için hiçbir parolayı bilmek gerekmez.

Artan bekleme aynı pencerede dokuz denemeye izin verir — kilitten dört fazla, sınırsız düzenden
otuz üç kat az — ve hiçbir doğru üyeyi kilitlemez. Kalan bedel son sütundadır: hedeflenen hesabın
gerçek sahibi, saldırganın yaktığı denemelerden sonra 256 saniye bekler. Yani gecikme de bir
yanlış kalma üretir, ama süresi sonludur ve görevli işi gerektirmez. İki savunma arasındaki seçim
"hangisi daha güvenli" sorusu değil, **yanlış kalmanın kimin üzerinde ve ne kadar kalacağı**
sorusudur.

#### Özet

- Sınırın algoritması ve yerleşimi bu dersin girdisidir; ölçülen karar sınırın hangi anahtara
  uygulandığıdır ve her anahtar kötüye kullanımın yalnız bir biçimini görür.
- İstemci anahtarı 2.260 kötü istek durdurup ortak adres yüzünden 52 doğru isteği düşürdü; katmanlı
  düzen 2.446 durdurup yanlış kalmayı sıfıra indirdi — kimlikli istek hesaba, kimliksiz istek adres
  ile hedefe bağlanarak.
- Anahtar normalize edilmediğinde aynı hesabı on yazımla yoklayan iki yüz deneme on sayaca dağıldı
  ve durdurulan istek 2.446'dan 2.266'ya indi; sayaç doğru çalışıyor, yalnız on ayrı şeyi sayıyor.
- Var olan ve olmayan kimliğe ayrı yanıt vermek kırk yoklamanın kırkını ele verdi; yanıtı
  eşitlemek imza ayrımını kapattı ama süre ayrımı kırkta kırk kaldı, çünkü iş yalnız bir dalda
  yapılıyordu.
- İşi iki dalda da yapmak ayırt etmeyi rastgele tahmin düzeyine (20/40) indirdi; koşumdan bağımsız
  ölçü, iki dalda yapılan özet turu sayısının eşitlenmesidir.
- Kilitleme geçen denemeyi 300'den 5'e indirdi ve ayda 30 doğru üye oturumunu kilitledi; iki yüz
  istekle kırk üyenin tamamı dışarıda bırakılabiliyor. Artan bekleme dokuz denemeye izin verdi,
  kimseyi kilitlemedi ve doğru üyeye 256 saniyelik sonlu bir bekleme bıraktı.

#### Sonraki Adım

Bu konu boyunca kurulan bütün savunmalar **uygulamanın içindedir**. Doğrulayıcı, kodlayıcı, başlık
katmanı, gizli değer deposu, denetim kaydı ve sayaç — hepsi ödünç servisinin kendi sürecinde
çalışır ve hepsi, uygulamanın gördüğü isteğin gerçekten gelen istek olduğunu varsayar. Üretimde bu
varsayım kendiliğinden doğru değildir. Uygulama orada yalnız başına durmaz: önünde bağlantıyı
karşılayan bir vekil, altında süreci ayakta tutan bir yönetici vardır ve ikisinin yapılandırması
uygulamanın gördüğü isteği değiştirir — istemci adresini, gövde boyunu, başlıkları, hatta sürecin
kaç kopya çalıştığını. Bu dersteki sayaçların hangi adresi saydığı da oraya bağlıdır. Sonraki ders
uygulamanın önündeki katmanı ele alır ve aynı kuralı orada ölçer: yanlış yapılandırma hata vermez,
ölçü farkı gösterir.

## Sunucu ve Dağıtım Topolojisi

Uygulamanın üretimde durduğu yer ve değiştirilme biçimi: ters vekilde TLS sonlandırma ile başlık aktarımı, süreç yöneticilerinin yeniden başlatma ve kaynak sınırları, yatay ile dikey ölçeklemenin maliyet farkı, ölçeklemenin önkoşulu olan durumsuz tasarım, oturum taşımanın ve yapışkanlığın etkisi, mavi–yeşil ile aşamalı yayında sıfır kesinti, geriye uyumlu göç adımlarının yayın sırasıyla ilişkisi ve yük testi verisinden kaynak tahmini.

### Ters Vekil Yapılandırması

Source: https://academia.sh/tr/kurslar/backend-uretim/ters-vekil-yapilandirmasi

Ters vekilin uygulamaya ne bıraktığı: TLS sonlandırmadan sonra kaybolan istemci adresi ile şema bilgisinin başlık aktarımıyla geri verilmesi, aktarım kapalıyken hız sınırının tek kovaya düşmesi ve denetim kaydının tek adres göstermesi, yolun vekilde bir kez fazla çözülmesiyle yanlış kaydın başarılı olarak dönmesi, gövde boyu sınırının iki katmanda ayrı durmasının etkin değeri ve kaydı nasıl değiştirdiği.

Önceki konu bütün savunmaları uygulamanın içine kurdu: doğrulayıcı, izin listesi, gizli değer
deposu, denetim kaydı, hız sınırlayıcı. Her biri isteği okuyup bir karar veriyordu ve hepsi tek
bir varsayıma yaslanıyordu — **uygulamanın gördüğü istek, istemcinin gönderdiği istektir.**

Üretimde bu varsayım doğru değildir. Uygulama yalnız başına durmaz: önünde bir ters vekil, altında
bir süreç yöneticisi vardır. İkisi de isteğe dokunur, ikisi de yapılandırılır ve yapılandırmaları
uygulamanın gördüğü isteği değiştirir. Ters vekilin ne olduğu Yük Dengeleyici ve Ters Vekil
dersinde kurulmuş, ölçülmüştü; bu ders bileşeni yeniden tanımlamaz, **ayarlarını** ölçer.

#### Sonlandırmanın Bıraktığı Boşluk

TLS sonlandırma vekilde yapılır: şifreli bağlantı vekilde biter, vekil ile uygulama arasındaki
bağlantı şifresiz taşınır. Bu düzenek uygulamadan iki bilgiyi alır. Birincisi **istemci adresi**:
uygulamanın gördüğü bağlantının öteki ucu artık istemci değil, vekildir. İkincisi **şema**:
uygulamaya gelen bağlantı şifresizdir, isteğin dışarıda şifreli gelip gelmediği bağlantıda yazmaz.

İki bilgi de kaybolmaz, **aktarılır** — vekil isteğe iki başlık ekler. Aktarım bir ayardır ve
kapalıyken uygulama hata vermez; yalnız yanlış bir değer okur.

- **DP1.** Tek makinede bütün istemciler aynı geri döngü adresini paylaşır. Üye adresi vekilde
  kaynak bağlantı noktası bloğundan türetilir; gerçek dağıtımda bu değer doğrudan bağlantıdan gelir.
- **DP2.** Ders şifrelemenin kendisini değil, sonlandırmanın uygulamaya bıraktığı bilgi kaybını
  ölçer.
- **DP3.** Hız sınırı kimlik başına beş istektir ve pencere tek koşumdur; denetim kaydı isteği
  çözülen kimliğe yazar.

Ödünç uygulaması kimliği önce başlıktan, yoksa bağlantıdan alır.

```js
// topoloji/uygulama.mjs — odunc uygulamasi: kimligi, semayi ve govde sinirini istekten okur.
// Kimlik once "x-iletilen-icin" basligindan, yoksa baglantinin kendisinden alinir.
import { createServer } from "node:http";

const [nokta, govdeSiniri] = [Number(process.argv[2]), Number(process.argv[3] ?? 16384)];
const SINIR = 5;                                   // kimlik basina istek
const KATALOG = { "KTP/17": "Bolge Tarihi", "KTP%2F17": "Bolge Tarihi Eki" };
const sayac = new Map();
const kayit = { istek: 0, gecen: 0, durdurulan: 0, kimlik: new Set(), okunanBayt: 0 };

function kimlikCoz(istek) {
  const iletilen = istek.headers["x-iletilen-icin"];
  return iletilen ?? istek.socket.remoteAddress;      // baslik yoksa: baglantinin oteki ucu
}

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/uygulama.mjs <nokta> [govde-siniri]");
else createServer((istek, yanit) => {
  yanit.sendDate = false;
  const yol = new URL(istek.url, "http://yerel").pathname;
  if (yol === "/olcum") {
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ ...kayit, kimlik: kayit.kimlik.size }));
    return;
  }
  const kimlik = kimlikCoz(istek);
  kayit.istek += 1;
  kayit.kimlik.add(kimlik);
  const n = (sayac.get(kimlik) ?? 0) + 1;
  sayac.set(kimlik, n);
  if (n > SINIR) {
    kayit.durdurulan += 1;
    yanit.writeHead(429, { "x-kimlik": kimlik }).end("sinir");
    return;
  }
  kayit.gecen += 1;
  if (istek.headers["x-iletilen-sema"] !== "guvenli") {
    yanit.writeHead(308, { location: yol, "x-kimlik": kimlik }).end("guvenli semaya");
    return;
  }
  if (yol.startsWith("/kitap/")) {
    const kimlikKitap = decodeURIComponent(yol.split("/")[2] ?? "");
    const ad = KATALOG[kimlikKitap];
    if (ad === undefined) yanit.writeHead(404, { "x-kimlik": kimlik }).end("yok");
    else yanit.writeHead(200, { "x-kayit": ad, "x-kimlik": kimlik }).end(ad);
    return;
  }
  if (istek.method === "POST") {
    let bayt = 0;
    istek.on("data", (p) => {
      bayt += p.length;
      kayit.okunanBayt += p.length;
      if (bayt > govdeSiniri) { yanit.writeHead(413, { "x-katman": "uygulama" }).end("buyuk"); istek.destroy(); }
    });
    istek.on("end", () => { if (yanit.writableEnded === false) yanit.writeHead(200, { "x-kimlik": kimlik }).end(String(bayt)); });
    return;
  }
  yanit.writeHead(200, { "x-kimlik": kimlik }).end("odunc");
}).listen(nokta, "127.0.0.1");
```

Vekil dört ayarı bir dizgeden alır: başlık aktarımı, şema aktarımı, yol işleme ve gövde sınırı.
Dördü de bir üründe bir yapılandırma satırıdır; burada bir dizge alanıdır.

```js
// topoloji/vekil.mjs — ters vekil. TLS'i kendinde sonlandirir, arkaya sifresiz baglanir.
// Ayar dizgesi: basliklar=acik|kapali,sema=acik|kapali,yol=gecir|coz,govde=<bayt>
import { createServer, request } from "node:http";

const [nokta, hedef, ayarMetni] = [Number(process.argv[2]), Number(process.argv[3]), process.argv[4] ?? ""];
const A = Object.fromEntries(ayarMetni.split(",").filter(Boolean).map((p) => p.split("=")));
const sayac = { gelen: 0, iletilen: 0, kesilen: 0, okunanBayt: 0 };
// DP1: tek makinede butun istemciler ayni geri dongu adresini paylasir. Uye adresi kaynak
// baglanti noktasi blogundan turetilir; gercek dagitimda bu deger dogrudan baglantidan gelir.
const AG = { 412: "10.4.0.11", 413: "10.4.0.12", 414: "10.4.0.13", 415: "10.4.0.14" };
const istemciAdresi = (s) => AG[Math.floor(s.remotePort / 100)] ?? s.remoteAddress;

if (Number.isInteger(nokta) === false || Number.isInteger(hedef) === false) {
  console.log("kullanim: node topoloji/vekil.mjs <nokta> <hedef> <ayar>");
} else createServer((istekDisi, yanitDisi) => {
  yanitDisi.sendDate = false;
  if (istekDisi.url === "/vekilolcum") { yanitDisi.writeHead(200).end(JSON.stringify(sayac)); return; }
  sayac.gelen += 1;
  const basliklar = { ...istekDisi.headers };
  delete basliklar["x-iletilen-icin"];              // disaridan gelen kimlik iddiasi atilir
  delete basliklar["x-iletilen-sema"];
  if (A.basliklar === "acik") basliklar["x-iletilen-icin"] = istemciAdresi(istekDisi.socket);
  if (A.sema === "acik") basliklar["x-iletilen-sema"] = "guvenli";
  const govdeSiniri = Number(A.govde ?? Infinity);
  const yol = A.yol === "coz" ? decodeURIComponent(istekDisi.url) : istekDisi.url;

  let bayt = 0;
  const istekIc = request({ port: hedef, path: yol, method: istekDisi.method, headers: basliklar }, (yanitIc) => {
    yanitDisi.writeHead(yanitIc.statusCode, yanitIc.headers);
    yanitIc.pipe(yanitDisi);
  });
  sayac.iletilen += 1;
  istekIc.on("error", () => {
    sayac.kesilen += 1;
    if (yanitDisi.headersSent === false) yanitDisi.writeHead(502, { "x-katman": "vekil" }).end("arka uc kesildi");
  });
  istekDisi.on("data", (p) => {
    bayt += p.length;
    sayac.okunanBayt += p.length;
    if (bayt > govdeSiniri) {
      if (yanitDisi.headersSent === false) yanitDisi.writeHead(413, { "x-katman": "vekil" }).end("buyuk");
      istekIc.destroy();
      istekDisi.destroy();
      return;
    }
    istekIc.write(p);
  });
  istekDisi.on("end", () => istekIc.end());
  istekDisi.on("error", () => istekIc.destroy());
}).listen(nokta, "127.0.0.1");
```

Dört üye ayrı kaynak bağlantı noktası bloklarından bağlanır. Üye A on istek gönderir, B, C ve D
ikişer istek — toplam on altı istek, kimlik başına sınır beş.

```js
// topoloji/istemci.mjs — uyeler ayri kaynak baglanti noktalarindan baglanir; kimlik baglantida durur.
// Senaryolar: hiz | sema | yol | govde.   Kullanim: node topoloji/istemci.mjs <nokta> <senaryo> <etiket>
import { request } from "node:http";

const [nokta, senaryo, etiket] = [Number(process.argv[2]), process.argv[3], process.argv[4] ?? "-"];
const UYE = [["A", 41200, 10], ["B", 41300, 2], ["C", 41400, 2], ["D", 41500, 2]];

function bir({ yol = "/odunc", kaynak, yontem = "GET", govde }) {
  return new Promise((coz) => {
    const r = request({ port: nokta, path: yol, method: yontem, agent: false, localPort: kaynak }, (y) => {
      y.resume();
      y.on("end", () => coz({ kod: y.statusCode, katman: y.headers["x-katman"] ?? "-", kayit: y.headers["x-kayit"] ?? "-", yer: y.headers.location }));
    });
    r.on("error", (e) => coz({ kod: 0, katman: e.code, kayit: "-", yer: undefined }));
    if (govde !== undefined) r.end(govde); else r.end();
  });
}

const olcum = async (yol) => JSON.parse(await new Promise((coz) => {
  request({ port: nokta, path: yol, agent: false }, (y) => { let b = ""; y.on("data", (d) => { b += d; }); y.on("end", () => coz(b)); }).end();
}));

if (senaryo === "hiz") {
  let gecen = 0, durdurulan = 0;
  const satir = [];
  for (const [ad, taban, adet] of UYE) {
    let g = 0, d = 0;
    for (let i = 0; i < adet; i += 1) {
      const s = await bir({ kaynak: taban + i });
      if (s.kod === 429) d += 1; else g += 1;
    }
    gecen += g; durdurulan += d;
    satir.push(`${ad}:${g}/${g + d}`);
  }
  const o = await olcum("/olcum");
  console.log(`${etiket.padEnd(24)} kimlik ${String(o.kimlik).padStart(2)}  gecen ${String(gecen).padStart(2)}  durdurulan ${String(durdurulan).padStart(2)}  ${satir.join(" ")}`);
} else if (senaryo === "sema") {
  let adim = 0, kod = 0, yol = "/odunc";
  while (adim < 5) {
    const s = await bir({ yol, kaynak: 41260 + adim });
    kod = s.kod;
    if (s.yer === undefined) break;
    yol = s.yer; adim += 1;
  }
  console.log(`${etiket.padEnd(24)} yonlendirme adimi ${adim}/5  son kod ${kod}`);
} else if (senaryo === "yol") {
  const ISTEK = [["KTP%2F17", "Bolge Tarihi"], ["KTP%252F17", "Bolge Tarihi Eki"]];
  const sonuc = [];
  for (const [i, [kodlu, beklenen]] of ISTEK.entries()) {
    const s = await bir({ yol: `/kitap/${kodlu}`, kaynak: 41270 + i });
    sonuc.push(`${kodlu} -> ${s.kod} ${s.kayit}${s.kod === 200 && s.kayit !== beklenen ? " (yanlis kayit)" : ""}`);
  }
  console.log(`${etiket.padEnd(24)} ${sonuc.join("  |  ")}`);
} else if (senaryo === "govde") {
  const s = await bir({ yontem: "POST", kaynak: 41280, govde: "x".repeat(8192) });
  const v = await olcum("/vekilolcum");
  const u = await olcum("/olcum");
  console.log(`${etiket.padEnd(24)} istemci ${s.kod} (${s.katman})  vekil okudu ${v.okunanBayt}  uygulama okudu ${u.okunanBayt}  uygulama kaydi ${u.istek}`);
} else console.log("kullanim: node topoloji/istemci.mjs <nokta> <senaryo> <etiket>");
```

Dört ayar ikişer değerle koşulur. Uygulama her koşumda yeniden başlatılır, yani sayaçlar sıfırdan
başlar; değişen tek şey vekilin ayar dizgesidir.

```bash
# olc.sh — ayni is dort ayar ikilisiyle. Her kosumda uygulama yeniden baslatilir (sayaclar sifirlanir).
kos() {                                   # kos <ayar> <senaryo> <etiket> [uygulama-govde-siniri]
  node topoloji/uygulama.mjs 8931 "${4:-16384}" & U=$!
  node topoloji/vekil.mjs 8930 8931 "$1" & V=$!
  sleep 0.4
  node topoloji/istemci.mjs 8930 "$2" "$3"
  kill $V $U 2>/dev/null; wait $V $U 2>/dev/null; sleep 0.2
}

kos "basliklar=kapali,sema=acik" hiz   "baslik kapali"
kos "basliklar=acik,sema=acik"   hiz   "baslik acik"
kos "basliklar=acik,sema=kapali" sema  "sema kapali"
kos "basliklar=acik,sema=acik"   sema  "sema acik"
kos "basliklar=acik,sema=acik,yol=gecir" yol "yol gecirildi"
kos "basliklar=acik,sema=acik,yol=coz"   yol "yol vekilde cozuldu"
kos "basliklar=acik,sema=acik,govde=4096"  govde "sinir vekilde (4096)"  16384
kos "basliklar=acik,sema=acik,govde=65536" govde "sinir uygulamada (4096)" 4096
```

```
baslik kapali            kimlik  1  gecen  5  durdurulan 11  A:5/10 B:0/2 C:0/2 D:0/2
baslik acik              kimlik  4  gecen 11  durdurulan  5  A:5/10 B:2/2 C:2/2 D:2/2
sema kapali              yonlendirme adimi 5/5  son kod 308
sema acik                yonlendirme adimi 0/5  son kod 200
yol gecirildi            KTP%2F17 -> 200 Bolge Tarihi  |  KTP%252F17 -> 200 Bolge Tarihi Eki
yol vekilde cozuldu      KTP%2F17 -> 404 -  |  KTP%252F17 -> 200 Bolge Tarihi (yanlis kayit)
sinir vekilde (4096)     istemci 413 (vekil)  vekil okudu 8192  uygulama okudu 0  uygulama kaydi 0
sinir uygulamada (4096)  istemci 413 (uygulama)  vekil okudu 8192  uygulama okudu 8192  uygulama kaydi 1
```

Sayılar belirlenimlidir: istek sayısı, sınır ve gövde boyu koşumdan bağımsız sabitlerdir, süre
ölçülmez.

#### Tek Kova ve Dört Kova

İlk iki satır aynı on altı isteği aynı hız sınırından geçiriyor. Fark yalnız bir ayarda.

Aktarım kapalıyken uygulamanın denetim kaydında **bir** kimlik var: bütün istekler vekilin adresine
yazılmış. Hız sınırı da tek kovaya düşüyor. Kovayı ilk gelen tüketiyor — üye A beş istek geçiriyor,
kalan beşi durduruluyor, ardından gelen B, C ve D'nin **altı isteğinin tamamı** durduruluyor.
Toplam: beş geçen, on bir durdurulan.

Aktarım açıkken kimlik sayısı dörde çıkıyor. A yine beş istek geçiriyor ve beşi durduruluyor;
B, C ve D altı isteğin altısını da geçiriyor. Toplam: on bir geçen, beş durdurulan.

İki koşumda **kötüye kullanan üyenin geçen istek sayısı aynı**: beş. Değişen, iyi üyelerin
sonucudur — aktarım kapalıyken altı iyi istek durduruldu, açıkken sıfır. Altı **yanlış kalma**,
tek bir ayardan geliyor ve hiçbiri hata olarak görünmüyor; istemci geçerli bir 429 alıyor.

Denetim kaydı tarafındaki sonuç daha uzun ömürlüdür: kayıtta tek adres duruyor. Bir olayın hangi
üyeden geldiği sorulduğunda kayıt yanıt veremez ve bunu da söylemez, çünkü kayıt eksik değil,
**yanlış doludur**.

#### Beş Adımlık Döngü

Üçüncü ve dördüncü satır şema aktarımını ölçüyor. Uygulama isteğin şifreli gelmediğini görünce
güvenli şemaya yönlendiriyor — bu, uygulama katmanında sık bir kuraldır.

Şema aktarımı kapalıyken istemci beş yönlendirme adımının beşini de tüketiyor ve elinde yalnız bir
308 kalıyor. Döngü kapalıdır: istemci güvenli şemaya gidiyor, vekil TLS'i yine sonlandırıyor,
uygulamaya yine şifresiz bir bağlantı geliyor ve uygulama yine yönlendiriyor. Aktarım açıkken adım
sayısı sıfır, kod 200.

Bu, yanlış ayarın **sessiz olmayan** ucudur: kullanıcı bir hata görür. Yine de kayıtlarda hata
yoktur; uygulama her adımda doğru çalışmış, doğru bir yönlendirme üretmiştir.

#### Bir Kez Fazla Çözmek

Beşinci ve altıncı satır yol işlemeyi ölçüyor. Katalogda iki ayrı kayıt var: kimliği `KTP/17` olan
kitap ve kimliği `KTP%2F17` olan eki. İlki `KTP%2F17` diye, ikincisi `KTP%252F17` diye kodlanır.

Vekil yolu olduğu gibi geçirdiğinde ikisi de doğru kayda ulaşıyor. Vekil yolu bir kez çözdüğünde
iki isteğin ikisi de kayıyor. Birincisi `/kitap/KTP/17` hâline geliyor; uygulama yol parçalarını
ayırdığında kitap kimliği `KTP` oluyor ve yanıt 404. İkincisi `/kitap/KTP%2F17` hâline geliyor;
uygulama bunu kendi payına bir kez daha çözüyor, `KTP/17` buluyor ve **başka bir kitabı** 200 ile
döndürüyor.

İki sonuç arasındaki fark önemlidir. 404 görünür bir arızadır. 200 ile dönen yanlış kayıt görünmez:
istemci geçerli bir yanıt, geçerli bir kitap adı alır — yalnız istediği kitap değildir. Çözme
işleminin **kaç kez** yapıldığı bir ayardır ve iki katman arasında bölünmüştür.

#### İki Sınır, Tek Etkin Değer

Son iki satır sekiz kilobaytlık bir gövdeyi iki farklı sınır yerleşiminden geçiriyor. Her iki
yerleşimde de istemci 413 alıyor; etkin sınır her zaman ikisinin küçüğüdür. Değişen, faturanın ve
kaydın yeridir.

Sınır vekildeyken vekil 8192 baytı okuyor, uygulamaya **sıfır** bayt geçiyor ve uygulamanın denetim
kaydında **sıfır** istek görünüyor. Uygulamanın 16384'lük izni ölü bir ayardır: hiçbir zaman
uygulanmaz. Reddedilen istek uygulama için hiç olmamıştır.

Sınır uygulamadayken aynı 8192 bayt **iki kez** okunuyor — bir kez vekilde, bir kez uygulamada — ve
uygulamanın kaydında bir istek görünüyor. Ret aynı, maliyet iki katı, kayıt dolu.

İki satır aynı soruyu iki yönden yanıtlıyor: sınırın hangi katmanda durduğu isteğin reddedilip
reddedilmeyeceğini değil, **kimin çalıştığını ve kimin gördüğünü** belirler.

#### Ayarın Yeri

Dört ayarın hepsi tek bir ölçüm tablosunda göründü, ama kodda dört ayrı yerde duruyorlar.

| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Başlık aktarımı | vekil | 1 (her vekil kopyası) | tek kimlik, altı yanlış kalma, yanlış dolu denetim kaydı |
| Şema aktarımı | vekil + uygulama kuralı | 2 | beş adımlık yönlendirme döngüsü |
| Yol işleme | vekil + uygulama çözmesi | 2 | 200 ile dönen yanlış kayıt |
| Gövde sınırı | vekil + uygulama | 2 | ölü ayar ya da iki kez okunan gövde |

Dört satırın üçünde ayar **iki katmana bölünmüştür** ve doğru davranış ikisinin uyumundan çıkar.
Bir katman tek başına değiştirildiğinde uygulama çalışmaya devam eder; ölçü değişir. Vekil kopyası
sayısı arttıkça birinci satırın "kaç yerde" değeri de artar: aktarımı açık unutulan tek bir kopya,
oradan geçen bütün istekleri tek kimliğe yazar.

#### Özet

- TLS sonlandırma uygulamadan istemci adresini ve şemayı alır; ikisi de başlıkla aktarılır ve
  aktarım bir ayardır.
- Başlık aktarımı kapalıyken uygulamanın denetim kaydında bir kimlik göründü, açıkken dört; aynı
  on altı istekten beşi geçti ve on biri durduruldu, açıkken on biri geçti ve beşi durduruldu.
- Kötüye kullanan üyenin geçen istek sayısı iki koşumda da beşti; ayarın bedelini iyi üyeler ödedi
  ve altı istek yanlışlıkla durduruldu.
- Şema aktarımı kapalıyken istemci beş yönlendirme adımının beşini de tüketti ve 200 alamadı;
  açıkken sıfır adımda 200 aldı.
- Yol vekilde bir kez çözüldüğünde iki isteğin biri 404 aldı, biri **başka bir kitabı** 200 ile
  aldı; ikinci sonuç istemci tarafında hatasız görünür.
- Gövde sınırı vekildeyken uygulamaya sıfır bayt geçti ve denetim kaydında sıfır istek göründü;
  uygulamadayken aynı 8192 bayt iki kez okundu ve kayda bir istek düştü.

#### Sonraki Adım

Bu ders uygulamanın **önündeki** katmanı yapılandırdı. Ölçümlerin hepsi tek bir şeyi varsayıyordu:
uygulama süreci ayaktaydı. Sekiz koşumun her birinde süreç elle başlatıldı, işi bitince elle
durduruldu ve arada hiç çökmedi. Üretimde süreci başlatan, çöktüğünde yeniden başlatan ve ne kadar
bellek kullanabileceğine karar veren başka bir bileşen vardır. Onun ayarları da sessizdir ve
sonuçları vekilinkinden daha serttir: yanlış bir yeniden başlatma politikası çöken bir süreci
saniyede onlarca kez ayağa kaldırmayı dener, yanlış bir bellek sınırı çalışan bir süreci ortasında
keser. Sonraki ders süreç yöneticisini yazar ve üç yeniden başlatma politikasının düşen istek
sayısını, bellek sınırının aşılmasında öldürme ile yavaşlatma arasındaki farkı ve zarif kapanmanın
boşaltılan istek sayısına etkisini ölçer.

### Süreç Yöneticileri

Source: https://academia.sh/tr/kurslar/backend-uretim/surec-yoneticileri

Uygulamanın altındaki katmanın iki ayarı: çöken sürecin anında, geri çekilmeli ve sınırlı denemeyle yeniden başlatılmasının düşen istek sayısına etkisi, hiç başlamayan süreçte başlatma döngüsünün politikaya göre kaç kez döndüğü, bellek sınırının aşılmasında sürecin öldürülmesi ile kayıt atıp ayakta kalması arasındaki fark ve durdurma işaretinden sonra tanınan bekleme süresinin boşaltılan istek sayısını nasıl belirlediği.

Önceki ders uygulamanın **önündeki** katmanı yapılandırdı ve sekiz koşumun hepsinde tek bir şeyi
varsaydı: uygulama süreci ayaktaydı. Elle başlatıldı, işi bitince elle durduruldu, arada hiç
çökmedi.

Üretimde süreci başlatan, çöktüğünde yeniden başlatan, ne kadar bellek kullanabileceğine karar veren
ve durdurulacağı zaman ona haber veren başka bir bileşen vardır: **süreç yöneticisi**. Vekilin
ayarları isteğin içeriğini değiştiriyordu; buranın ayarları isteğin **yanıtlanıp
yanıtlanmayacağını** değiştirir.

#### İki Ayar, Bir İşçi

Süreç yöneticisinin ölçülecek iki ayarı var. Birincisi **yeniden başlatma politikası**: süreç
çıktığında ne kadar beklenip yeniden başlatılacağı ve kaç deneme sonra vazgeçileceği. İkincisi
**kaynak sınırı**: sürecin ne kadar bellek tutabileceği ve sınır aşıldığında ne yapılacağı. Üçüncü
bir ayar ikisinin dışında durur ve ikisinden de sessizdir: durdurma işaretinden sonra sürece
tanınan **bekleme süresi**.

- **DP1.** İşçi her açık ödünç kaydı için 2 MB ayırır ve tuttuğu toplamı yöneticiye bildirir; sınır
  bu bildirim üzerinden uygulanır. Gerçek dağıtımda aynı değer işletim sisteminden okunur.
- **DP2.** Bir isteğin işi sabit sürelidir. Bellek ölçümünde yanıtlar kabul sırasına göre 100
  milisaniye arayla verilir; sınırın hangi anda aşıldığı ve o anda kaç isteğin uçuşta olduğu böylece
  belirli olur.
- **DP3.** Bu derste tek bir işçi süreci vardır. Kopya sayısı sonraki dersin konusudur.

İşçi, ödünç uygulamasının çalışan hâlidir ve dört davranışı bir mod dizgesinden alır.

```js
// topoloji/isci.mjs — odunc isci sureci. Mod dizgesi davranisini belirler:
//   cok=<n>  n istek yanitladiktan sonra cikar   |  baslarken-cok=1  dinlemeden once cikar
//   is=<ms>  bir istegin suresi   basamak=<ms>  kabul sirasina gore isin uzamasi (yanitlar ayrisir)
//   bellek=yumusak  sinirda en eski kayitlarin yarisini atar ve ayakta kalir
//   kapanis=zarif|ani  durdurma isaretinde bekleyen istekleri bosaltir ya da hemen cikar
import { createServer } from "node:http";

const [nokta, modMetni] = [Number(process.argv[2]), process.argv[3] ?? ""];
const M = Object.fromEntries(modMetni.split(",").filter(Boolean).map((p) => p.split("=")));
const KAYIT_MB = 2;                       // tutulan odunc kaydi basina bellek
const SINIR_MB = 40;                      // yumusak sinir esigi
const tutulan = [];
let yanit = 0, atilan = 0, kabul = 0, kapaniyor = false;

if (Number.isInteger(nokta) === false) { console.log("kullanim: node topoloji/isci.mjs <nokta> <mod>"); process.exit(0); }
if (M["baslarken-cok"] === "1") process.exit(9);

const sunucu = createServer((istek, cevap) => {
  cevap.sendDate = false;
  if (kapaniyor) { cevap.writeHead(503).end("kapaniyor"); return; }
  const sira = kabul++;
  tutulan.push(new Array((KAYIT_MB * 1024 * 1024) / 8).fill(1));   // acik odunc kaydi bellekte durur
  if (M.bellek === "yumusak" && tutulan.length * KAYIT_MB > SINIR_MB) {
    atilan += tutulan.splice(0, tutulan.length >> 1).length;       // en eski yarisi atilir, surec yasar
  }
  setTimeout(() => {
    yanit += 1;
    cevap.writeHead(200, { "x-tutulan-mb": String(tutulan.length * KAYIT_MB) }).end("odunc");
    if (process.send !== undefined) process.send({ tutulanMB: tutulan.length * KAYIT_MB, yanit, atilan });
    if (M.cok !== undefined && yanit >= Number(M.cok)) process.exit(9);
  }, Number(M.is ?? 40) + sira * Number(M.basamak ?? 0));
});

process.on("SIGTERM", () => {
  if (M.kapanis === "zarif") { kapaniyor = true; sunucu.close(() => process.exit(0)); }
  else process.exit(0);                    // bekleyen istekler yaniti gormeden dusuyor
});
sunucu.listen(nokta, "127.0.0.1");
```

Yönetici işçiyi başlatır, bildirimlerini dinler, çıkışını yakalar ve politikayı uygular.

```js
// topoloji/yonetici.mjs — surec yoneticisi. Ayar dizgesi:
//   politika=aninda|geri-cekilmeli|sinirli:<n>   bellek=yok|sert:<mb>
//   sure=<ms>  0 ise durdurma isaretini bekler ve ozet basar; >0 ise o anda zarif durdurmayi baslatir
//   bekleme=<ms>  durdurma isaretinden sonra sert oldurmeye kadar taninan sure
import { fork } from "node:child_process";

const [nokta, ayarMetni, isciMod, etiket] = [process.argv[2], process.argv[3] ?? "", process.argv[4] ?? "", process.argv[5] ?? "-"];
const A = Object.fromEntries(ayarMetni.split(",").filter(Boolean).map((p) => p.split("=")));
const GERI = [100, 200, 400, 800, 1600];          // geri cekilme merdiveni (ms)
let deneme = 0, gecikme = 0, oldurme = 0, tepe = 0, atilan = 0, vazgecti = false, duruyor = false, cocuk = null;

function baslat() {
  let olduruldu = false;
  cocuk = fork("topoloji/isci.mjs", [nokta, isciMod], { stdio: ["ignore", "ignore", "ignore", "ipc"] });
  cocuk.on("message", (m) => {
    tepe = Math.max(tepe, m.tutulanMB);
    atilan = m.atilan;
    const sert = (A.bellek ?? "yok").startsWith("sert:") ? Number(A.bellek.split(":")[1]) : Infinity;
    if (m.tutulanMB > sert && olduruldu === false) {                   // sinir asildi: surec olduruluyor
      olduruldu = true;
      oldurme += 1;
      cocuk.kill("SIGKILL");
    }
  });
  cocuk.on("exit", () => { if (duruyor === false) yeniden(); });
}

function yeniden() {
  const en = (A.politika ?? "").startsWith("sinirli:") ? Number(A.politika.split(":")[1]) : Infinity;
  if (deneme >= en) { vazgecti = true; return; }
  const g = A.politika === "geri-cekilmeli" ? GERI[Math.min(deneme, GERI.length - 1)] : 0;
  deneme += 1;
  gecikme += g;
  setTimeout(baslat, g);
}

if (nokta === undefined) console.log("kullanim: node topoloji/yonetici.mjs <nokta> <ayar> <isci-mod> <etiket>");
else {
  setInterval(() => {}, 60000);          // vazgecildikten sonra da ozet icin ayakta kalinir
  baslat();
  process.on("SIGTERM", () => {
    duruyor = true;
    cocuk?.kill("SIGKILL");
    console.log(`${etiket} yonetici: ${deneme} yeniden baslatma, ${gecikme} ms gecikme, ${oldurme} oldurme, ` +
      `tepe ${tepe} MB, ${atilan} kayit atildi${vazgecti ? ", vazgecti" : ""}`);
    process.exit(0);
  });
  if (Number(A.sure ?? 0) > 0) setTimeout(() => {
    duruyor = true;
    cocuk.kill("SIGTERM");                                            // once zarif kapanma istegi
    const sert = setTimeout(() => cocuk.kill("SIGKILL"), Number(A.bekleme ?? 500));
    cocuk.on("exit", () => { clearTimeout(sert); process.exit(0); }); // bekleme dolmadan cikarsa temiz
  }, Number(A.sure));
}
```

Yük üreteci istekleri sabit aralıkla ya da hepsini birden gönderir ve üç sonucu ayırır: yanıtlanan,
temiz reddedilen, düşen.

```js
// topoloji/yuk.mjs — yuk ureteci. Kip "aralikli": adet istek, aralik ms arayla.
// Kip "esz": adet istek ayni anda. Baglanti kurulamayan ya da yanitsiz kapanan istek "dusen" sayilir.
import { request } from "node:http";

const [nokta, kip, adet, aralik, etiket] = [Number(process.argv[2]), process.argv[3], Number(process.argv[4]), Number(process.argv[5]), process.argv[6] ?? "-"];
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
let yanitlanan = 0, reddedilen = 0, dusen = 0;

const bir = () => new Promise((coz) => {
  const r = request({ port: nokta, path: "/odunc", agent: false }, (y) => {
    y.resume();
    y.on("end", () => { if (y.statusCode === 200) yanitlanan += 1; else reddedilen += 1; coz(); });
    y.on("aborted", () => { dusen += 1; coz(); });
  });
  r.on("error", () => { dusen += 1; coz(); });
  r.end();
});

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/yuk.mjs <nokta> <kip> <adet> <aralik> <etiket>");
else {
  if (kip === "esz") await Promise.all(Array.from({ length: adet }, bir));
  else {
    const acik = [];
    for (let i = 0; i < adet; i += 1) { acik.push(bir()); await bekle(aralik); }
    await Promise.all(acik);
  }
  console.log(`${etiket} yuk: ${adet} istek, ${yanitlanan} yanit, ${reddedilen} reddedilen, ${dusen} dusen`);
}
```

#### Çöken Süreç ve Başlamayan Süreç

İlk düzenek aynı çökmeyi üç politikayla karşılıyor. İşçi her dört yanıttan sonra çıkıyor, yük 24
isteği 100 milisaniye arayla gönderiyor. Ardından aynı üç politika **hiç başlamayan** bir işçiyle
koşuyor: süreç dinlemeye geçmeden çıkıyor.

```bash
# olc1.sh — ayni cokme, uc yeniden baslatma politikasi. Isci her 4 yanittan sonra cikiyor.
kos() {                                   # kos <politika> <isci-mod> <adet> <etiket>
  node topoloji/yonetici.mjs 8941 "politika=$1,sure=0" "$2" "$4" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8941 aralikli "$3" 100 "$4"
  kill -TERM $M; wait $M; sleep 0.2
}

kos aninda        "cok=4,is=40" 24 "cokme / aninda        "
kos geri-cekilmeli "cok=4,is=40" 24 "cokme / geri cekilmeli"
kos sinirli:2     "cok=4,is=40" 24 "cokme / sinirli (2)   "
kos aninda        "baslarken-cok=1" 8 "baslamiyor / aninda   "
kos geri-cekilmeli "baslarken-cok=1" 8 "baslamiyor / geri cek."
kos sinirli:2     "baslarken-cok=1" 8 "baslamiyor / sinirli 2"
```

```
cokme / aninda         yuk: 24 istek, 20 yanit, 0 reddedilen, 4 dusen
cokme / aninda         yonetici: 5 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi
cokme / geri cekilmeli yuk: 24 istek, 14 yanit, 0 reddedilen, 10 dusen
cokme / geri cekilmeli yonetici: 3 yeniden baslatma, 700 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi
cokme / sinirli (2)    yuk: 24 istek, 12 yanit, 0 reddedilen, 12 dusen
cokme / sinirli (2)    yonetici: 2 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi, vazgecti
baslamiyor / aninda    yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / aninda    yonetici: 43 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi
baslamiyor / geri cek. yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / geri cek. yonetici: 4 yeniden baslatma, 1500 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi
baslamiyor / sinirli 2 yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / sinirli 2 yonetici: 2 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi, vazgecti
```

İlk üç ikili aynı çökmeyi karşılıyor ve sıralama beklenenin tersine dönüyor. **Anında** politikası
24 isteğin 20'sini yanıtladı, dördü düştü (bu koşumda). **Geri çekilmeli** politika 14 yanıt verdi,
on istek düştü — merdiven 100, 200 ve 400 milisaniyeyle toplam 700 milisaniyelik bir bekleme ekledi
ve o bekleme boyunca gelen her istek düştü. **Sınırlı** politika iki denemeden sonra vazgeçti ve
kalan on iki isteğin hepsi düştü; bu son sayı politikanın tanımından çıkar, koşumdan değil.

Yanıtlanan ve düşen sayılar sabit aralıklı bir yükten okunur ve makinenin o andaki yüküne bağlıdır;
koşumdan bağımsız olan, politikaların eklediği bekleme ile deneme sayılarıdır.

Çökme geçici olduğunda sıralama nettir: geri çekilme, bir sonraki denemenin başarılı olacağı
durumda saf kayıptır. Merdivenin varlık nedeni son üç satırda görünüyor.

İşçi hiç başlamadığında üç politika da aynı sonucu veriyor — sekiz isteğin sekizi düşüyor. Fark
istek tarafında değil, **makinenin harcadığı işte**: anında politikası aynı 0,8 saniyelik pencerede
onlarca süreç başlattı (bu koşumda 43), geri çekilmeli politika dört, sınırlı politika iki. Bu üç
sayının ikisi koşumdan bağımsızdır: geri çekilme merdiveni 100, 200, 400 ve 800 ile o pencereye dört
deneme sığdırır, sınırlı politika tanımı gereği ikide durur. Anında politikasının sayısı ise
pencere uzunluğu ile süreç başlatma maliyetinin oranıdır ve **üst sınırı yoktur**: pencere büyüdükçe
büyür, makine hızlandıkça büyür.

Bir ayrım daha var ve en sessiz olanı odur. Sınırlı politika **vazgeçti** diye yazıyor; öteki ikisi
sonsuza kadar denemeye devam eder. Ayakta olmayan bir sistem, hiç durmadan başlatılmaya çalışıldığı
sürece "başlatılıyor" görünür.

#### Sınırı Aşmak: Öldürmek ve Yavaşlatmak

İkinci düzenek bellek sınırını iki yerde uyguluyor. **Sert** sınırda yönetici bildirimi okur ve
eşiği geçen süreci öldürür. **Yumuşak** sınırda işçi kendi eşiğini görür, en eski kayıtların yarısını
atar ve ayakta kalır. Yirmi dört istek birden gönderiliyor, yani sınır aşıldığında hepsi uçuşta.
Üçüncü düzenek durdurma işaretini uçuşta altı istek varken gönderiyor.

```bash
# olc2.sh — bellek siniri iki bicimde, ardindan durdurma isaretinde ucustaki istekler.
bellek() {                                # bellek <yonetici-ayar> <isci-mod> <etiket>
  node topoloji/yonetici.mjs 8942 "$1" "$2" "$3" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8942 esz 24 0 "$3"
  kill -TERM $M; wait $M; sleep 0.2
}
bellek "politika=aninda,bellek=sert:40,sure=0" "is=200,basamak=100"                "sert sinir 40 MB "
bellek "politika=aninda,bellek=yok,sure=0"     "is=200,basamak=100,bellek=yumusak" "yumusak sinir 40 MB"

kapanis() {                               # kapanis <yonetici-ayar> <isci-mod> <etiket>
  node topoloji/yonetici.mjs 8943 "$1" "$2" "$3" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8943 esz 6 0 "$3"
  wait $M; sleep 0.2
}
kapanis "politika=aninda,sure=700,bekleme=500" "is=300,kapanis=zarif" "zarif, bekleme 500 ms"
kapanis "politika=aninda,sure=700,bekleme=50"  "is=300,kapanis=zarif" "zarif, bekleme  50 ms"
kapanis "politika=aninda,sure=700,bekleme=500" "is=300,kapanis=ani"   "ani kapanma          "
```

```
sert sinir 40 MB  yuk: 24 istek, 1 yanit, 0 reddedilen, 23 dusen
sert sinir 40 MB  yonetici: 1 yeniden baslatma, 0 ms gecikme, 1 oldurme, tepe 48 MB, 0 kayit atildi
yumusak sinir 40 MB yuk: 24 istek, 24 yanit, 0 reddedilen, 0 dusen
yumusak sinir 40 MB yonetici: 0 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 28 MB, 10 kayit atildi
zarif, bekleme 500 ms yuk: 6 istek, 6 yanit, 0 reddedilen, 0 dusen
zarif, bekleme  50 ms yuk: 6 istek, 0 yanit, 0 reddedilen, 6 dusen
ani kapanma           yuk: 6 istek, 0 yanit, 0 reddedilen, 6 dusen
```

Sert sınır 48 MB'de aşılıyor ve süreç öldürülüyor: yirmi dört isteğin **biri** yanıtlanıyor, yirmi
üçü düşüyor, bir yeniden başlatma yapılıyor. Yumuşak sınırda aynı yirmi dört isteğin yirmi dördü de
yanıtlanıyor, hiç düşen yok, hiç yeniden başlatma yok; bedel on kaydın atılması ve tepenin 48 yerine
28 MB'de kalmasıdır.

İki satır aynı eşiği aynı yükte karşılıyor ve **hangi işin kaybolduğu** değişiyor. Öldürmede
kaybolan şey uçuştaki isteklerdir: yirmi üç ödünç işlemi yarıda kesilir ve istemci yanıt almaz.
Kayıt atmada kaybolan şey bellekteki geçmiştir: süreç yaşar, istek yanıtlanır, atılan kaydın
gerektiğinde yeniden okunması gerekir. Birincisi görünür bir arıza üretir, ikincisi bir yavaşlama
üretir.

Yirmi üç sayısı sınırın aşıldığı anda uçuşta olan istek sayısıdır: öldürmenin faturası **o anda
taşınan işle** ölçülür.

Yumuşak sınırın sessiz tarafı da buradadır: hiçbir sayaç kırmızıya dönmez. Yirmi dört istek
yanıtlandı, düşen yok. Atılan kayıt sayısını raporlamayan bir düzenekte bu satır kusursuz görünür ve
bellek eşiğinin sürekli aşıldığı bilgisi hiçbir yere yazılmaz.

#### Bekleme Süresi

Son üç satır durdurma işaretini uçuşta altı istek varken gönderiyor. İstek başına iş 300
milisaniye.

Zarif kapanmada işçi yeni istek almayı bırakır ve bekleyenleri boşaltır; bekleme 500 milisaniye
olduğunda altı isteğin **altısı** boşaltıldı. Aynı kod, bekleme 50 milisaniyeye çekildiğinde altı
isteğin **altısını** kaybetti: işçi boşaltmaya başladı, yönetici sonunu beklemedi ve süreci
öldürdü. Ani kapanmada işçi hiç boşaltmadı ve sonuç aynı oldu.

Üç satırın ikisi aynı sonucu veriyor, ama nedenleri farklı: birinde boşaltma **yok**, ötekinde
boşaltma **var ama tanınan süre yetmiyor**. Kod tarafında ikincisi doğru yazılmıştır; ayar tarafında
ikisi de aynı kadar kayıptır. Bekleme süresi, uygulamanın en uzun isteğinden büyük olmadıkça zarif
kapanma kodu çalışmaz — ve kodun çalışmadığı hiçbir yere yazılmaz.

#### Ayarın Yeri

| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Yeniden başlatma politikası | süreç yöneticisi | 1 (her düğüm) | geçici çökmede fazladan düşen istek, kalıcı çökmede sonsuz başlatma döngüsü |
| Deneme sınırı | süreç yöneticisi | 1 | sınır yoksa arıza "başlatılıyor" olarak görünür |
| Bellek sınırı ve biçimi | yönetici (sert) ya da uygulama kodu (yumuşak) | 2 | öldürmede uçuştaki bütün istekler, kayıt atmada sessiz yavaşlama |
| Bekleme süresi | süreç yöneticisi | 1 | zarif kapanma kodu yazılıdır ve hiç çalışmaz |

Dört satırın üçü tek bir yerde durur ve tek bir sayıdır. Etkileri ancak bir çökme ya da bir yayın
sırasında görünür.

#### Özet

- Aynı geçici çökmede anında yeniden başlatma 24 isteğin 20'sini yanıtladı, geri çekilmeli politika
  14'ünü, iki denemeyle sınırlı politika 12'sini (ilk ikisi bu koşumda); geri çekilmenin eklediği
  700 milisaniyelik bekleme doğrudan düşen isteğe dönüştü.
- Süreç hiç başlamadığında üç politika da sekiz isteğin sekizini kaybetti; fark harcanan işte oldu —
  anında politikası aynı pencerede onlarca süreç başlattı (bu koşumda 43), geri çekilmeli dört,
  sınırlı iki ve yalnız sonuncusu vazgeçtiğini bildirdi.
- Geri çekilme merdiveninin ve deneme sınırının ürettiği sayılar koşumdan bağımsızdır; anında
  politikasının başlatma sayısının üst sınırı yoktur.
- Bellek sınırı sert uygulandığında yirmi dört isteğin yirmi üçü düştü ve bir yeniden başlatma
  yapıldı; yumuşak uygulandığında hiç istek düşmedi ve bedel on kaydın atılması oldu. Düşen istek
  sayısı, sınırın aşıldığı anda uçuşta olan iş kadardır.
- Zarif kapanmada 500 milisaniyelik bekleme uçuştaki altı isteğin altısını boşalttı; aynı kod 50
  milisaniyelik beklemeyle altısını da kaybetti ve sonuç ani kapanmayla aynı çıktı.

#### Sonraki Adım

Bu iki ders tek bir süreçle çalıştı. Vekil tek bir hedefe iletti, yönetici tek bir işçiyi
başlattı ve bellek sınırı o tek sürecin sınırıydı. Yükü karşılamanın iki yolu vardır ve ikisi de
bu tek sayıya dokunur: sürece daha çok kaynak vermek ya da daha çok süreç açmak. İki yol aynı
isteği karşılar, aynı yanıtı döndürür ve arka planda birbirinden çok farklı bir fatura üretir.
Sonraki ders aynı yükü tek büyük süreçle ve dört küçük süreçle karşılar; bellek toplamını, ayağa
kalkma süresini ve her sürecin kendi bağlantı havuzunu açmasından doğan çarpanı ölçer — bir
sürecin havuz ayarı doğruyken dört sürecin aynı ayarı depoya dört katı bağlantı gönderir.

### Yatay ve Dikey Ölçekleme

Source: https://academia.sh/tr/kurslar/backend-uretim/yatay-ve-dikey-olcekleme

İki ölçekleme ekseninin backend tarafındaki faturası: aynı yükün tek büyük süreçle ve dört küçük süreçle karşılanmasında süreç tabanının kaç kez ödendiği, hazır olma süresinin sıralı yayında nasıl çarpıldığı, her sürecin kendi bağlantı havuzunu açmasından doğan çarpan ve süreç sayısı artarken değişmeyen havuz ayarının depoya düşen toplam bağlantıyı sınırın üstüne taşıması.

Önceki iki ders tek bir süreçle çalıştı. Vekil tek bir hedefe iletti, süreç yöneticisi tek bir
işçiyi başlattı ve bellek sınırı o tek sürecin sınırıydı. Yük büyüdüğünde iki yol vardır: aynı
sürece daha çok kaynak vermek — **dikey ölçekleme** — ya da daha çok süreç açmak — **yatay
ölçekleme**.

İki eksenin hızlanma tarafı, seri payın koyduğu tavan ve kopya eklemenin azalan getirisi Sistem
Tasarımına Giriş kursunda kurulmuştu; burada girdi olarak alınır ve tekrarlanmaz. Bu dersin ölçtüğü
şey backend tarafındadır: iki yerleşim aynı yükü karşıladığında **arka planda ne kadar fazla kaynak
tutulduğu** ve süreç sayısı değiştiğinde hangi ayarın sessizce yanlışa döndüğü.

#### Üç Yerleşim, Aynı Yük

Düzenek üç parçadan oluşuyor: bir kayıt deposu, ödünç uygulamasının bir ya da dört kopyası ve
kopyalar arasında sırayla dağıtan bir süreç.

- **DP1.** Depo eş zamanlı 16 bağlantı kabul eder; on yedincisi kurulur kurulmaz kapatılır. Bir
  sorgu 20 milisaniye sürer.
- **DP2.** Her uygulama kopyası başlarken havuzu kadar kalıcı bağlantı açar (ısınma). Hiçbiri
  kurulamazsa havuz boştur ve süreç isteği depoya hiç gitmeden reddeder.
- **DP3.** Kopyalar sırayla kaldırılır; havuzunu önce açan kopya bağlantıyı önce alır. Bellek süreç
  başına ortalanır, hazır olma süresi kopyaların en uzunudur; ikisi de 10'a yuvarlanır ve koşuma
  bağlıdır. Çarpanlar koşuma bağlı değildir.

```js
// topoloji/depo.mjs — kayit deposu. Es zamanli baglanti sayisinin bir ustu vardir; asan baglanti
// kurulur kurulmaz kapatilir. Sayaclar ayri bir noktadan okunur ki olcum baglantisi sayima girmesin.
import { createServer } from "node:http";

const [nokta, olcumNoktasi, sinir, sorguMs] = process.argv.slice(2, 6).map(Number);
let acik = 0, tepe = 0, reddedilen = 0, sorgu = 0;

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/depo.mjs <nokta> <olcum-noktasi> <baglanti-siniri> <sorgu-ms>");
else {
  const s = createServer((istek, cevap) => {
    sorgu += 1;
    cevap.sendDate = false;
    setTimeout(() => cevap.writeHead(200).end("kayit"), sorguMs);
  });
  s.on("connection", (soket) => {
    acik += 1;
    tepe = Math.max(tepe, acik);
    soket.on("close", () => { acik -= 1; });
    if (acik > sinir) { reddedilen += 1; soket.destroy(); }   // sinir asildi: baglanti kapatiliyor
  });
  s.listen(nokta, "127.0.0.1");
  createServer((istek, cevap) => {
    cevap.sendDate = false;
    cevap.writeHead(200).end(JSON.stringify({ tepe, reddedilen, sorgu }));
  }).listen(olcumNoktasi, "127.0.0.1");
}
```

Uygulama kopyası havuz boyunu bir argüman olarak alır. Bu sayı yerleşimin tek ayarıdır ve tek
süreçliyken doğru seçilmiştir.

```js
// topoloji/uygulama.mjs — odunc surecinin bir kopyasi. Baslarken depoya <havuz> kadar kalici
// baglanti acar (isinma). Hicbiri kurulamazsa havuz bostur ve surec istegi depoya hic gitmeden
// reddeder; kurulanlar varsa her istek havuzdaki bir baglantiyla karsilanir.
import { createServer, request, Agent } from "node:http";

const baslangic = Date.now();
const [nokta, depo, havuzBoyu] = process.argv.slice(2, 5).map(Number);
const havuz = new Agent({ keepAlive: true, maxSockets: havuzBoyu });
let hazirMs = -1, isinmaHatasi = 0, yanit = 0, hata = 0;

const depoya = (yol) => new Promise((coz) => {
  const r = request({ port: depo, path: yol, agent: havuz }, (y) => { y.resume(); y.on("end", () => coz(true)); });
  r.on("error", () => coz(false));
  r.end();
});

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/uygulama.mjs <nokta> <depo> <havuz>");
else {
  const sonuc = await Promise.all(Array.from({ length: havuzBoyu }, () => depoya("/isinma")));
  isinmaHatasi = sonuc.filter((b) => b === false).length;
  hazirMs = Date.now() - baslangic;
  createServer(async (istek, cevap) => {
    cevap.sendDate = false;
    if (istek.url === "/olcum") {
      const rss = Math.round(process.memoryUsage().rss / (1024 * 1024));
      cevap.writeHead(200).end(JSON.stringify({ rss, hazirMs, isinmaHatasi, yanit, hata }));
      return;
    }
    if (isinmaHatasi === havuzBoyu) { hata += 1; cevap.writeHead(503).end("havuz bos"); }
    else if (await depoya("/kayit")) { yanit += 1; cevap.writeHead(200).end("odunc"); }
    else { hata += 1; cevap.writeHead(503).end("depo yok"); }
  }).listen(nokta, "127.0.0.1");
}
```

```js
// topoloji/dagitici.mjs — istekleri kopyalar arasinda sirayla dagitir. Kopya listesi virgulle verilir.
import { createServer, request } from "node:http";

const [nokta, listeMetni] = [Number(process.argv[2]), process.argv[3] ?? ""];
const HEDEF = listeMetni.split(",").filter(Boolean).map(Number);
let sira = 0;

if (Number.isInteger(nokta) === false || HEDEF.length === 0) console.log("kullanim: node topoloji/dagitici.mjs <nokta> <hedef,hedef,...>");
else createServer((istekDisi, yanitDisi) => {
  yanitDisi.sendDate = false;
  const hedef = HEDEF[sira++ % HEDEF.length];
  const istekIc = request({ port: hedef, path: istekDisi.url, method: istekDisi.method }, (yanitIc) => {
    yanitDisi.writeHead(yanitIc.statusCode);
    yanitIc.pipe(yanitDisi);
  });
  istekIc.on("error", () => yanitDisi.writeHead(502).end("kopya yok"));
  istekDisi.pipe(istekIc);
}).listen(nokta, "127.0.0.1");
```

```js
// topoloji/yuk.mjs — <adet> istegi <esz> es zamanlilikla gonderir, sonra kopyalarin ve deponun
// sayaclarini toplar. Bellek ve hazir olma sureleri kopya basina okunup toplanir.
import { request } from "node:http";

const [nokta, adet, esz, depoOlcum, kopyaMetni, etiket] = [Number(process.argv[2]), Number(process.argv[3]), Number(process.argv[4]), Number(process.argv[5]), process.argv[6] ?? "", process.argv[7] ?? "-"];
const KOPYA = kopyaMetni.split(",").filter(Boolean).map(Number);
let yanit = 0, reddedilen = 0;

const bir = () => new Promise((coz) => {
  const r = request({ port: nokta, path: "/odunc" }, (y) => {
    y.resume();
    y.on("end", () => { if (y.statusCode === 200) yanit += 1; else reddedilen += 1; coz(); });
  });
  r.on("error", () => { reddedilen += 1; coz(); });
  r.end();
});

const oku = (p, yol) => new Promise((coz) => {
  request({ port: p, path: yol, agent: false }, (y) => { let b = ""; y.on("data", (d) => { b += d; }); y.on("end", () => coz(JSON.parse(b))); }).end();
});

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/yuk.mjs <nokta> <adet> <esz> <depo-olcum> <kopyalar> <etiket>");
else {
  let gonderilen = 0;
  await Promise.all(Array.from({ length: esz }, async () => { while (gonderilen++ < adet) await bir(); }));
  const k = await Promise.all(KOPYA.map((p) => oku(p, "/olcum")));
  const d = await oku(depoOlcum, "/olcum");
  const toplam = (alan) => k.reduce((t, x) => t + x[alan], 0);
  const P = KOPYA.length;
  // Surec basina bellek ortalamasi ve en uzun hazir olma suresi 10'a yuvarlanir, carpimla yazilir.
  const bellek = Math.round(toplam("rss") / P / 10) * 10;
  const hazir = Math.round(Math.max(...k.map((x) => x.hazirMs)) / 10) * 10;
  console.log(`${etiket} yanit ${String(yanit).padStart(2)}/${adet}  ` +
    `depo tepe ${String(d.tepe).padStart(2)} baglanti, ${String(d.reddedilen).padStart(2)} reddedilen  ` +
    `isinma hatasi ${String(toplam("isinmaHatasi")).padStart(2)}  ` +
    `bellek ${P}x${bellek}=${String(P * bellek).padStart(3)} MB  hazir ${P}x${hazir}=${String(P * hazir).padStart(3)} ms`);
}
```

Üç yerleşim aynı altmış isteği aynı eş zamanlılıkla karşılıyor. Birincisi tek süreç, havuz sekiz.
İkincisi dört süreç ve **aynı** havuz ayarı — süreç sayısı büyütülürken havuza dokunulmamış.
Üçüncüsü dört süreç, havuz iki.

```bash
# olc.sh — ayni yuk uc yerlesimle. Depo es zamanli 16 baglantiyi kabul ediyor, sorgu 20 ms.
# Kopyalar sirayla kaldiriliyor: havuzunu once acan kopya baglantiyi once aliyor.
kur() {                                   # kur <havuz> <kopya-sayisi>
  node topoloji/depo.mjs 8950 8959 16 20 & D=$!
  P=""; N=""
  for i in $(seq 1 "$2"); do
    node topoloji/uygulama.mjs $((8950 + i)) 8950 "$1" & P="$P $!"
    N="$N,$((8950 + i))"
    sleep 0.25
  done
  node topoloji/dagitici.mjs 8960 "${N#,}" & G=$!
  sleep 1
}
kapat() { kill $D $G $P 2>/dev/null; wait $D $G $P 2>/dev/null; sleep 0.3; }

kur 8 1; node topoloji/yuk.mjs 8960 60 32 8959 "8951" "tek surec, havuz 8 "; kapat
kur 8 4; node topoloji/yuk.mjs 8960 60 32 8959 "8951,8952,8953,8954" "dort surec, havuz 8 "; kapat
kur 2 4; node topoloji/yuk.mjs 8960 60 32 8959 "8951,8952,8953,8954" "dort surec, havuz 2 "; kapat
```

```
tek surec, havuz 8  yanit 60/60  depo tepe  8 baglanti,  0 reddedilen  isinma hatasi  0  bellek 1x60= 60 MB  hazir 1x30= 30 ms
dort surec, havuz 8  yanit 30/60  depo tepe 17 baglanti, 16 reddedilen  isinma hatasi 16  bellek 4x60=240 MB  hazir 4x30=120 ms
dort surec, havuz 2  yanit 60/60  depo tepe  8 baglanti,  0 reddedilen  isinma hatasi  0  bellek 4x60=240 MB  hazir 4x30=120 ms
```

Bellek ve süre sütunları koşuma bağlıdır; sayılar bu koşumdandır. Kopya sayısı, havuz boyu, depo
sınırı, istek sayısı ve reddedilen bağlantı sayısı koşumdan bağımsızdır.

#### Taban Kaç Kez Ödenir

Birinci ve üçüncü satır aynı işi yapıyor: altmış isteğin altmışı yanıtlandı. Bellek toplamı 60
MB'den 240 MB'ye çıktı.

Fark isteğin kendisinden gelmiyor. Aynı altmış istek, aynı yirmi milisaniyelik sorgu, aynı yanıt.
Değişen şey **tabanın kaç kez ödendiğidir**: yorumlayıcı, yüklenen kod, olay döngüsü ve süreç
başına ayrılan yığın, süreç başına 60 MB tutuyor ve dört süreç bunu dört kez ödüyor. Çıktı bu
yüzden toplamı değil çarpımı yazıyor — okunması gereken sayı çarpandır.

İki eksenin faturası burada ayrışır. **Dikey ölçekleme tabanı bir kez öder**: süreç büyür, taban
sabit kalır, artan bellek işe gider. **Yatay ölçekleme tabanı süreç sayısı kadar öder**: dört
kopyaya geçmek, hiç istek gelmese bile 180 MB'yi işten önce harcar. Kopya sayısı arttıkça bu kalem
doğrusal büyür ve iş yapmaz.

Bu, yatay ölçeklemenin yanlış olduğunu söylemez; K01'in kopya tablosu yatay eksenin niçin
seçildiğini zaten kurmuştu. Söylediği şey, **karşılaştırmanın kaynak toplamı üzerinden yapılması
gerektiğidir**: dört küçük süreç, aynı toplam belleği kullanan tek bir büyük süreçten daha az iş
belleğine sahiptir.

#### Hazır Olma Süresi

Bir kopya, havuzunu açıp isteğe hazır hâle gelene kadar 30 milisaniye harcıyor. Bu sayı üç
yerleşimde de aynı; kopya sayısı onu değiştirmiyor.

Değişen, o sürenin **kaç kez ödendiğidir**. Dört kopya koşut başlatıldığında duvar saati tek
kopyanınkine yakın kalır. Kopyalar sırayla kaldırıldığında — bir yayında kopyaların teker teker
değiştirilmesi tam olarak budur — süreler toplanır ve duvar saatine 120 milisaniye yazılır. Aynı
30 milisaniye, dört kopyada dört kez ödenir ve kopya sayısıyla doğrusal büyür.

Sayının kendisi küçüktür, çarpanı değildir. Isınmanın bir depo bağlantısı yerine yüklenecek bir
dizin, kurulacak bir bağlantı kümesi ya da ısıtılacak bir önbellek olduğu bir uygulamada aynı
çarpan saniyelere uygulanır; sıralı bir yayının süresi kopya sayısına bağlanır.

#### Havuz Çarpanı

İkinci satır bu dersin asıl ölçüsüdür. Havuz ayarı değişmedi — tek süreçte doğru olan sekiz, dört
süreçte de sekiz. Değişen tek şey süreç sayısı.

Her süreç **kendi havuzunu** açar. Depoya düşen bağlantı sayısı süreç başına havuz değil, süreç
sayısı çarpı havuzdur:

| Yerleşim | Süreç | Süreç başına havuz | Depoya düşen | Depo sınırı | Sonuç |
|---|---|---|---|---|---|
| tek süreç | 1 | 8 | 8 | 16 | 60/60 yanıt |
| dört süreç, havuz 8 | 4 | 8 | 32 | 16 | 30/60 yanıt, 16 reddedilen bağlantı |
| dört süreç, havuz 2 | 4 | 2 | 8 | 16 | 60/60 yanıt |

İkinci satırda depo tepe olarak 17 bağlantı gördü — sınırın bir üstü, çünkü sınırı aşan bağlantı
sayılıp sonra kapatılıyor. Otuz iki bağlantı isteğinin on altısı kapatıldı; kapatılanların hepsi
sonradan kalkan iki kopyanın ısınma bağlantılarıydı.

Sonuç bu iki kopyanın **boş havuzla ayakta kalmasıdır**. Süreçleri çalışıyor, bağlantı noktalarını
dinliyor, süreç yöneticisi dört canlı süreç sayıyor ve dağıtıcı dört hedef görüyor. Altmış isteğin
otuzu bu iki kopyaya gidiyor ve depoya hiç ulaşmadan 503 alıyor. Ayakta olmakla iş yapabilmek
arasındaki fark, yerleşim ölçeklendiği anda açılıyor.

Bu ölçünün sessiz tarafı yapılandırmanın **nerede olmadığındadır**. Havuz boyu uygulama kodunda tek
bir sayıdır ve doğrudur. Süreç sayısı süreç yöneticisinde başka bir sayıdır ve o da doğrudur.
Depoya düşen toplam bağlantı **hiçbir yerde yazmaz**; iki ayarın çarpımıdır ve o çarpımı tutan bir
dosya yoktur. Kopya sayısını ikiden dörde çıkaran değişiklik havuz satırına dokunmaz, gözden
geçirmede havuz satırı görünmez ve sonuç bir hata iletisi olarak değil, depo tarafında kapanan
bağlantılar olarak ortaya çıkar.

Üçüncü satır düzeltmeyi gösteriyor: çarpım korunuyor. Dört süreç, havuz iki, depoya düşen sekiz.
Aynı altmış istek yeniden yanıtlanıyor. Düzeltme havuzu küçültmektir ve bedeli vardır — süreç
başına eş zamanlılık dörtte bire iner. Yatay ölçeklemenin depo tarafındaki gerçek kısıtı budur:
**kopya sayısı bağlantı bütçesini böler.**

#### Ayarın Yeri

| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Kopya sayısı | süreç yöneticisi | 1 | bellek tabanı kopya sayısı kadar ödenir |
| Süreç başına havuz | uygulama kodu | 1 (her kopyada aynı) | çarpım depo sınırını aşar, bağlantılar sessizce kapanır |
| Depo bağlantı sınırı | depo | 1 | düşük sınır iyi istekleri, yüksek sınır depoyu düşürür |
| Isınma bağlantısı | uygulama kodu | 1 | havuzu kurulamayan kopya ayakta kalır ve payına düşen isteği reddeder |

Dört satırın ikisi tek başına doğru, birlikte yanlış olabilir. Kopya sayısı ile havuz boyu ayrı
dosyalarda durur, ayrı zamanlarda değişir ve aralarındaki çarpımı denetleyen bir yer yoktur.

#### Özet

- Aynı altmış istek tek süreçte 60 MB, dört süreçte 240 MB tuttu; artan şey iş belleği değil, süreç
  tabanının dört kez ödenmesidir.
- Kopya başına 30 milisaniyelik hazır olma süresi kopya sayısıyla çarpılır; dört kopya sırayla
  kaldırıldığında duvar saatine 120 milisaniye yazılır.
- Süreç sayısı birden dörde çıkarılıp havuz ayarına dokunulmadığında depoya düşen bağlantı 8'den
  32'ye çıktı, depo 16 bağlantıyı kapattı, 16 ısınma isteği başarısız oldu ve altmış isteğin yalnız
  otuzu yanıtlandı.
- Havuzu kurulamayan iki kopya ayakta kaldı: süreç yöneticisi dört canlı süreç, dağıtıcı dört hedef
  sayarken bu iki kopya payına düşen otuz isteği depoya hiç gitmeden reddetti.
- Havuzu ikiye indirmek çarpımı sekizde tuttu ve altmış isteğin altmışı yeniden yanıtlandı; bedeli
  süreç başına eş zamanlılığın dörtte bire inmesidir.
- Depoya düşen toplam bağlantı iki ayarın çarpımıdır ve hiçbir dosyada yazmaz; iki ayar ayrı yerlerde
  durduğu için değişiklik gözden geçirmede görünmez.

#### Sonraki Adım

Bu ders kopya sayısını bir ayar gibi ele aldı: süreç yöneticisine bir sayı yazıldı, dört süreç
kalktı, dağıtıcı istekleri sırayla paylaştırdı. Üç yerleşimin hepsinde altmış isteğin her biri
herhangi bir kopyaya gidebildi ve sonuç değişmedi. Bu, ölçeklemenin görünmeyen önkoşuludur ve
kendiliğinden sağlanmaz: kopyalar ancak isteğin hangisine gittiği fark etmiyorsa çoğaltılabilir.
Uygulama kodunda bellekte bir oturum, yerel diskte bir dosya, süreç içinde bir önbellek ya da
kurulmuş bir zamanlayıcı varsa kopyalar birbirinin eşi olmaktan çıkar. Sonraki ders bu bağların
kodda nerede durduğunu sayar: kaç çağrı yeri düğüme bağlıdır ve ikinci süreç eklendiğinde kaç istek
yanlış sonuç alır.

### Durumsuz Uygulama Tasarımı

Source: https://academia.sh/tr/kurslar/backend-uretim/durumsuz-uygulama-tasarimi

Ölçeklemenin önkoşulunun kodda nerede kırıldığı: bellekteki oturum, kopyaya özel yerel dosya, süreç içi önbellek ve süreç başına kurulan zamanlayıcı için düğüme bağlı çağrı yerlerinin taranıp sayılması, aynı uygulamanın tek kopya ve iki kopyayla koşulup kaç isteğin yanlış sonuç aldığının ölçülmesi ve dört sızıntının görünürlük sırasına dizilmesi.

Önceki ders kopya sayısını bir ayar gibi ele aldı: süreç yöneticisine bir sayı yazıldı, dört süreç
kalktı, istekler kopyalar arasında paylaştırıldı. Üç yerleşimin hepsinde altmış isteğin her biri
herhangi bir kopyaya gidebildi ve sonuç değişmedi.

Bu, ölçeklemenin görünmeyen önkoşuludur ve kendiliğinden sağlanmaz. Kopyalar ancak isteğin
hangisine gittiği fark etmiyorsa çoğaltılabilir. Bu ders o önkoşulun kodda nerede kırıldığını
arar ve iki soruyu sayıyla yanıtlar: **kaç çağrı yeri düğüme bağlıdır** ve ikinci süreç
eklendiğinde **kaç istek yanlış sonuç alır**.

#### Dört Bağ

Ödünç uygulamasında dört yer sürecin kendisine bağlıdır. **Bellekteki oturum**: giriş bir belirteç
üretir ve onu bir tabloya yazar. **Yerel dosya**: ödünç makbuzu sürecin kendi dizinine yazılır.
**Süreç içi önbellek**: katalog değeri ilk okuyuşta belleğe alınır. **Zamanlayıcı**: gecikme cezası
süreç içinde kurulan bir sayaçla işlenir.

- **DP1.** Paylaşılan tek kaynak `katalog.json` ve ceza defteridir; makbuz dizini kopyaya özeldir
  ve kopyanın yerel diskini temsil eder.
- **DP2.** İstekler kopyalar arasında sırayla paylaştırılır. Dağıtım kuralı önceki derste ölçüldü;
  buranın ölçüsü dağıtım değil, kopyaların ayrışmasıdır.
- **DP3.** Zamanlayıcı süreç başına tam üç tur işler ve durur. Ceza defterine düşen satır sayısı bu
  yüzden koşumdan bağımsızdır.

```js
// topoloji/uygulama.mjs — odunc uygulamasinin bir kopyasi. Dort durum bu surecin icinde durur:
// oturum tablosu, yerel makbuz dizini, katalog onbellegi ve gecikme cezasi zamanlayicisi.
// Paylasilan tek yer katalog.json ile defter.txt; makbuz dizini kopyaya ozeldir.
import { createServer } from "node:http";
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";

const nokta = Number(process.argv[2]);
const oturum = new Map();                        // bellekteki oturum
const onbellek = new Map();                      // surec ici katalog onbellegi
let belirtecSayaci = 0;

const katalogOku = () => JSON.parse(readFileSync("katalog.json", "utf8"));
const govde = (istek) => new Promise((coz) => { let b = ""; istek.on("data", (p) => { b += p; }); istek.on("end", () => coz(b)); });

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/uygulama.mjs <nokta>");
else {
  mkdirSync(`makbuz-${nokta}`, { recursive: true });          // kopyanin kendi yerel diski
  let tur = 0;
  const zamanlayici = setInterval(() => {                     // her kopya kendi zamanlayicisini kurar
    if (++tur > 3) { clearInterval(zamanlayici); return; }
    appendFileSync("defter.txt", `uye-7 gecikme cezasi 2 (kopya ${nokta}, tur ${tur})\n`);
  }, 150);

  createServer(async (istek, cevap) => {
    cevap.sendDate = false;
    const [, kok, alt] = istek.url.split("/");
    if (kok === "giris") {
      const belirtec = `B${nokta}-${++belirtecSayaci}`;
      oturum.set(belirtec, "uye-7");
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(belirtec);
    } else if (kok === "odunclerim") {
      const uye = oturum.get(istek.headers["x-belirtec"]);
      if (uye === undefined) cevap.writeHead(401, { "x-kopya": String(nokta) }).end("oturum yok");
      else cevap.writeHead(200, { "x-kopya": String(nokta) }).end(uye);
    } else if (kok === "makbuz" && istek.method === "POST") {
      const kimlik = `M${nokta}-${Date.now() % 100000}`;
      writeFileSync(`makbuz-${nokta}/${kimlik}.txt`, "odunc alindi");
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(kimlik);
    } else if (kok === "makbuz") {
      let icerik = null;
      try { icerik = readFileSync(`makbuz-${nokta}/${alt}.txt`, "utf8"); } catch { icerik = null; }
      if (icerik === null) cevap.writeHead(404, { "x-kopya": String(nokta) }).end("makbuz yok");
      else cevap.writeHead(200, { "x-kopya": String(nokta) }).end(icerik);
    } else if (kok === "kitap" && istek.method === "POST") {
      const k = katalogOku();
      k[alt] = Number(await govde(istek));
      writeFileSync("katalog.json", JSON.stringify(k));        // paylasilan kaynak guncellendi
      onbellek.delete(alt);                                    // yalniz bu surecin onbellegi tazelendi
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(String(k[alt]));
    } else if (kok === "kitap") {
      if (onbellek.has(alt) === false) onbellek.set(alt, katalogOku()[alt]);
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(String(onbellek.get(alt)));
    } else cevap.writeHead(404, { "x-kopya": String(nokta) }).end("yok");
  }).listen(nokta, "127.0.0.1");
}
```

Birinci ölçü koda bakar. Tarayıcı dört kalıbı kaynakta arar ve düğüme bağlı satırları listeler.

```js
// topoloji/tarayici.mjs — kaynakta dugume bagli cagri yerlerini sayar. Her kalip bir durum turudur;
// bulunan satirlar o surece bagli olan ve ikinci kopya eklendiginde ayrisan yerlerdir.
// Ice aktarma satirlari sayilmaz; sayilan sey durumun bildirildigi ve kullanildigi yerlerdir.
import { readFileSync } from "node:fs";

const KALIP = [
  ["bellekteki oturum", /oturum\.(set|get|has|delete)|new Map\(\).*oturum|const oturum/],
  ["yerel dosya", /makbuz-\$\{nokta\}|mkdirSync/],
  ["surec ici onbellek", /onbellek\.(set|get|has|delete)|const onbellek/],
  ["zamanlayici", /setInterval|appendFileSync/],
];

const dosya = process.argv[2];
if (dosya === undefined) console.log("kullanim: node topoloji/tarayici.mjs <kaynak-dosya>");
else {
  const satir = readFileSync(dosya, "utf8").split("\n");
  let toplam = 0;
  console.log("durum turu           cagri yeri  satir");
  for (const [ad, kalip] of KALIP) {
    const yer = satir.map((s, i) => (kalip.test(s) && s.startsWith("import") === false ? i + 1 : 0)).filter(Boolean);
    toplam += yer.length;
    console.log(`${ad.padEnd(21)}${String(yer.length).padStart(10)}  ${yer.join(", ")}`);
  }
  console.log(`${"toplam".padEnd(21)}${String(toplam).padStart(10)}`);
}
```

İkinci ölçü çalışmaya bakar. İstemci dört senaryoyu sırayla koşar ve her birinde kaç isteğin yanlış
sonuç aldığını sayar.

```js
// topoloji/istemci.mjs — istekleri kopyalar arasinda sirayla paylastirir (onceki dersin dagiticisi
// da bunu yapiyordu; burada olculen sey dagitim degil, kopyalarin ayrismasidir) ve dort durum
// turunun her birinde kac istegin yanlis sonuc aldigini sayar.
import { request } from "node:http";
import { readFileSync } from "node:fs";

const [kopyaMetni, etiket] = [process.argv[2] ?? "", process.argv[3] ?? "-"];
const KOPYA = kopyaMetni.split(",").filter(Boolean).map(Number);
const ADET = 6;
let sira = 0;

const bir = (yol, { yontem = "GET", basliklar = {}, govde } = {}) => new Promise((coz) => {
  const hedef = KOPYA[sira++ % KOPYA.length];
  const r = request({ port: hedef, path: yol, method: yontem, headers: basliklar }, (y) => {
    let b = "";
    y.on("data", (p) => { b += p; });
    y.on("end", () => coz({ kod: y.statusCode, govde: b, kopya: y.headers["x-kopya"] }));
  });
  r.on("error", () => coz({ kod: 0, govde: "", kopya: "-" }));
  r.end(govde);
});

const yaz = (ad, yanlis, ek) => {
  console.log(`${etiket} ${ad.padEnd(20)} ${ADET - yanlis}/${ADET} dogru  ${String(yanlis).padStart(2)} yanlis  ${ek}`);
  return yanlis;
};

if (KOPYA.length === 0) console.log("kullanim: node topoloji/istemci.mjs <kopya,kopya> <etiket>");
else {
  const giris = await bir("/giris", { yontem: "POST" });
  const o = await Promise.all(Array.from({ length: ADET }, () => bir("/odunclerim", { basliklar: { "x-belirtec": giris.govde } })));
  const a = yaz("bellekteki oturum", o.filter((y) => y.kod !== 200).length, `oturum ${giris.kopya} kopyasinda`);

  const makbuz = await bir("/makbuz", { yontem: "POST" });
  const m = await Promise.all(Array.from({ length: ADET }, () => bir(`/makbuz/${makbuz.govde}`)));
  const b = yaz("yerel dosya", m.filter((y) => y.kod !== 200).length, `makbuz ${makbuz.kopya} kopyasinda`);

  await Promise.all([bir("/kitap/K1"), bir("/kitap/K1")]);            // iki kopyanin onbellegi isiniyor
  await bir("/kitap/K1", { yontem: "POST", govde: "2" });             // paylasilan katalog 1'den 2'ye
  const k = await Promise.all(Array.from({ length: ADET }, () => bir("/kitap/K1")));
  const c = yaz("surec ici onbellek", k.filter((y) => y.govde !== "2").length, "guncel deger 2");

  const satir = readFileSync("defter.txt", "utf8").trim().split("\n").filter((s) => s.includes("uye-7"));
  const d = Math.max(0, satir.length - 3);
  console.log(`${etiket} ${"zamanlayici".padEnd(20)} ${3}/${satir.length} dogru  ${String(d).padStart(2)} yanlis  ` +
    `uye-7 cezasi ${satir.length} kez islendi`);
  console.log(`${etiket} ${"toplam".padEnd(20)} yanlis sonuc alan istek ${a + b + c + d}/${3 * ADET + 3}`);
}
```

Aynı uygulama iki yerleşimle koşuyor. Kodda tek bir satır değişmiyor; değişen tek şey kaç süreç
kaldırıldığı.

```bash
# olc.sh — ayni uygulama once tek kopyayla, sonra iki kopyayla. Paylasilan katalog her kosumdan
# once sifirlaniyor; defter ve kopyalara ozel makbuz dizinleri de siliniyor.
kur() {                                   # kur <kopya-sayisi>
  rm -rf makbuz-897* defter.txt
  echo '{"K1":1}' > katalog.json
  : > defter.txt
  P=""; N=""
  for i in $(seq 1 "$1"); do node topoloji/uygulama.mjs $((8970 + i)) & P="$P $!"; N="$N,$((8970 + i))"; done
  sleep 0.9
}
kapat() { kill $P 2>/dev/null; wait $P 2>/dev/null; sleep 0.2; }

kur 1; node topoloji/istemci.mjs "${N#,}" "tek kopya"; kapat
kur 2; node topoloji/istemci.mjs "${N#,}" "iki kopya"; kapat
echo
node topoloji/tarayici.mjs topoloji/uygulama.mjs
```

```
tek kopya bellekteki oturum    6/6 dogru   0 yanlis  oturum 8971 kopyasinda
tek kopya yerel dosya          6/6 dogru   0 yanlis  makbuz 8971 kopyasinda
tek kopya surec ici onbellek   6/6 dogru   0 yanlis  guncel deger 2
tek kopya zamanlayici          3/3 dogru   0 yanlis  uye-7 cezasi 3 kez islendi
tek kopya toplam               yanlis sonuc alan istek 0/21
iki kopya bellekteki oturum    3/6 dogru   3 yanlis  oturum 8971 kopyasinda
iki kopya yerel dosya          3/6 dogru   3 yanlis  makbuz 8972 kopyasinda
iki kopya surec ici onbellek   3/6 dogru   3 yanlis  guncel deger 2
iki kopya zamanlayici          3/6 dogru   3 yanlis  uye-7 cezasi 6 kez islendi
iki kopya toplam               yanlis sonuc alan istek 12/21

durum turu           cagri yeri  satir
bellekteki oturum             3  8, 29, 32
yerel dosya                   3  17, 37, 41
surec ici onbellek            4  9, 48, 51, 52
zamanlayici                   2  19, 21
toplam                       12
```

Bütün sayılar belirlenimlidir: istek sayısı sabit, paylaştırma sırayla, zamanlayıcı tur sayısı üç.

#### Kodun Söylediği

Tarama elli beş satırlık bir kaynakta **on iki çağrı yeri** buluyor. Sayının kendisinden çok
dağılımı önemlidir. Oturum üç yerde geçiyor: bildirildiği satır, yazıldığı satır, okunduğu satır.
Önbellek dört yerde. Zamanlayıcı yalnız iki yerde ve o iki yer bir uç noktanın içinde bile değil —
sunucu kurulmadan önce, modülün gövdesinde duruyor.

Bu dağılım bir düzeltme planının maliyetidir. Oturumu paylaşılan bir depoya taşımak üç satıra
dokunur, önbelleği dörde. Zamanlayıcı iki satırdır ama ona dokunmak kodu değil **yerleşimi**
değiştirir: işin bir kopyada çalışması, ötekilerde çalışmaması gerekir; bu bir kod satırı değil,
bir dağıtım kararıdır.

Bir sayının okunmasında dikkat gerekir. Tarama on iki çağrı yeri, koşum on iki yanlış sonuç veriyor;
ikisi aynı şey değildir ve eşitlikleri bu düzeneğe özgüdür. Biri kaynağın **statik** ölçüsüdür ve
kopya sayısından bağımsızdır; öteki isteklerin **çalışma anındaki** ölçüsüdür ve kopya sayısıyla
büyür.

#### Yirmi Bir İsteğin On İkisi

Tek kopyalı yerleşimde yirmi bir isteğin hepsi doğru sonuç aldı. Uygulama doğru çalışıyor,
sınamalardan geçiyor ve hiçbir sızıntı görünmüyor. Sızıntıların hepsi tek kopyada **doğru
davranıştır**: oturumu bellekte tutmak hızlıdır, makbuzu yerel diske yazmak ucuzdur, katalog
önbelleği bir okuma tasarrufudur, zamanlayıcı işini yapar.

İkinci süreç eklendiğinde aynı kod yirmi bir isteğin on ikisinde yanlış sonuç veriyor. Dört türün
her birinde tam üçer istek, çünkü paylaştırma sırayla yapılıyor ve durumun tutulduğu kopyaya
isteklerin yarısı gidiyor.

- **Oturum** 8971 kopyasında açıldı; 8972'ye düşen üç istek 401 aldı. Üye giriş yaptığını biliyor
  ve sistem her ikinci isteğinde onu tanımıyor.
- **Makbuz** 8972 kopyasının yerel dizinine yazıldı; 8971'e düşen üç istek 404 aldı. Makbuz
  kaybolmadı, yanlış düğümde duruyor.
- **Önbellek** iki kopyada da ısıtıldı, güncelleme yalnız birinde geçersizleştirildi. Paylaşılan
  katalog 2 değerini tutuyor, üç istek 1 değerini alıyor — **kod 200 ile.**
- **Zamanlayıcı** iki kopyada da kuruldu ve gecikme cezası üç yerine altı kez işlendi. Bu üç fazla
  işlemin karşılığında **hiçbir istek yoktur**; kimse bir şey sormadı, bir durum kodu üretilmedi.

#### Dört Sızıntı, Dört Görünürlük

Dördü aynı ağırlıkta değildir ve sıralamayı belirleyen şey ne sıklıkları ne de sayılarıdır —
**dışarıdan ne olarak göründükleridir.**

| Sızıntı | İstemcinin gördüğü | Kayıtta görünür mü | Veriye dokunur mu |
|---|---|---|---|
| Bellekteki oturum | 401 | evet, hata olarak | hayır |
| Yerel dosya | 404 | evet, hata olarak | hayır |
| Süreç içi önbellek | 200, eski değer | hayır | hayır |
| Zamanlayıcı | hiçbir şey | hayır | evet |

İlk iki satır gürültülüdür: yanlış sonuç bir hata kodu olarak döner, kayda düşer, sayaçta görünür.
Rahatsız edicidir ve tam bu yüzden erken bulunur.

Üçüncü satır sessizdir. İstek başarılıdır, kayıt temizdir, gecikme düşüktür. Ödünç sisteminde bu,
elde olmayan bir kitabın rafta görünmesidir. Üçüncü satırın ölçülebilmesinin tek nedeni istemcinin
doğru cevabı önceden bilmesidir; bir kullanıcı bilmez.

Dördüncü satır hem sessizdir hem de tek başına **paylaşılan veriyi bozar**. Üye yediye gecikme
cezası iki kez işlendi. İstek yok, hata yok, kayıtta bir sapma yok — yalnız defterde iki katı
satır. Kopya sayısı üçe çıkarılsa ceza üç kat işlenirdi; sızıntının şiddeti doğrudan kopya sayısına
bağlıdır ve kopya sayısı ölçekleme kararının kendisidir.

#### Ayarın Yeri

| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Kopya sayısı | süreç yöneticisi | 1 satır | uygulamada 12 çağrı yeri ayrışır, 21 isteğin 12'si yanlışlanır |
| Oturumun tutulduğu yer | uygulama kodu | 3 | her ikinci istekte oturum kaybı |
| Makbuz dizini | uygulama kodu | 3 | yazan kopya dışında makbuz yok |
| Önbellek geçersizleştirme | uygulama kodu | 4 | eski değer 200 ile döner |
| Zamanlayıcının kurulduğu yer | uygulama kodu, modül gövdesi | 2 | iş kopya sayısı kadar tekrar edilir |

Tablodaki asimetri bu dersin kuralıdır. Değişen ayar **bir** satırdır ve süreç yöneticisindedir;
sonucunu belirleyen satırlar **on iki** tanedir ve uygulama kodundadır. Kopya sayısını artıran
değişiklik uygulama kodunda hiçbir satıra dokunmaz, bu yüzden gözden geçirmede uygulama kodu hiç
açılmaz.

#### Özet

- Aynı uygulama tek kopyayla yirmi bir isteğin hepsinde doğru sonuç verdi, iki kopyayla on ikisinde
  yanlış sonuç verdi; kodda tek satır değişmedi.
- Dört sızıntı türünün her biri tam üç isteği yanlışladı: oturum 401, makbuz 404, önbellek eski
  değeri 200 ile, zamanlayıcı üye yedinin gecikme cezasını üç yerine altı kez işleyerek.
- Kaynak taraması elli beş satırda on iki düğüme bağlı çağrı yeri buldu: oturum 3, yerel dosya 3,
  önbellek 4, zamanlayıcı 2; bu dağılım düzeltmenin dokunacağı satır sayısıdır.
- Statik on iki ile çalışma anındaki on iki farklı ölçülerdir; ilki kopya sayısından bağımsızdır,
  ikincisi kopya sayısıyla büyür.
- En sessiz iki sızıntı önbellek ile zamanlayıcıdır: biri yanlış değeri 200 ile döndürür, öteki
  hiçbir istek üretmeden paylaşılan veriyi bozar ve şiddeti kopya sayısıyla çarpılır.

#### Sonraki Adım

Dört sızıntının üçü için yön bellidir: durum sürecin dışına, kopyaların paylaştığı bir yere taşınır.
Oturum için bu, akla gelen tek çözüm değildir ve çoğu zaman ilk denenen de o olmaz. Daha ucuz
görünen bir yol vardır: durumu taşımak yerine **isteği** taşımak, yani aynı üyenin bütün isteklerini
oturumunun açıldığı kopyaya göndermek. Bu, ölçümdeki üç yanlış isteği sıfıra indirir ve uygulama
kodunda hiçbir satıra dokunmaz. Bedelini başka bir yerde ödetir. Sonraki ders bu yolu adlandırır ve
faturasını çıkarır: dağılımın ne kadar bozulduğunu, bir kopya düştüğünde kaç oturumun kaybolduğunu
ve oturumu paylaşılan bir depoya taşımanın aynı ölçümde ne değiştirdiğini sayar.

### Oturum ve Yapışkanlık

Source: https://academia.sh/tr/kurslar/backend-uretim/oturum-ve-yapiskanlik

Oturumun üç yerleşimi aynı düzenek üzerinde ölçülür: süreç belleğinde sessizce boşalan sepet, düğüm eklendiğinde taşınan üyenin kaybolan oturumu, paylaşılan deponun istek başına okuması, imzalı belirtecin çerez boyu ve iptal edilemezliği.

Önceki ders durumu uygulamadan çıkardı: yerel dosyalar, süreç içi sayaçlar ve düğüme
bağlanan ne varsa dışarı taşındı, düğüm yerine konabilir hâle geldi. Geriye kullanıcıya
bağlı tek bir durum kaldı: oturum. Oturum silinemez, yalnızca bir yere konur. Bu ders o
yerin üç seçeneğini tek düzenek üzerinde ölçer.

Ölçülen nesne bölünmüş ödünç sisteminin **ödünç sepetidir**: üyenin ayırttığı ama henüz
onaylamadığı kitaplar. Sepet oturumda durur, oturum da üç yerden birinde: süreç belleğinde,
paylaşılan bir depoda ya da istemcideki imzalı bir belirteçte.

- **DP10.** Ödünç düğümleri aynı sürümü çalıştırır; aralarında ağ ya da donanım farkı
  yoktur. Ölçümler arasında değişen tek şey oturumun yerleşimidir.
- **DP11.** İstemci çerez taşır: her yanıttaki çerezi saklar, sonraki istekte geri gönderir.
- **DP12.** Paylaşılan depo aynı makinede bir `node:sqlite` dosyasıdır; gerçek dağıtımda
  ayrı bir süreç olur, istek başına okuma sayısı değişmez.

#### Üç Yerleşim, Tek Düğüm

Düğüm üç yerleşimi de tanır; hangisini kullanacağı `OTURUM_YERI` ile seçilir. Üçünün de
işi aynı: çerezden oturumu okumak, yazıp yeni çerezi döndürmek, iptal etmek. Ne yaptıkları
değil, **nerede yaptıkları** farklıdır.

```js
// dugum.mjs — odunc sepeti dugumu; oturumun nerede durdugu OTURUM_YERI ile secilir
import { createServer } from 'node:http';
import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
import { DatabaseSync } from 'node:sqlite';

const AD = process.env.DUGUM_ADI;
const ANAHTAR = process.env.IMZA_ANAHTARI;
const LISTE = process.env.IPTAL_LISTESI === 'acik';
const sayac = { depoOkuma: 0, imzaDogrulama: 0 };

const depo = new DatabaseSync(process.env.DEPO_YOLU);
depo.exec('PRAGMA journal_mode = WAL');
depo.exec('CREATE TABLE IF NOT EXISTS oturum (kimlik TEXT PRIMARY KEY, sepet TEXT, iptal INTEGER)');
depo.exec('CREATE TABLE IF NOT EXISTS iptal (kimlik TEXT PRIMARY KEY)');
const oku = (sorgu, d) => { sayac.depoOkuma += 1; return depo.prepare(sorgu).get(d); };

const imzala = (govde) => createHmac('sha256', ANAHTAR).update(govde).digest('base64url');
const bos = () => ({ kimlik: randomUUID(), sepet: [], yeni: true });

const YERLESIM = {
  // 1. Surec bellegi: oturum yalniz bu surecin yigininda yasar.
  surec: {
    bellek: new Map(),
    oku(cerez) {
      const bulunan = this.bellek.get(cerez.oturum);
      return bulunan ? { kimlik: cerez.oturum, sepet: bulunan, yeni: false } : bos();
    },
    yaz(o) { this.bellek.set(o.kimlik, o.sepet); return `oturum=${o.kimlik}`; },
    iptal(o) { this.bellek.delete(o.kimlik); },
  },
  // 2. Paylasilan depo: oturum butun dugumlerin gordugu tek yerde durur.
  depo: {
    oku(cerez) {
      const s = cerez.oturum && oku('SELECT sepet, iptal FROM oturum WHERE kimlik = ?', cerez.oturum);
      return !s || s.iptal ? bos() : { kimlik: cerez.oturum, sepet: JSON.parse(s.sepet), yeni: false };
    },
    yaz(o) {
      depo.prepare('INSERT INTO oturum VALUES (?, ?, 0) ON CONFLICT(kimlik) DO UPDATE SET sepet = excluded.sepet')
        .run(o.kimlik, JSON.stringify(o.sepet));
      return `oturum=${o.kimlik}`;
    },
    iptal(o) { depo.prepare('UPDATE oturum SET iptal = 1 WHERE kimlik = ?').run(o.kimlik); },
  },
  // 3. Imzali belirtec: oturum istemcide durur, dugum yalniz imzayi dogrular.
  belirtec: {
    oku(cerez) {
      const [govde, imza] = String(cerez.belirtec ?? '').split('.');
      if (!govde || !imza) return bos();
      sayac.imzaDogrulama += 1;
      const beklenen = Buffer.from(imzala(govde));
      const gelen = Buffer.from(imza);
      if (beklenen.length !== gelen.length || !timingSafeEqual(beklenen, gelen)) return bos();
      const o = JSON.parse(Buffer.from(govde, 'base64url').toString());
      if (LISTE && oku('SELECT 1 FROM iptal WHERE kimlik = ?', o.kimlik)) return bos();
      return { ...o, yeni: false };
    },
    yaz(o) {
      const govde = Buffer.from(JSON.stringify({ kimlik: o.kimlik, sepet: o.sepet })).toString('base64url');
      return `belirtec=${govde}.${imzala(govde)}`;
    },
    iptal(o) { if (LISTE) depo.prepare('INSERT OR IGNORE INTO iptal VALUES (?)').run(o.kimlik); },
  },
}[process.env.OTURUM_YERI];

const cerezAyristir = (b) => Object.fromEntries((b ?? '').split(';').map((p) => p.trim())
  .filter(Boolean).map((p) => [p.slice(0, p.indexOf('=')), p.slice(p.indexOf('=') + 1)]));

createServer((istek, yanit) => {
  const yol = new URL(istek.url, 'http://yerel');
  const bas = { 'content-type': 'application/json' };
  if (yol.pathname === '/sayac') return yanit.writeHead(200, bas).end(JSON.stringify(sayac));
  const oturum = YERLESIM.oku(cerezAyristir(istek.headers.cookie));
  if (yol.pathname === '/cikis') {
    YERLESIM.iptal(oturum);
    return yanit.writeHead(200, bas).end(JSON.stringify({ dugum: AD, cikis: true }));
  }
  if (istek.method === 'POST') oturum.sepet = [...oturum.sepet, yol.searchParams.get('kitap')];
  const cerez = YERLESIM.yaz(oturum);
  yanit.writeHead(200, { ...bas, 'set-cookie': cerez });
  yanit.end(JSON.stringify({ dugum: AD, sepet: oturum.sepet.length, yeni: oturum.yeni, cerezBoyu: cerez.length }));
}).listen(Number(process.env.PORT), () => console.log(`hazir ${AD}`));
```

Aşağıdaki koşum paylaşılan depoyu seçiyor: iki ekleme, bir okuma.

```sh
OTURUM_YERI=depo DUGUM_ADI=a PORT=8719 DEPO_YOLU=oturum.db \
  IMZA_ANAHTARI=kutuphane-imza-anahtari node dugum.mjs > /dev/null &
SUNUCU=$!
sleep 0.6
curl -s -X POST -c cerez.txt 'http://127.0.0.1:8719/sepet?kitap=K-101'; echo
curl -s -X POST -b cerez.txt -c cerez.txt 'http://127.0.0.1:8719/sepet?kitap=K-102'; echo
curl -s -b cerez.txt 'http://127.0.0.1:8719/sepet'; echo
kill $SUNUCU
```

```
{"dugum":"a","sepet":1,"yeni":true,"cerezBoyu":43}
{"dugum":"a","sepet":2,"yeni":false,"cerezBoyu":43}
{"dugum":"a","sepet":2,"yeni":false,"cerezBoyu":43}
```

Sepet ikiye çıktı, çerez 43 bayt kaldı. Tek düğümde yerleşim kararının gözlenebilir bir
sonucu yoktur; kararın neden ertelendiğini bu açıklar.

#### Dağıtım Değişince Sepete Ne Oluyor

Ölçüm iki düğümle başlar. Dağıtıcı istekleri sırayla dağıtır; istemci çerez taşır.

```js
// dagitici.mjs — dugumleri baslatir, istegi sirayla ya da yapiskan bicimde dagitir
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const kok = mkdtempSync(join(tmpdir(), 'oturum-'));
export const temizle = () => rmSync(kok, { recursive: true, force: true });
export const durdur = (d) => d.forEach((x) => x.cocuk.kill('SIGKILL'));

export async function dugumBaslat(yer, ad, port, ek = {}) {
  const cocuk = spawn(process.execPath, ['dugum.mjs'], { stdio: ['ignore', 'pipe', 'inherit'],
    env: { ...process.env, OTURUM_YERI: yer, DUGUM_ADI: ad, PORT: String(port),
           IMZA_ANAHTARI: 'kutuphane-imza-anahtari', DEPO_YOLU: join(kok, 'oturum.db'), ...ek } });
  await new Promise((c) => cocuk.stdout.once('data', c));
  return { ad, port, cocuk };
}

// Cerez kavanozu tasiyan istemci: her yanittaki cerezi saklar, bir sonrakinde geri gonderir.
export function istemci(kimlik) {
  const kavanoz = new Map();
  return { kimlik, async cagir(dugum, yol) {
    const cerez = [...kavanoz].map(([a, d]) => `${a}=${d}`).join('; ');
    const yanit = await fetch(`http://127.0.0.1:${dugum.port}${yol}`,
      { method: yol.includes('kitap=') ? 'POST' : 'GET', headers: cerez ? { cookie: cerez } : {} });
    for (const ham of yanit.headers.getSetCookie()) {
      kavanoz.set(ham.slice(0, ham.indexOf('=')), ham.slice(ham.indexOf('=') + 1));
    }
    return yanit.json();
  } };
}

export const sirayla = (d, n) => d[n % d.length];
export const yapiskan = (d, kimlik) => d[createHash('sha256').update(kimlik).digest()[0] % d.length];
```

Birinci ölçümde tek üye sekiz kitabı, sırayla dağıtılan sekiz istekle sepete ekler.
İkincisi yapışkan dağıtımı açar — aynı üye hep aynı düğüme gider — ve düğüm sayısını üçe
çıkarır.

```js
// yerlesim-olcumu.mjs — sirayla dagitilan sekiz ekleme, sonra yapiskan dagitimda ucuncu dugum
import { dugumBaslat, durdur, temizle, istemci, sirayla, yapiskan } from './dagitici.mjs';

const KITAP = ['K-101', 'K-102', 'K-103', 'K-104', 'K-105', 'K-106', 'K-107', 'K-108'];
const UYELER = Array.from({ length: 12 }, (_, i) => `U-${String(i + 1).padStart(4, '0')}`);

async function sirayliOlcum(yer) {
  const d = [await dugumBaslat(yer, 'a', 8710), await dugumBaslat(yer, 'b', 8711)];
  const uye = istemci('U-0001');
  const bildirilen = [];
  for (const [i, kitap] of KITAP.entries()) {
    bildirilen.push((await uye.cagir(sirayla(d, i), `/sepet?kitap=${kitap}`)).sepet);
  }
  const son = await uye.cagir(d[0], '/sepet');
  durdur(d);
  return `${yer.padEnd(10)} ${bildirilen.join(' ').padEnd(24)} ${son.sepet}`;
}

async function yapiskanOlcum(yer) {
  const iki = [await dugumBaslat(yer, 'a', 8712), await dugumBaslat(yer, 'b', 8713)];
  const istemciler = UYELER.map(istemci);
  for (const uye of istemciler) {
    for (const kitap of ['K-201', 'K-202', 'K-203']) await uye.cagir(yapiskan(iki, uye.kimlik), `/sepet?kitap=${kitap}`);
  }
  const uc = [...iki, await dugumBaslat(yer, 'c', 8714)];
  let tasinan = 0, bosalan = 0;
  for (const uye of istemciler) {
    if (yapiskan(uc, uye.kimlik).ad !== yapiskan(iki, uye.kimlik).ad) tasinan += 1;
    if ((await uye.cagir(yapiskan(uc, uye.kimlik), '/sepet')).sepet === 0) bosalan += 1;
  }
  durdur(uc);
  return `${yer.padEnd(10)} ${String(tasinan).padEnd(9)} ${String(bosalan).padEnd(9)} ${UYELER.length - bosalan}`;
}

console.log('sirayla dagitim, tek uye, sekiz ekleme (hicbir istek hata donmedi)');
console.log('yerlesim   eklemede bildirilen sepet  son okuma');
for (const yer of ['surec', 'depo', 'belirtec']) console.log(await sirayliOlcum(yer));
console.log('');
console.log('yapiskan dagitim, on iki uye, dugum sayisi ikiden uce cikiyor');
console.log('yerlesim   tasinan   bosalan   sepeti duran');
for (const yer of ['surec', 'depo', 'belirtec']) console.log(await yapiskanOlcum(yer));
temizle();
```

```
sirayla dagitim, tek uye, sekiz ekleme (hicbir istek hata donmedi)
yerlesim   eklemede bildirilen sepet  son okuma
surec      1 1 1 1 1 1 1 1          0
depo       1 2 3 4 5 6 7 8          8
belirtec   1 2 3 4 5 6 7 8          8

yapiskan dagitim, on iki uye, dugum sayisi ikiden uce cikiyor
yerlesim   tasinan   bosalan   sepeti duran
surec      7         7         5
depo       7         0         12
belirtec   7         0         12
```

Birinci tablonun `surec` satırı bu dersin çekirdeğidir. Sekiz istek gönderildi, sekizi de
başarıyla döndü ve her biri sepette **bir** kitap bildirdi: her istek bir önceki düğümün
bilmediği bir çerezle gelince düğüm yeni bir oturum açıyor ve kitabı ona ekliyor. Sepet
hiç birikmiyor, son okumada görünen sayı sıfır. Uygulama kayıp bildirmedi çünkü kendi
bakış açısından kayıp yok: her istek geçerli bir oturumla bitti.

İkinci tablo yapışkanlığın neyi satın alıp neyi almadığını gösteriyor. Yapışkan dağıtımda
süreç belleği çalışır görünür; sorun düğüm kümesi değiştiğinde çıkar. Üçüncü düğüm
eklenince on iki üyenin yedisi başka bir düğüme taşındı ve süreç belleği yerleşiminde o
yedi sepet boşaldı. Taşınan üye sayısı üç yerleşimde de aynı — dağıtım kuralı aynı kural —
ama sepeti boşalan üye sayısı yalnız süreç belleğinde sıfırdan farklı. Yapışkanlık oturumu
düğüme bağlar; düğüm kümesi değiştiği anda o bağ kopar. Yayın da düğüm kümesini değiştirir.

#### İstek Başına Ödenen Bedel

Paylaşılan depo ve imzalı belirteç ikinci tabloda aynı sonucu verdi. Farkları üç yerde:
istek başına yapılan iş, çerezde taşınan bayt ve çıkıştan sonrası.

```js
// maliyet.mjs — istek basina dogrulama isi, cerez boyu ve cikistan sonra gecen istek
import { dugumBaslat, durdur, temizle, istemci } from './dagitici.mjs';

const ISTEK = 200;
const BOY = [1, 8, 32, 128];

async function olc(yer, ek = {}, ad = yer) {
  const dugum = await dugumBaslat(yer, 'a', 8715, ek);
  const uye = istemci('U-0001');
  const boy = new Map();
  for (let i = 1; i <= ISTEK; i += 1) {
    const yanit = await uye.cagir(dugum, `/sepet?kitap=K-${300 + i}`);
    if (BOY.includes(i)) boy.set(i, yanit.cerezBoyu);
  }
  const sayac = await fetch(`http://127.0.0.1:${dugum.port}/sayac`).then((y) => y.json());
  await uye.cagir(dugum, '/cikis');
  let gecen = 0;
  for (let i = 0; i < 5; i += 1) if ((await uye.cagir(dugum, '/sepet')).sepet > 0) gecen += 1;
  durdur([dugum]);
  return { ad, ...sayac, boy, gecen };
}

const o = [await olc('surec'), await olc('depo'), await olc('belirtec'),
           await olc('belirtec', { IPTAL_LISTESI: 'acik' }, 'belirtec+liste')];

console.log(`${ISTEK} istekte yapilan is ve cikistan sonra kabul edilen bes istek`);
console.log('yerlesim        depo okuma  imza dogrulama  cikistan sonra gecen');
for (const s of o) {
  console.log(`${s.ad.padEnd(15)} ${String(s.depoOkuma).padEnd(11)} ${String(s.imzaDogrulama).padEnd(15)} ${s.gecen} / 5`);
}
console.log('');
console.log('cerezde tasinan bayt, sepetteki kitap sayisina gore');
console.log(`yerlesim        ${BOY.map((n) => `${n} kitap`.padEnd(10)).join('')}`);
for (const s of o) console.log(`${s.ad.padEnd(15)} ${BOY.map((n) => String(s.boy.get(n)).padEnd(10)).join('')}`);
temizle();
```

```
200 istekte yapilan is ve cikistan sonra kabul edilen bes istek
yerlesim        depo okuma  imza dogrulama  cikistan sonra gecen
surec           0           0               0 / 5
depo            199         0               0 / 5
belirtec        0           199             5 / 5
belirtec+liste  199         199             0 / 5

cerezde tasinan bayt, sepetteki kitap sayisina gore
yerlesim        1 kitap   8 kitap   32 kitap  128 kitap 
surec           43        43        43        43        
depo            43        43        43        43        
belirtec        143       217       473       1497
belirtec+liste  143       217       473       1497
```

İki yüz istekte 199 okuma ya da 199 doğrulama sayıldı; eksik olan tek istek çerezsiz gelen
ilkidir. Sayılar koşumdan bağımsız: depo istek başına bir okuma, belirteç istek başına bir
doğrulama yapar. Bedelleri aynı yerde ödenmez — depo okuması ağ üzerinden gider ve düğüm
sayısı arttıkça deponun üstüne biner, imza doğrulaması düğümle birlikte ölçeklenir.

Çerez sütunu belirtecin bedelidir. Kimlikle yetinen iki yerleşimde çerez sepet ne kadar
büyürse büyüsün 43 baytta duruyor; belirteçte sepet 128 kitaba çıktığında 1497 bayta
çıkıyor ve bu bayt **her istekte iki yönde** taşınıyor.

Son sütun kararı belirleyen yerdir. Çıkıştan sonra gönderilen beş istek paylaşılan depoda
sıfır kez, imzalı belirteçte **beşinde de** kabul edildi: düğüm imzayı doğruluyor, imza
geçerli, oturum yaşıyor. Belirteç kendi başına iptal edilemez. `IPTAL_LISTESI` açıldığında
iptal çalışıyor — ama `belirtec+liste` satırı hem 199 doğrulama hem 199 depo okuması
gösteriyor: iptal edilebilirlik, belirtecin depodan kaçma üstünlüğünü geri veriyor.

#### Yanlış Anahtarın Sessizliği

Belirteç yerleşiminin doğru ayarı, bütün düğümlerin aynı imza anahtarını taşımasıdır. Üç
düğümden birine başka bir anahtar verildiğinde ne olduğu sayılabilir.

```js
// yanlis-anahtar.mjs — uc dugumden birine baska imza anahtari verilirse ne olur
import { dugumBaslat, durdur, temizle, istemci, sirayla } from './dagitici.mjs';

async function olc(ucuncununAnahtari) {
  const d = [await dugumBaslat('belirtec', 'a', 8716), await dugumBaslat('belirtec', 'b', 8717),
             await dugumBaslat('belirtec', 'c', 8718, { IMZA_ANAHTARI: ucuncununAnahtari })];
  const uye = istemci('U-0001');
  let sifirlanan = 0, hataKodu = 0;
  const izlek = [];
  for (let i = 0; i < 12; i += 1) {
    const yanit = await uye.cagir(sirayla(d, i), `/sepet?kitap=K-${400 + i}`);
    if (yanit.yeni && i > 0) sifirlanan += 1;
    izlek.push(`${yanit.dugum}:${yanit.sepet}`);
  }
  durdur(d);
  return { sifirlanan, hataKodu, izlek: izlek.join(' ') };
}

for (const [etiket, anahtar] of [['dogru ayar    ', 'kutuphane-imza-anahtari'],
                                 ['yanlis anahtar', 'eski-imza-anahtari']]) {
  const s = await olc(anahtar);
  console.log(`${etiket}: sessizce sifirlanan oturum ${s.sifirlanan}/11, hata kodu donen istek ${s.hataKodu}/12`);
  console.log(`  dugum:sepet  ${s.izlek}`);
}
temizle();
```

```
dogru ayar    : sessizce sifirlanan oturum 0/11, hata kodu donen istek 0/12
  dugum:sepet  a:1 b:2 c:3 a:4 b:5 c:6 a:7 b:8 c:9 a:10 b:11 c:12
yanlis anahtar: sessizce sifirlanan oturum 7/11, hata kodu donen istek 0/12
  dugum:sepet  a:1 b:2 c:1 a:1 b:2 c:1 a:1 b:2 c:1 a:1 b:2 c:1
```

İzlek satırı farkı tek bakışta veriyor: doğru ayarda sepet birden on ikiye kadar sayıyor,
yanlış anahtarla ikiyi geçemiyor ve her üçüncü istekte başa dönüyor. On iki isteğin tamamı
her iki koşumda da başarıyla döndü; hata kodu sayısı sıfır. Anahtarı farklı olan düğüm hata
bildirmiyor, doğrulayamadığı belirteci **tanımadı** sayıp yeni bir oturum açıyor — sonra
kendi anahtarıyla imzaladığı çerezi geri gönderiyor ve diğer iki düğüm de onu tanımıyor.
Ayar hatası, kaybolan sepet olarak görünüyor.

#### Ayarın Yeri

Yerleşim kararı tek bir ayar değildir; kaç yere dağıldığı yerleşime göre değişir.

| Ayar | Katman | Kaç yerde durur |
|---|---|---|
| oturum yerleşimi | uygulama kodu | her düğümde bir |
| yapışkanlık kuralı | istek dağıtıcı | tek yerde |
| depo adresi | ortam değişkeni | her düğümde bir |
| imza anahtarı | ortam değişkeni | her düğümde bir |

Dört düğümlü bir kurulumda süreç belleği beş ayar noktası ister (dört düğüm artı
dağıtıcıdaki yapışkanlık kuralı), paylaşılan depo sekiz, imzalı belirteç sekiz, iptal
listeli belirteç on iki; yirmi düğümde yirmi bir, kırk, kırk ve altmış. Anlamlı olan
toplam değil, **her düğümde bir** satırlarıdır: oradaki bir değerin tek bir düğümde farklı
kalması yukarıdaki sessiz sonucu üretir. Bu yüzden imza anahtarı ve depo adresi tek
kaynaktan dağıtılır, düğüm başına elle girilmez.

#### Özet

- Sepet oturumda durur; oturum üç yerden birine konur: süreç belleği, paylaşılan depo,
  istemcideki imzalı belirteç. Tek düğümde üçü de aynı davranır.
- Sırayla dağıtılan sekiz ekleme, süreç belleğinde sepeti hiç biriktirmedi: sekiz istek de
  başarıyla döndü, son okumada sepet sıfır çıktı, hiçbiri hata bildirmedi.
- Düğüm sayısı ikiden üçe çıktığında on iki üyenin yedisi taşındı; süreç belleğinde o yedi
  sepet boşaldı, diğer iki yerleşimde sıfır.
- Paylaşılan depo istek başına bir okuma yapar ve iptali destekler; imzalı belirteç okuma
  yapmaz ama çerezi 43 bayttan 1497 bayta çıkarır ve çıkıştan sonraki beş isteğin beşini de
  kabul eder. İptal listesi eklenince depo okuması geri gelir.
- Üç düğümden birine farklı imza anahtarı verildiğinde on bir istekten yedisi sessizce yeni
  oturum açtı; hata kodu dönen istek sayısı sıfır kaldı.

#### Sonraki Adım

Bu dersin son ölçümü düğüm sayısını ikiden üçe çıkardı ve bunun oturumlara ne yaptığını
saydı. Düğüm kümesini değiştiren tek olay ölçekleme değildir: her yayın da düğümleri
durdurur, yenisini başlatır, bir süre ikisini birlikte çalıştırır. Sonraki ders yayının
kendisini ölçer: sabit hızda istek akarken sürüm değiştirildiğinde kaç istek düşer, bu kaç
saniyelik kesintiye denk gelir, açık bağlantıları boşaltmak bu sayıyı ne kadar düşürür ve
geri alma kaç adımda tamamlanır.

### Sıfır Kesintili Yayın

Source: https://academia.sh/tr/kurslar/backend-uretim/sifir-kesintili-yayin

Yayın sırasında düşen istek üç düzende sayılır: doğrudan değiştirme, mavi–yeşil ve aşamalı yayın. Düşen istek eşdeğer kesinti saniyesine çevrilir, bağlantı boşaltmanın uçuştaki isteklere etkisi ölçülür ve geri almanın adım sayısı karşılaştırılır.

Önceki dersin son ölçümü düğüm sayısını ikiden üçe çıkardı ve bunun oturumlara ne yaptığını
saydı. Düğüm kümesini değiştiren ikinci olay yayındır: bir sürüm durur, başkası açılır ve
bir süre ikisi birlikte hizmet verir. Ölçekleme bu değişikliği seyrek yapar, yayın sık.

Bu ders bölünmüş ödünç sisteminin ödünç servisini üç ayrı düzende yayınlar ve tek bir şeyi
sayar: **yayın sırasında kaç istek düştü**. Düşen istek sayısı, akan yükün hızına
bölündüğünde kesinti saniyesine çevrilir; kesinti "sistem kapalıydı" demenin sayılabilir
biçimidir.

- **DP13.** Yeni sürecin açılıp istek almaya hazır olması, düzenekte **20 isteklik** bir
  pencereye denk gelir. Gerçek bir kurulumda bu pencere sürecin açılış süresine ve o anki
  istek hızına bağlıdır; burada sabit tutulur ki düzenler karşılaştırılabilsin.
- **DP14.** Ödünç servisinin iki sürümü aynı sözleşmeyi karşılar; istemci hangi sürüme
  düştüğünü ayırt etmez. Sözleşmenin kendisi değişiyorsa bu ders geçerli değildir.
- **DP15.** Yük sıralı akar: her istek bir öncekinin yanıtından sonra gönderilir. Eşdeğer
  kesinti saniyesi, saniyede 50 istek varsayımıyla hesaplanır.

#### Ölçüm Düzeneği

İki süreç türü var. Birincisi ödünç servisinin bir sürümü; hangi sürüm olduğunu ve
kapanırken ne yapacağını dışarıdan alır.

```js
// surum.mjs — odunc servisinin bir surumu; SURUM, GECIKME ve kapanma turu disaridan gelir
import { createServer } from 'node:http';

const SURUM = process.env.SURUM;
const GECIKME = Number(process.env.GECIKME ?? 0);
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

const sunucu = createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, 'http://yerel');
  if (yol.pathname === '/kapan') {
    yanit.writeHead(200).end('kapaniyor');
    if (yol.searchParams.get('tur') === 'zarif') {
      // Yeni baglanti alinmaz, ucustaki istekler bitirilir, sonra surec cikar.
      sunucu.close(() => process.exit(0));
      sunucu.closeIdleConnections();
      setTimeout(() => process.exit(0), 5000).unref();
    } else {
      process.exit(0);                      // ucustaki istekler yarida kalir
    }
    return;
  }
  if (GECIKME) await bekle(GECIKME);
  yanit.writeHead(200, { 'content-type': 'application/json' })
    .end(JSON.stringify({ surum: SURUM, port: Number(process.env.PORT) }));
});
sunucu.listen(Number(process.env.PORT), () => console.log(`hazir ${SURUM}`));
```

İkincisi ön yüzdür: istemcinin bildiği tek adres. Arkasındaki havuzu bir denetim ucundan
değiştirir; havuz boşken istek gidecek yer bulamaz ve 503 döner. Yayın düzenleri arasındaki
bütün fark, bu havuzun hangi sırayla değiştirildiğidir.

```js
// onyuz.mjs — istegi havuzdaki sureclere sirayla dagitir; havuz bosken 503 doner
import { createServer } from 'node:http';

let havuz = [];
let sira = 0;

createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, 'http://yerel');
  if (yol.pathname === '/havuz') {
    havuz = (yol.searchParams.get('hedef') ?? '').split(',').filter(Boolean);
    sira = 0;
    return yanit.writeHead(200).end(havuz.join(','));
  }
  if (havuz.length === 0) return yanit.writeHead(503).end('havuz bos');
  const hedef = havuz[sira++ % havuz.length];
  try {
    const ust = await fetch(`http://127.0.0.1:${hedef}${istek.url}`);
    yanit.writeHead(ust.status, { 'content-type': 'application/json' }).end(await ust.text());
  } catch {
    yanit.writeHead(502).end('ust akis yanit vermedi');
  }
}).listen(Number(process.env.PORT), () => console.log('onyuz hazir'));
```

```sh
SURUM=v1 PORT=8731 node surum.mjs > /dev/null &
ESKI=$!
PORT=8730 node onyuz.mjs > /dev/null &
ON=$!
sleep 0.6
curl -s -o /dev/null -w 'havuz bosken durum kodu: %{http_code}\n' 'http://127.0.0.1:8730/odunc'
curl -s -o /dev/null 'http://127.0.0.1:8730/havuz?hedef=8731'
curl -s -w '\n' 'http://127.0.0.1:8730/odunc'
kill $ESKI $ON
```

```
havuz bosken durum kodu: 503
{"surum":"v1","port":8731}
```

Düşen isteğin tanımı bu 503'tür: istemci istedi, sistem veremedi.

#### Üç Düzen

Üç düzen de aynı işi yapar — `v1` gider, `v2` gelir — ve aynı yük altında koşar. Ayrıldıkları
yer, eski sürümün ne zaman durdurulduğudur. Doğrudan değiştirmede eskiyi durdurmak yeniyi
açmanın önkoşuludur; mavi–yeşilde yeni sürüm eski hâlâ hizmet verirken ayağa kalkar;
aşamalıda havuz karışık kalır ve yeni sürümün payı adım adım büyür.

```js
// yayin.mjs — uc yayin duzeninde akan yuk: dogrudan degistirme, mavi-yesil, asamali
import { spawn } from 'node:child_process';

const ONYUZ = 8730, HIZ = 50, YUK = 20, PENCERE = 20;
const surecler = new Map();

async function baslat(surum, port) {
  const cocuk = spawn(process.execPath, ['surum.mjs'], { stdio: ['ignore', 'pipe', 'inherit'],
    env: { ...process.env, SURUM: surum, PORT: String(port) } });
  await new Promise((c) => cocuk.stdout.once('data', c));
  surecler.set(port, cocuk);
}
const durdur = (portlar) => portlar.forEach((p) => { surecler.get(p)?.kill('SIGKILL'); surecler.delete(p); });
const havuza = (portlar) => fetch(`http://127.0.0.1:${ONYUZ}/havuz?hedef=${portlar}`).then((y) => y.text());

async function akit(n) {
  const say = { v1: 0, v2: 0, dusen: 0 };
  for (let i = 0; i < n; i += 1) {
    try {
      const y = await fetch(`http://127.0.0.1:${ONYUZ}/odunc`);
      if (y.ok) say[(await y.json()).surum] += 1;
      else { await y.text(); say.dusen += 1; }
    } catch { say.dusen += 1; }
  }
  return say;
}

async function dogrudan() {
  const adim = [], asama = [];
  await baslat('v1', 8731); await baslat('v1', 8732); await havuza('8731,8732');
  asama.push(['eski surum hizmet veriyor', await akit(YUK)]);
  adim.push('eski surecleri durdur ve havuzu bosalt'); durdur([8731, 8732]); await havuza('');
  asama.push(['eski durdu, yeni acilmadi', await akit(PENCERE)]);
  adim.push('yeni surumu ayaga kaldir'); await baslat('v2', 8733); await baslat('v2', 8734);
  adim.push('havuza yeni surumu koy'); await havuza('8733,8734');
  asama.push(['yeni surum hizmet veriyor', await akit(YUK)]);
  const geri = ['yeni surecleri durdur ve havuzu bosalt', 'eski surumu ayaga kaldir', 'havuza eski surumu koy'];
  durdur([8733, 8734]); await havuza('');
  const geriSay = await akit(PENCERE);
  await baslat('v1', 8731); await baslat('v1', 8732); await havuza('8731,8732');
  durdur([8731, 8732]);
  return { ad: 'dogrudan degistirme', adim, asama, geri, geriSay };
}

async function maviYesil() {
  const adim = [], asama = [];
  await baslat('v1', 8731); await baslat('v1', 8732); await havuza('8731,8732');
  asama.push(['mavi hizmet veriyor', await akit(YUK)]);
  adim.push('yesili ayaga kaldir'); await baslat('v2', 8733); await baslat('v2', 8734);
  asama.push(['yesil ayakta, havuzda degil', await akit(YUK)]);
  adim.push('havuzu yesile cevir'); await havuza('8733,8734');
  asama.push(['havuz yesile cevrildi', await akit(YUK)]);
  const geriSay = await (async () => { await havuza('8731,8732'); return akit(YUK); })();
  adim.push('maviyi bosalt ve kapat');
  durdur([8731, 8732, 8733, 8734]);
  return { ad: 'mavi-yesil', adim, asama, geri: ['havuzu maviye geri cevir'], geriSay };
}

async function asamali() {
  const adim = [], asama = [];
  await baslat('v1', 8731); await baslat('v1', 8732); await havuza('8731,8732');
  asama.push(['yeni surumun payi yok', await akit(YUK)]);
  adim.push('yeni surumun ilk dugumunu ayaga kaldir'); await baslat('v2', 8733);
  adim.push('havuzda uc dugumun biri yeni'); await havuza('8731,8732,8733');
  asama.push(['uc dugumun biri yeni', await akit(YUK)]);
  adim.push('ikinci yeni dugum, bir eski dugum havuzdan cikar'); await baslat('v2', 8734);
  await havuza('8731,8733,8734');
  asama.push(['uc dugumun ikisi yeni', await akit(YUK)]);
  adim.push('havuz butunuyle yeni surume gecer'); await havuza('8733,8734');
  asama.push(['butun havuz yeni', await akit(YUK)]);
  const geriSay = await (async () => { await havuza('8731,8732'); return akit(YUK); })();
  adim.push('eski dugumleri bosalt ve kapat');
  durdur([8731, 8732, 8733, 8734]);
  return { ad: 'asamali', adim, asama, geri: ['havuzu eski surume geri cevir'], geriSay };
}

const onyuz = spawn(process.execPath, ['onyuz.mjs'], { stdio: ['ignore', 'pipe', 'inherit'],
  env: { ...process.env, PORT: String(ONYUZ) } });
await new Promise((c) => onyuz.stdout.once('data', c));

const sonuc = [await dogrudan(), await maviYesil(), await asamali()];
for (const s of sonuc) {
  console.log(`${s.ad} — yayin adimi ${s.adim.length}`);
  for (const [etiket, say] of s.asama) {
    console.log(`  ${etiket.padEnd(32)} v1=${String(say.v1).padEnd(3)} v2=${String(say.v2).padEnd(3)} dusen=${say.dusen}`);
  }
  console.log(`  ${`geri alma, ${s.geri.length} adim`.padEnd(32)} ${' '.repeat(14)} dusen=${s.geriSay.dusen}`);
}
console.log('');
console.log(`akan yuk saniyede ${HIZ} istek varsayimiyla`);
console.log('duzen                yayin adimi  dusen istek  esdeger kesinti (sn)  geri alma adimi  geri almada dusen');
for (const s of sonuc) {
  const dusen = s.asama.reduce((t, [, y]) => t + y.dusen, 0);
  console.log(`${s.ad.padEnd(20)} ${String(s.adim.length).padEnd(12)} ${String(dusen).padEnd(12)} ` +
    `${(dusen / HIZ).toFixed(2).padEnd(21)} ${String(s.geri.length).padEnd(16)} ${s.geriSay.dusen}`);
}
onyuz.kill('SIGKILL');
```

```
dogrudan degistirme — yayin adimi 3
  eski surum hizmet veriyor        v1=20  v2=0   dusen=0
  eski durdu, yeni acilmadi        v1=0   v2=0   dusen=20
  yeni surum hizmet veriyor        v1=0   v2=20  dusen=0
  geri alma, 3 adim                               dusen=20
mavi-yesil — yayin adimi 3
  mavi hizmet veriyor              v1=20  v2=0   dusen=0
  yesil ayakta, havuzda degil      v1=20  v2=0   dusen=0
  havuz yesile cevrildi            v1=0   v2=20  dusen=0
  geri alma, 1 adim                               dusen=0
asamali — yayin adimi 5
  yeni surumun payi yok            v1=20  v2=0   dusen=0
  uc dugumun biri yeni             v1=14  v2=6   dusen=0
  uc dugumun ikisi yeni            v1=7   v2=13  dusen=0
  butun havuz yeni                 v1=0   v2=20  dusen=0
  geri alma, 1 adim                               dusen=0

akan yuk saniyede 50 istek varsayimiyla
duzen                yayin adimi  dusen istek  esdeger kesinti (sn)  geri alma adimi  geri almada dusen
dogrudan degistirme  3            20           0.40                  3                20
mavi-yesil           3            0            0.00                  1                0
asamali              5            0            0.00                  1                0
```

Doğrudan değiştirmenin kesintisi bir yanlışlıktan doğmuyor; **düzenin kendisinden**
doğuyor. Eskiyi durdurmadan yeniyi aynı yere koyamayan bir kurulumda, aradaki pencere
boyunca gidecek yer yoktur. Yirmi istek düştü ve saniyede elli istekle bu 0,40 saniyelik
kesintiye denk geliyor. Sayı küçük görünür; günde on yayın yapan bir ekipte dört saniyelik
bir kesintidir ve tamamı kullanıcıya hata olarak gider.

Mavi–yeşil aynı işi sıfır düşen istekle bitirdi. Farkı ikinci satırda görünüyor: yeşil
ayağa kalkarken mavi hizmet vermeye devam etti, yani açılış penceresi yükün dışında kaldı.
Bunun bedeli, o pencere boyunca **iki sürümün birden ayakta** olmasıdır — yayın anında
kaynak gereksinimi iki katına çıkar.

Aşamalı yayının tablosu ikinci ve üçüncü satırda karışık pay gösteriyor: üç düğümün biri
yeniyken yirmi isteğin altısı, ikisi yeniyken on üçü yeni sürüme gitti. Ön yüz havuzu her
değiştiğinde sırayı sıfırladığı için bu paylar koşumdan bağımsızdır. Aşamalı yayının
kazandırdığı şey kesinti değil — mavi–yeşilde de sıfırdı — **hatalı sürümün kaç isteğe
dokunacağıdır**: yeni sürüm ikinci aşamada bozuk çıksaydı yirmi isteğin altısı etkilenirdi,
mavi–yeşilde yirmisi de.

Geri alma sütunu bu dersin asıl kararını taşıyor. Mavi–yeşilde ve aşamalıda geri alma tek
adımdır — havuzu eski sürüme çevirmek — ve sıfır istek düşürür, çünkü eski süreçler hâlâ
ayaktadır. Doğrudan değiştirmede geri alma yeni bir yayındır: üç adım, yirmi düşen istek.
Buradan okunacak kural, eski süreçlerin ayakta tutulduğu sürenin adı olan **geri alma
penceresidir**; o pencere kapandığı anda geri alma tek adım olmaktan çıkar.

#### Bağlantı Boşaltma

Havuz değiştiğinde eski düğüme yeni istek gitmez, ama o anda **uçuşta** olan istekler
vardır. Onların ne olduğu, eski sürecin nasıl kapatıldığına bağlıdır.

```js
// bosaltma.mjs — eski dugum kapatilirken ucusta kalan istekler: sert ve zarif kapanma
import { spawn } from 'node:child_process';

const PORT = 8735, UCUSTA = 8, GECIKME = 500;
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

async function olc(tur) {
  const cocuk = spawn(process.execPath, ['surum.mjs'], { stdio: ['ignore', 'pipe', 'inherit'],
    env: { ...process.env, SURUM: 'v1', PORT: String(PORT), GECIKME: String(GECIKME) } });
  await new Promise((c) => cocuk.stdout.once('data', c));

  const ucus = Array.from({ length: UCUSTA }, () =>
    fetch(`http://127.0.0.1:${PORT}/odunc`).then((y) => y.ok).catch(() => false));
  await bekle(150);                                  // sekiz istek ucusta, hicbiri bitmedi
  await fetch(`http://127.0.0.1:${PORT}/kapan?tur=${tur}`).catch(() => {});
  const sonuc = await Promise.all(ucus);
  cocuk.kill('SIGKILL');
  return { tur, tamamlanan: sonuc.filter(Boolean).length };
}

console.log(`kapanma aninda ucusta ${UCUSTA} istek var, her biri ${GECIKME} ms suruyor`);
console.log('kapanma  tamamlanan  dusen');
for (const tur of ['sert', 'zarif']) {
  const s = await olc(tur);
  console.log(`${s.tur.padEnd(8)} ${String(s.tamamlanan).padEnd(11)} ${UCUSTA - s.tamamlanan}`);
}
```

```
kapanma aninda ucusta 8 istek var, her biri 500 ms suruyor
kapanma  tamamlanan  dusen
sert     0           8
zarif    8           0
```

Doğru ayarın ölçüsü budur: zarif kapanmada uçuştaki sekiz isteğin sekizi de tamamlandı,
sert kapanmada sekizi de düştü. Aradaki tek fark, kapanma işaretini alan sürecin hemen
çıkması ile açık isteklerin bitmesini beklemesidir.

#### Sessiz Olan Ne

Bu iki ölçüm aynı anın iki tarafını gösteriyor ve birbirini yalanlıyor gibi görünüyor.
Yayın tablosunda mavi–yeşilin düşen istek sayısı sıfırdı; boşaltma tablosunda aynı geçişte
sekiz istek düştü. İkisi de doğru. Yayın tablosunu üreten yük, ön yüzden geçen **yeni**
isteklerdir ve havuz çevrildikten sonra hepsi yeşile gitti, hiçbiri düşmedi. Sekiz düşen
istek, havuz çevrilmeden **önce** maviye yönlendirilmiş ve hâlâ yanıt bekleyen isteklerdir.
Ön yüzün sayaçlarında görünmezler.

Yanlış ayarın sessiz sonucu tam olarak budur: kapanma türü sert bırakıldığında yayın
"sıfır kesintili" raporlanmaya devam eder, çünkü ön yüz hiç boş havuz görmemiştir ve
düğümler açılışta hazır bildirmiştir. Uygulama hata da vermez — süreç düzgün bir çıkış
koduyla kapanır. Fark yalnız uçuştaki isteklerin sayısında görünür ve o sayı yükle birlikte
büyür: saniyede elli istek alan, ortalama yarım saniye süren bir uçta her kapanmada
yaklaşık yirmi beş istek uçuştadır.

Ayarların durduğu yer de düzenler arasında değişiyor. Havuz üyeliği **tek bir yerde**, ön
yüzdedir; bir yayın sırasında birkaç kez değişir ama hep aynı yerden. Kapanma türü ise
**her düğümde** durur: sürece hangi işaretin gönderileceği süreç yöneticisinin ayarıdır,
o işaretin nasıl karşılandığı uygulama kodundadır. İkisinin uyuşmaması — süreç yöneticisi
zarif kapanma işareti gönderirken uygulamanın onu dinlememesi — tabloda sert kapanma
satırını üretir, hiçbir hata kaydı bırakmadan.

#### Özet

- Düşen istek, ön yüzün gidecek yer bulamadığı istektir; düşen istek sayısı yük hızına
  bölününce eşdeğer kesinti saniyesini verir.
- Doğrudan değiştirmede kesinti düzenin kendisinden gelir: eskiyi durdurmak yeniyi açmanın
  önkoşulu olduğu için açılış penceresi boyunca yirmi istek düştü, 0,40 saniye.
- Mavi–yeşil ve aşamalı yayın sıfır istek düşürdü; bedeli, yayın penceresinde iki sürümün
  birden ayakta durmasıdır.
- Aşamalı yayının kazancı kesinti değil etki alanıdır: üç düğümün biri yeniyken yirmi
  isteğin altısı yeni sürüme gitti.
- Geri alma mavi–yeşilde ve aşamalıda tek adım ve sıfır düşen istek; doğrudan değiştirmede
  üç adım ve yirmi düşen istektir. Eski süreçlerin ayakta kaldığı süre geri alma
  penceresidir.
- Sert kapanmada uçuştaki sekiz isteğin sekizi de düştü, zarif kapanmada sıfırı; bu fark
  ön yüzün sayaçlarında görünmez.

#### Sonraki Adım

Bu dersin üç düzeni de bir varsayıma yaslandı: iki sürüm aynı anda ayakta durabilir ve
aynı isteği aynı biçimde karşılayabilir. Süreçler için doğru olan bu varsayım, altlarındaki
şema için kendiliğinden doğru değildir. Mavi–yeşil geçişte iki sürüm aynı veritabanını
okur; aşamalı yayında bu birlikte yaşama daha da uzun sürer. Sonraki ders bu pencereyi
ölçer: göç ile kod yayınının hangi sırada gittiği, yanlış sırada kaç isteğin hata aldığı,
iki sürümün aynı şemayla çalışabildiği aralık ve geri alınamayan adımın nerede durduğu.

### Veritabanı Göçü ile Yayın Sırası

Source: https://academia.sh/tr/kurslar/backend-uretim/veritabani-gocu-ile-yayin-sirasi

Aynı şema değişikliği üç ayrı sırada koşturulur ve her sırada kaç isteğin hata aldığı sayılır: göç önce, kod önce ve genişlet–daralt. İki sürümün aynı şemayla çalıştığı pencere, çift yazmanın işlevi ve sütun silmenin geri alınamazlığı ölçülür.

Önceki dersin üç yayın düzeni de aynı varsayıma yaslandı: iki sürüm bir süre birlikte
ayakta durabilir. Süreçler için sağlanan bu varsayım, altlarındaki şema için kendiliğinden
sağlanmaz. Mavi–yeşil geçişte iki sürüm aynı veritabanını okur; aşamalı yayında bu birlikte
yaşama daha uzun sürer. Şema iki sürümden yalnız birini destekliyorsa yayın düzeninin
kesintisizliği bir şey ifade etmez.

Bu dersin konusu göçün kendisi değil, **sırasıdır**: göç ile kod yayınının hangisi önce
gider. Göç adımlarının teste alınması `tumlesim/04`'te, sözleşmenin sürümler arası
evrimi M16/K06'da kuruldu; burada sorulan tek soru, iki işlemin arasındaki pencerede
sistemin ne yaptığıdır. Ölçü, o pencerede **hata alan istek sayısıdır**.

- **DP16.** Göç adımları uygulamadan ayrı koşar ve kod yayınıyla aynı işlemin parçası
  değildir. Sıra sorusu bu ayrılıktan doğar.
- **DP17.** Şema tek bir veritabanında durur ve bütün sürümler aynı veritabanını okur.
- **DP18.** Üye kaydının iletişim alanı serbest metindir: kimi üyede e-posta, kimisinde
  telefon numarası. Değişiklik bu alanı yalnız e-posta tutan bir alana daraltır.

#### İki Sürüm, Tek Şema

Üye ucunun üç sürümü var. `v1` eski sütunu okur ve yazar, `v2` yeni sütunu okur ama iki
sütuna birden yazar, `v3` yalnız yeni sütunu tanır. Üçü de aynı veritabanı dosyasını açar.

```js
// servis.mjs — uye ucunun bir surumu; SURUM hangi sutunu okuyup yazacagini belirler
import { createServer } from 'node:http';
import { DatabaseSync } from 'node:sqlite';

const SURUM = process.env.SURUM;
const db = new DatabaseSync(process.env.DB_YOLU);

const OKU = {
  v1: 'SELECT uye_no, ad, iletisim AS deger FROM uye WHERE uye_no = ?',
  v2: 'SELECT uye_no, ad, eposta AS deger FROM uye WHERE uye_no = ?',
  v3: 'SELECT uye_no, ad, eposta AS deger FROM uye WHERE uye_no = ?',
};
const YAZ = {
  v1: 'INSERT INTO uye (uye_no, ad, iletisim) VALUES (?, ?, ?)',
  v2: 'INSERT INTO uye (uye_no, ad, iletisim, eposta) VALUES (?, ?, ?, ?)',  // cift yazma
  v3: 'INSERT INTO uye (uye_no, ad, eposta) VALUES (?, ?, ?)',
};

createServer((istek, yanit) => {
  const yol = new URL(istek.url, 'http://yerel');
  const no = yol.searchParams.get('no');
  const bas = { 'content-type': 'application/json' };
  try {
    if (istek.method === 'POST') {
      const ad = yol.searchParams.get('ad');
      const deger = yol.searchParams.get('deger');
      db.prepare(YAZ[SURUM]).run(...(SURUM === 'v2' ? [no, ad, deger, deger] : [no, ad, deger]));
      return yanit.writeHead(201, bas).end(JSON.stringify({ surum: SURUM, yazildi: no }));
    }
    const satir = db.prepare(OKU[SURUM]).get(no);
    yanit.writeHead(200, bas).end(JSON.stringify({ surum: SURUM, ...satir }));
  } catch (e) {
    yanit.writeHead(500, bas).end(JSON.stringify({ surum: SURUM, hata: e.message }));
  }
}).listen(Number(process.env.PORT), () => console.log(`hazir ${SURUM}`));
```

Göç yapılmamış bir şema üzerinde ikisi birden ayağa kaldırılıp aynı üye sorulduğunda fark
görünür.

```sh
node -e "const {DatabaseSync} = require('node:sqlite');
  const db = new DatabaseSync('katalog.db');
  db.exec('CREATE TABLE uye (uye_no TEXT PRIMARY KEY, ad TEXT NOT NULL, iletisim TEXT)');
  db.prepare('INSERT INTO uye VALUES (?, ?, ?)').run('U-0001', 'Ayse Demir', 'ayse@kutuphane')"
SURUM=v1 PORT=8741 DB_YOLU=katalog.db node servis.mjs > /dev/null &
ESKI=$!
SURUM=v2 PORT=8742 DB_YOLU=katalog.db node servis.mjs > /dev/null &
YENI=$!
sleep 0.6
curl -s -w '\n' 'http://127.0.0.1:8741/uye?no=U-0001'
curl -s -w '\n' 'http://127.0.0.1:8742/uye?no=U-0001'
kill $ESKI $YENI
```

```
{"surum":"v1","uye_no":"U-0001","ad":"Ayse Demir","deger":"ayse@kutuphane"}
{"surum":"v2","hata":"no such column: eposta"}
```

Şema bir sürümü destekliyor, diğerini desteklemiyor. Yayın sırasının bütün sorunu, bu
durumun **ne kadar sürdüğü** ve o sürede hangi sürümün yükü taşıdığıdır.

#### Aynı Değişiklik, Üç Sıra

Üç sıra da aynı sonuca varır: iletişim alanı e-posta alanına dönüşür. Birincisi göçü öne
alır, ikincisi kod yayınını öne alır, üçüncüsü değişikliği geriye uyumlu adımlara böler.
Her sırada her aşamada yirmi istek akıtılır ve kaçının hata aldığı sayılır.

```js
// sira.mjs — ayni sema degisikligi uc ayri sirada: yikici goc once, kod once, genislet-daralt
import { spawn } from 'node:child_process';
import { DatabaseSync } from 'node:sqlite';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const kok = mkdtempSync(join(tmpdir(), 'goc-sira-'));
const YUK = 20;
const UYELER = [
  ['U-0001', 'Ayse Demir', 'ayse@kutuphane'], ['U-0002', 'Emre Koc', '0500 000 0002'],
  ['U-0003', 'Elif Sahin', 'elif@kutuphane'], ['U-0004', 'Mert Yilmaz', '0500 000 0004'],
  ['U-0005', 'Zeynep Ak', 'zeynep@kutuphane'], ['U-0006', 'Can Ozturk', '0500 000 0006'],
  ['U-0007', 'Deniz Kaya', 'deniz@kutuphane'], ['U-0008', 'Bora Tas', '0500 000 0008'],
  ['U-0009', 'Sema Gul', 'sema@kutuphane'], ['U-0010', 'Kerem Yurt', '0500 000 0010'],
  ['U-0011', 'Nil Aydin', 'nil@kutuphane'], ['U-0012', 'Efe Duran', 'efe@kutuphane'],
];

function veritabaniKur(yol) {
  const db = new DatabaseSync(yol);
  db.exec('CREATE TABLE uye (uye_no TEXT PRIMARY KEY, ad TEXT NOT NULL, iletisim TEXT)');
  const ekle = db.prepare('INSERT INTO uye VALUES (?, ?, ?)');
  for (const u of UYELER) ekle.run(...u);
  return db;
}

const GOC = {
  yikici: (db) => db.exec('ALTER TABLE uye RENAME COLUMN iletisim TO eposta'),
  genislet: (db) => {
    db.exec('ALTER TABLE uye ADD COLUMN eposta TEXT');
    db.exec("UPDATE uye SET eposta = iletisim WHERE instr(iletisim, '@') > 0");
  },
  daralt: (db) => db.exec('ALTER TABLE uye DROP COLUMN iletisim'),
};

const cocuklar = [];
async function baslat(surum, port, yol) {
  const cocuk = spawn(process.execPath, ['servis.mjs'], { stdio: ['ignore', 'pipe', 'inherit'],
    env: { ...process.env, SURUM: surum, PORT: String(port), DB_YOLU: yol } });
  await new Promise((c) => cocuk.stdout.once('data', c));
  cocuklar.push(cocuk);
  return port;
}
const kapat = () => { cocuklar.forEach((c) => c.kill('SIGKILL')); cocuklar.length = 0; };

async function akit(portlar, n) {
  const say = { tamam: 0, hata: 0 };
  for (let i = 0; i < n; i += 1) {
    const y = await fetch(`http://127.0.0.1:${portlar[i % portlar.length]}/uye?no=${UYELER[i % UYELER.length][0]}`);
    say[y.ok ? 'tamam' : 'hata'] += 1;
    await y.text();
  }
  return say;
}
const yaz = (port, no, ad, deger) =>
  fetch(`http://127.0.0.1:${port}/uye?no=${no}&ad=${ad}&deger=${deger}`, { method: 'POST' }).then((y) => y.json());
const oku = (port, no) => fetch(`http://127.0.0.1:${port}/uye?no=${no}`).then((y) => y.json());

async function kur(ad) {
  const yol = join(kok, `${ad}.db`);
  const db = veritabaniKur(yol);
  const [v1, v2, v3] = [await baslat('v1', 8741, yol), await baslat('v2', 8742, yol),
                        await baslat('v3', 8743, yol)];
  return { db, v1, v2, v3, faz: [], adim: [] };
}

async function gocOnce() {
  const o = await kur('goc-once');
  o.faz.push(['eski surum, goc yapilmadi', await akit([o.v1], YUK)]);
  o.adim.push('yikici goc'); GOC.yikici(o.db);
  o.faz.push(['goc gecti, kod hala eski', await akit([o.v1], YUK)]);
  o.adim.push('yeni surumun yayini');
  o.faz.push(['yeni surum yayinlandi', await akit([o.v2], YUK)]);
  kapat();
  return { ad: 'once goc sonra kod', ...o, birlikte: 0 };
}

async function kodOnce() {
  const o = await kur('kod-once');
  o.faz.push(['eski surum, goc yapilmadi', await akit([o.v1], YUK)]);
  o.adim.push('yeni surumun yayini');
  o.faz.push(['kod yeni, sutun yok', await akit([o.v2], YUK)]);
  o.adim.push('yikici goc'); GOC.yikici(o.db);
  o.faz.push(['goc gecti', await akit([o.v2], YUK)]);
  kapat();
  return { ad: 'once kod sonra goc', ...o, birlikte: 0 };
}

async function genisletDaralt() {
  const o = await kur('genislet-daralt');
  o.faz.push(['eski surum, goc yapilmadi', await akit([o.v1], YUK)]);
  o.adim.push('genislet: sutun eklenir ve doldurulur'); GOC.genislet(o.db);
  o.faz.push(['sema genis, kod hala eski', await akit([o.v1], YUK)]);
  o.adim.push('cift yazan surumun yayini');
  o.faz.push(['iki surum birlikte', await akit([o.v1, o.v2], YUK)]);
  await yaz(o.v2, 'U-0013', 'Cift Yazan', 'onuc@kutuphane');
  await yaz(o.v3, 'U-0014', 'Tek Yazan', 'ondort@kutuphane');
  const pencere = [await oku(o.v1, 'U-0013'), await oku(o.v1, 'U-0014')];
  o.adim.push('eski surum cekilir');
  o.faz.push(['yalniz cift yazan surum', await akit([o.v2], YUK)]);
  o.adim.push('cift yazma kaldirilir');
  o.faz.push(['cift yazma kaldirildi', await akit([o.v3], YUK)]);
  o.adim.push('daralt: eski sutun silinir'); GOC.daralt(o.db);
  o.faz.push(['sema dar, kod yeni', await akit([o.v3], YUK)]);
  const erken = await yaz(o.v2, 'U-0015', 'Erken Daralt', 'onbes@kutuphane');
  kapat();
  return { ad: 'genislet-daralt', ...o, birlikte: 1, pencere, erken };
}

const sonuc = [await gocOnce(), await kodOnce(), await genisletDaralt()];
for (const s of sonuc) {
  console.log(`${s.ad} — ${s.adim.length} adim`);
  for (const [etiket, say] of s.faz) {
    console.log(`  ${etiket.padEnd(28)} tamam=${String(say.tamam).padEnd(3)} hata=${say.hata}`);
  }
}
console.log('');
console.log('sira                  adim  gonderilen  hata alan  iki surumun birlikte kostugu faz');
for (const s of sonuc) {
  const g = s.faz.reduce((t, [, y]) => t + y.tamam + y.hata, 0);
  const h = s.faz.reduce((t, [, y]) => t + y.hata, 0);
  console.log(`${s.ad.padEnd(21)} ${String(s.adim.length).padEnd(5)} ${String(g).padEnd(11)} ` +
    `${String(h).padEnd(10)} ${s.birlikte}`);
}
console.log('');
const gd = sonuc.at(-1);
console.log('pencerede yeni surum yazdi, eski surum okudu');
for (const p of gd.pencere) console.log(`  ${p.uye_no} ${p.ad.padEnd(12)} eski surumun gordugu deger: ${p.deger}`);
console.log('');
console.log('daralt tamamlandiktan sonra cift yazan surum yazmayi denerse');
console.log(`  ${gd.erken.hata}`);

console.log('');
const db = gd.db;
const say = (kosul) => db.prepare(`SELECT COUNT(*) AS n FROM uye WHERE ${kosul}`).get().n;
db.exec('ALTER TABLE uye ADD COLUMN iletisim TEXT');
db.exec('UPDATE uye SET iletisim = eposta WHERE eposta IS NOT NULL');
console.log('daralt sonrasi geri alma: sutun yeniden eklenip epostadan dolduruldu');
console.log(`  toplam uye                 : ${say('1 = 1')}`);
console.log(`  iletisimi kurtarilan       : ${say('iletisim IS NOT NULL')}`);
console.log(`  iletisimi kurtarilamayan   : ${say('iletisim IS NULL')}`);
rmSync(kok, { recursive: true, force: true });
```

```
once goc sonra kod — 2 adim
  eski surum, goc yapilmadi    tamam=20  hata=0
  goc gecti, kod hala eski     tamam=0   hata=20
  yeni surum yayinlandi        tamam=20  hata=0
once kod sonra goc — 2 adim
  eski surum, goc yapilmadi    tamam=20  hata=0
  kod yeni, sutun yok          tamam=0   hata=20
  goc gecti                    tamam=20  hata=0
genislet-daralt — 5 adim
  eski surum, goc yapilmadi    tamam=20  hata=0
  sema genis, kod hala eski    tamam=20  hata=0
  iki surum birlikte           tamam=20  hata=0
  yalniz cift yazan surum      tamam=20  hata=0
  cift yazma kaldirildi        tamam=20  hata=0
  sema dar, kod yeni           tamam=20  hata=0

sira                  adim  gonderilen  hata alan  iki surumun birlikte kostugu faz
once goc sonra kod    2     60          20         0
once kod sonra goc    2     60          20         0
genislet-daralt       5     120         0          1

pencerede yeni surum yazdi, eski surum okudu
  U-0013 Cift Yazan   eski surumun gordugu deger: onuc@kutuphane
  U-0014 Tek Yazan    eski surumun gordugu deger: null

daralt tamamlandiktan sonra cift yazan surum yazmayi denerse
  table uye has no column named iletisim

daralt sonrasi geri alma: sutun yeniden eklenip epostadan dolduruldu
  toplam uye                 : 14
  iletisimi kurtarilan       : 9
  iletisimi kurtarilamayan   : 5
```

#### Yanlış Sıranın Ölçüsü

İlk iki sıra aynı göçü çalıştırıyor ve aynı sayıyı üretiyor: yirmi hata alan istek. Yönleri
farklı, sonuçları değil. Göç önce giderse eski sürüm kendi sütununu bulamaz; kod önce
giderse yeni sürüm henüz var olmayan sütunu arar. İkisinde de hatanın süresi, iki işlem
arasındaki boşluktur — göç akşam koşup yayın sabah yapılıyorsa bu boşluk bir gecedir.

Bu sayının anlamı, önceki dersin sayısıyla yan yana konunca ortaya çıkıyor. Orada mavi–yeşil
düzen sıfır düşen istekle ölçülmüştü. Aynı yayın, altındaki şema tek sürümü destekliyorsa
yirmi hata üretir. Yayın düzeninin kesintisizliği şemanın uyumluluğuna bağlıdır; ikisi ayrı
ayrı ölçülmediğinde birinin sıfırı diğerinin yirmisini gizler.

Genişlet–daralt sırası yüz yirmi isteğin hepsini hatasız karşıladı. Bedeli adım sayısında
görünüyor: iki adım yerine beş. Beş adımın her biri ayrı bir yayın ya da ayrı bir göç
koşumudur ve aralarında bir bekleme vardır — o beklemeler olmadan sıra bir anlam taşımaz.
Değişikliğin takvimi bu yüzden uzar: iki adımlık bir yeniden adlandırma, beş adımlık ve
birkaç yayına yayılan bir işe dönüşür.

#### Pencere ve Çift Yazma

Üçüncü faz, iki sürümün aynı şemayla birlikte koştuğu tek fazdır. Yirmi istek dönüşümlü
olarak eski ve yeni sürüme gitti, hepsi tamamlandı. Bu pencerenin ayakta durmasının koşulu,
yeni sürümün eski sütuna da yazmaya devam etmesidir.

Pencere içinde iki üye yazıldı. `U-0013` çift yazan sürümle geldi ve eski sürüm onu
okuyabildi. `U-0014` yalnız yeni sütunu yazan sürümle geldi ve eski sürümün gördüğü değer
`null` oldu. İkinci satırda hiçbir hata yok: istek 200 döndü, kayıt var, alan boş. Çift
yazmanın atlanması bir hata üretmez, **eksik veri** üretir; ve bu eksiklik yalnız eski
sürümün baktığı yerde görünür.

Çift yazmanın ne zaman kaldırılacağı da sıranın parçasıdır. Daralt adımı tamamlandıktan
sonra çift yazan sürüm yazmayı denediğinde `table uye has no column named iletisim` hatası
alıyor. Bu, dördüncü adımın — çift yazmanın kaldırılması — beşinci adımdan önce gelmesinin
nedenidir. Sıra iki uçtan da kısıtlıdır: genişletme kod yayınından önce, daraltma kod
yayınından sonra gelir.

#### Geri Alınamayan Adım

Beş adımın dördü geri alınabilir. Genişletme sütunu düşürerek, kod yayınları önceki sürüme
dönerek geri alınır ve önceki dersin ölçtüğü gibi bu tek adımlık bir iştir. Daraltma böyle
değildir.

Sütun geri eklenebilir; çıktı bunu yapıyor ve `eposta` alanından dolduruyor. On dört üyeden
dokuzunun iletişim bilgisi geri geldi, beşininki gelmedi. Kayıp olan beş üye, iletişim alanı
telefon numarası tutan üyelerdir: genişletme adımı yalnız e-posta görünenleri kopyaladığı
için o beş değer hiçbir zaman yeni sütuna geçmedi ve daraltma adımı onları taşıyan tek yeri
sildi. Geri alma komutu **başarıyla çalıştı**; sonuç yine de eksik.

Buradan iki kural çıkar. Birincisi, daraltma adımı geri alma penceresinin dışında durur:
ondan sonrası için geri alma yolu yayın düzeni değil, yedekten dönmedir. İkincisi,
genişletme adımının geri doldurması **kaçırdığı satırları saymalıdır**; bu ölçümde o sayı
beştir ve daraltmadan önce bilinebilecek tek uyarı odur.

#### Sıranın Durduğu Yer

Göç adımları veri katmanında, yayın adımları dağıtım düzeninde durur; ikisini birbirine
bağlayan bir ayar yoktur. Sırayı tutan tek şey yazılı bir adım listesidir ve o liste hiçbir
yerde doğrulanmaz. Çift yazma ayarı ise uygulama kodundadır ve her düğümde bulunur: bir
düğümde eski, diğerinde yeni sürüm koşarken çift yazmanın açık olup olmadığı düğüm başına
değişebilir.

Yanlış ayarın sessizliği burada iki kademelidir. Yanlış **sıra** gürültülüdür: yirmi istek
500 alır, ilk dakikada görünür. Yanlış **çift yazma** sessizdir: istekler 200 döner, kayıtlar
oluşur, yalnız bir alan boş kalır. Daraltmadan sonra fark edilen bu boşluk artık
düzeltilemez, çünkü değerin durduğu sütun silinmiştir. Ölçülmesi gereken sayı bu yüzden
hata sayısı değil, **geri doldurmanın kaçırdığı satır sayısıdır**.

#### Özet

- Aynı şema değişikliği göç önce ve kod önce sıralarında yirmişer hata alan istek üretti;
  genişlet–daralt sırasında yüz yirmi isteğin hepsi tamamlandı.
- Genişlet–daralt iki adım yerine beş adım ister ve adımlar arasındaki beklemelerle birlikte
  değişikliğin takvimini birkaç yayına yayar.
- İki sürümün aynı şemayla koşabildiği pencerenin koşulu çift yazmadır: çift yazan sürümün
  yazdığı kaydı eski sürüm okuyabildi, yalnız yeni sütunu yazanınkinde alan boş göründü ve
  hiçbir istek hata almadı.
- Çift yazma daraltmadan önce kaldırılır; daralttan sonra eski yazma yolu sütunu bulamaz.
- Sütun silme geri alınamaz: sütun yeniden eklenip yeni alandan dolduruldu, on dört üyenin
  dokuzu kurtarıldı, beşi kurtarılamadı — geri alma komutu hatasız çalıştığı hâlde.

#### Sonraki Adım

Bu konu boyunca ölçülen her karar, kaynağın var olduğunu varsaydı: ters vekilin arkasında
duracak düğümler, yayın penceresinde ikiye katlanan süreçler, oturumun taşındığı depo ve
göç adımlarının koştuğu veritabanı. Hiçbir ölçüm bu kaynağın ne kadar olması gerektiğini
sormadı. Kurs bu soruyla kapanır: bir düğüm hangi yükü taşır, kaç düğüm gerekir, yayın ve
arıza payı bu sayının üstüne ne ekler ve tahmin ile gerçekleşen arasındaki fark nasıl
ölçülür.

### Kapasite Planlama

Source: https://academia.sh/tr/kurslar/backend-uretim/kapasite-planlama

Ölçülen yükten süreç ve düğüm sayısına giden hesap: tek sürecin havuz boyuna göre ölçülen kapasitesi, buradan çıkan süreç, bağlantı ve düğüm sayısı, depo bağlantı sınırının havuz boyunu geriye doğru belirlemesi ve az ile çok tahminin hedef yükte hiç görünmeyip yığılmada ürettiği fark.

Bu konu boyunca sayılan her şey — düşen istek, kesinti saniyesi, geri alma adımı — kaynağın
verili olduğu bir düzende ölçüldü; düğümler, yayında ikiye katlanan süreçler ve göçün koştuğu
depo hep vardı. Bu ders o düzeni kuran sayıyı üretir: bir süreç hangi yükü
taşır, kaç süreç gerekir, yayın ve arıza payı ne ekler, kaç düğüm eder.

Yük testinin yöntemi **Başarım Karşıt Kalıpları ve İzleme**, kapasite sınırının sınamayla
bulunması **İşlevsel Olmayan Test** kursunda kuruldu; burada o ölçüm girdidir. Kursun kuralı
sürer: yanlış tahmin hata vermez, ölçü farkı gösterir.

- **DP19.** Hedef yük saniyede 150 istektir; yığılmada dört katı gelir.
- **DP20.** Depo eş zamanlı 16 bağlantı kabul eder, üstündeki yuva okunmadan kapatılır.
- **DP21.** Bir düğümde uygulamaya 256 MiB ayrılır.

#### Ölçülen Düzenek

Depo bağlantı sınırını gerçekten uygular.

```js
// depo.mjs — odunc kaydini veren depo; es zamanli acik baglanti sayisi SINIR ile kisitlidir
import { createServer } from 'node:http';

const { SINIR = 16, GECIKME = 12, PORT = 8830 } = process.env;   // plan.mjs bunlari gecer
let acik = 0, enYuksek = 0;

const depo = createServer((istek, yanit) => {
  if (istek.url === '/olcum') return yanit.end(JSON.stringify({ enYuksek }));
  setTimeout(() => yanit.end('{"kayit":1}'), Number(GECIKME));
});
depo.on('connection', (yuva) => {          // sinirin ustundeki yuva okunmadan kapatilir
  if (acik >= Number(SINIR)) return yuva.destroy();
  acik += 1;
  enYuksek = Math.max(enYuksek, acik);
  yuva.on('close', () => { acik -= 1; });
});
depo.listen(Number(PORT), () => console.log('depo hazir'));
```

Ödünç ucunda havuz hem depo yuvasını hem çalışma tamponunu tutar; istek başına iş sabittir.

```js
// servis.mjs — odunc ucunun bir kopyasi; HAVUZ yuva ve HAVUZ tampon bastan ayrilir
import { createServer, Agent, get } from 'node:http';
import { createHash } from 'node:crypto';

const { HAVUZ = 2, KUYRUK = 32, TUR = 40000, TAMPON = 4194304, DEPO = 8830,
  PORT = 8831 } = process.env;                    // plan.mjs bunlari gecer
const havuz = Number(HAVUZ), tur = Number(TUR);
const baglantilar = new Agent({ keepAlive: true, maxSockets: havuz });
const tamponlar = Array.from({ length: havuz }, () => Buffer.allocUnsafe(Number(TAMPON)).fill(0x2d));
const bos = tamponlar.map((_, i) => i);
const bekleyen = [];
let enUzunKuyruk = 0;

function al() {                     // bos is yeri yoksa kuyruga gir, kuyruk da doluysa -1
  if (bos.length) return Promise.resolve(bos.pop());
  if (bekleyen.length >= Number(KUYRUK)) return Promise.resolve(-1);
  enUzunKuyruk = Math.max(enUzunKuyruk, bekleyen.length + 1);
  return new Promise((c) => bekleyen.push(c));
}
const birak = (y) => { const s = bekleyen.shift(); if (s) s(y); else bos.push(y); };
const depoSorgu = () => new Promise((c, r) => get({ port: Number(DEPO), path: '/kayit',
  agent: baglantilar }, (y) => { y.resume(); y.on('end', c); }).on('error', r));

function isle(no, yer) {            // istek basina sabit is: tur kadar ozet turu
  let ozet = createHash('sha256').update(String(no)).digest();
  for (let i = 0; i < tur; i += 1) ozet = createHash('sha256').update(ozet).digest();
  tamponlar[yer].write(ozet.toString('hex'));
  return tamponlar[yer].subarray(0, 8).toString('hex');
}

createServer(async (istek, yanit) => {
  if (istek.url === '/olcum') {
    return yanit.end(JSON.stringify({ enUzunKuyruk, rss: process.memoryUsage().rss }));
  }
  const yer = await al();
  if (yer < 0) return yanit.writeHead(503).end('{"durum":"kuyruk dolu"}');
  try {
    await depoSorgu();
    yanit.end(JSON.stringify({ damga: isle(istek.url, yer) }));
  } catch (e) {
    yanit.writeHead(502).end(JSON.stringify({ hata: e.code ?? e.message }));
  } finally { birak(yer); }
}).listen(Number(PORT), () => console.log('servis hazir'));
```

#### Ölçümden Tahmine

Sürücü tek süreci havuz boyunu değiştirerek doyurur, kapasiteden süreç ve düğüm sayısını
hesaplar, üç tahmini aynı yük ve yığılma altında koşturur. 40.000 özet turu, 4 MiB tampon ve
depo sınırı koşumdan bağımsızdır; hız ve bellek bu koşumundur.

```js
// plan.mjs — tek surecin kapasitesi havuz basina olculur, surec ve dugum sayisi hesaplanir,
// uc tahmin ayni hedef yuk ve ayni yigilma altinda kosturulur
import { spawn } from 'node:child_process';

const HEDEF = 150, YIGILMA = 4;             // DP: saniyede gelen istek ve yigilma kati
const SURE = 1800, YSURE = 1000;            // olcum ve yigilma sureleri (ms)
const TUR = 40000, TAMPON = 4 * 1024 * 1024, KUYRUK = 32;
const DEPO_SINIR = 16, DEPO_GECIKME = 12;   // DP: deponun kabul ettigi es zamanli baglanti
const DUGUM_BELLEGI = 256, PAY = 2;         // DP: dugum bellegi (MiB); yayin + ariza payi

let taban = 8830;
const cocuklar = [];
const baslat = (dosya, env) => new Promise((k, r) => {
  const c = spawn(process.execPath, [dosya], { stdio: ['ignore', 'pipe', 'inherit'], env: { ...process.env, ...env } });
  cocuklar.push(c);
  c.stdout.once('data', k);
  c.once('exit', (kod) => r(new Error(`${dosya} ${kod} ile cikti`)));
});
const kapat = () => { cocuklar.forEach((c) => c.kill('SIGKILL')); cocuklar.length = 0; };

async function kur(surec, havuz) {
  const depo = taban;
  taban += 20;
  await baslat('depo.mjs', { PORT: depo, SINIR: DEPO_SINIR, GECIKME: DEPO_GECIKME });
  const portlar = [];
  for (let i = 1; i <= surec; i += 1) {
    await baslat('servis.mjs', { PORT: depo + i, DEPO: depo, HAVUZ: havuz, KUYRUK, TUR, TAMPON });
    portlar.push(depo + i);
  }
  return { depo, portlar };
}
const olcum = (p) => fetch(`http://127.0.0.1:${p}/olcum`).then((y) => y.json());
const iste = (p, i) => fetch(`http://127.0.0.1:${p}/odunc/${i}`)
  .then((y) => { y.body.cancel().catch(() => {}); return y.status; }).catch(() => 0);
function tablo(bas, satirlar) {
  const g = bas.map((b, i) => Math.max(b.length, ...satirlar.map((s) => String(s[i]).length)));
  for (const r of [bas, ...satirlar]) console.log(r.map((v, i) => String(v).padEnd(g[i])).join('  ').trimEnd());
}

// acik dongu: sabit hizda gonderilir, yanit kodlari sayilir
async function dene(surec, havuz, hiz, sure) {
  const { depo, portlar } = await kur(surec, havuz);
  const say = { tamam: 0, kuyruk: 0, kopan: 0 }, ucusta = [];
  let borc = 0, n = 0;
  for (let t = 0; t < sure; t += 20) {
    for (borc += (hiz * 20) / 1000; borc >= 1; borc -= 1) {
      n += 1;
      ucusta.push(iste(portlar[n % portlar.length], n)
        .then((k) => { say[k === 200 ? 'tamam' : k === 503 ? 'kuyruk' : 'kopan'] += 1; }));
    }
    await new Promise((k) => setTimeout(k, 20));
  }
  await Promise.all(ucusta);
  const ic = await Promise.all(portlar.map((x) => olcum(x)));
  cocuklar.slice(1).forEach((c) => c.kill('SIGKILL'));   // depo olcumu bir baglanti ister
  await new Promise((k) => setTimeout(k, 150));
  const d = await olcum(depo);
  kapat();
  return [say.tamam, say.kuyruk, say.kopan, Math.max(...ic.map((x) => x.enUzunKuyruk)),
    d.enYuksek, (ic.reduce((t, x) => t + x.rss, 0) / 2 ** 20).toFixed(0)];
}

// 1. asama: kapali dongu; istemci 16 istek ucusta tutar, sinirlayan sey havuzdur
const plan = [];
for (const h of [1, 2, 4, 8]) {
  const { depo, portlar } = await kur(1, h);
  const bit = Date.now() + SURE;
  let n = 0;
  await Promise.all(Array.from({ length: 16 }, async () => {
    while (Date.now() < bit) if (await iste(portlar[0], n) === 200) n += 1;
  }));
  const { rss } = await olcum(portlar[0]);
  const { enYuksek } = await olcum(depo);
  kapat();
  const hiz = n / (SURE / 1000), yuk = Math.ceil(HEDEF / hiz);
  plan.push({ h, hiz, mib: rss / 2 ** 20, yuva: enYuksek, yuk, surec: yuk + PAY, baglanti: (yuk + PAY) * h });
}
tablo(['havuz', 'tamamlanan/sn', 'depo yuvasi', 'rss (MiB)', 'yuku karsilayan surec', '+pay',
  'toplam baglanti', 'depo siniri'],
  plan.map((p) => [p.h, p.hiz.toFixed(1), p.yuva, p.mib.toFixed(0), p.yuk, p.surec, p.baglanti,
    p.baglanti <= DEPO_SINIR ? 'gecer' : 'asilir']));
const sec = plan.filter((p) => p.baglanti <= DEPO_SINIR).sort((a, b) => a.surec - b.surec)[0];
const dBasina = Math.floor(DUGUM_BELLEGI / sec.mib);
console.log(`\nsecilen plan: havuz ${sec.h}, ${sec.surec} surec (${sec.yuk} yuk + ${PAY} pay), `
  + `${sec.baglanti} baglanti, ${Math.ceil(sec.surec / dBasina)} dugum (dugume ${dBasina} surec)`);

// 2. asama: uc tahmin, once hedef yuk sonra yigilma
const ayarlar = [['az tahmin', sec.yuk - 1, sec.h], ['secilen plan', sec.surec, sec.h],
  ['cok tahmin, havuz dokunulmamis', sec.surec + 2, 4]];
for (const [ad, hiz, sure] of [['hedef yuk', HEDEF, SURE], ['yigilma', HEDEF * YIGILMA, YSURE]]) {
  const satirlar = [];
  for (const [etiket, surec, havuz] of ayarlar) {
    satirlar.push([etiket, surec, havuz, surec * havuz, ...await dene(surec, havuz, hiz, sure)]);
  }
  console.log(`\n${ad} — ${hiz} istek/sn`);
  tablo(['ayar', 'surec', 'havuz', 'havuz carpimi', '200', '503', '502', 'en uzun kuyruk',
    'depo yuvasi', 'rss'], satirlar);
}
```

```
havuz  tamamlanan/sn  depo yuvasi  rss (MiB)  yuku karsilayan surec  +pay  toplam baglanti  depo siniri
1      41.7           2            83         4                      6     6                gecer
2      69.4           3            90         3                      5     10               gecer
4      68.9           5            98         3                      5     20               asilir
8      70.6           9            112        3                      5     40               asilir

secilen plan: havuz 2, 5 surec (3 yuk + 2 pay), 10 baglanti, 3 dugum (dugume 2 surec)

hedef yuk — 150 istek/sn
ayar                            surec  havuz  havuz carpimi  200  503  502  en uzun kuyruk  depo yuvasi  rss
az tahmin                       2      2      4              270  0    0    23              4            181
secilen plan                    5      2      10             270  0    0    0               6            449
cok tahmin, havuz dokunulmamis  7      4      28             270  0    0    0               7            642

yigilma — 600 istek/sn
ayar                            surec  havuz  havuz carpimi  200  503  502  en uzun kuyruk  depo yuvasi  rss
az tahmin                       2      2      4              279  321  0    32              4            183
secilen plan                    5      2      10             460  140  0    32              10           520
cok tahmin, havuz dokunulmamis  7      4      28             351  0    249  17              16           683
```

#### Havuzun Üç Faturası

Havuz bir iken süreç saniyede 41,7 istek tamamladı; ikide 69,4'e sıçradı, dört ve sekizde aynı
bantta kaldı. Havuz bir iken depo beklemesi hiçbir işle örtüşemez, ikide bir istek beklerken
diğeri işlenir. İkinin üstünde örtüşecek bekleme kalmaz.

Sonraki iki sütun ayarın fiyatıdır: depoda açılan yuva 2, 3, 5 ve 9'a, süreç belleği 83'ten
112 MiB'a çıktı. Havuz bir üst sınır değil, doyumda gerçekleşen bağlantı sayısıdır.

#### Süreçten Düğüme

Havuz iki iken 150 isteği karşılamak için `tavan(150 / 69,4) = 3` süreç yeter; yayın ve arıza
payı eklenince beş olur. Beş süreç havuz iki ile depoya on bağlantı düşürür. Havuz dört
seçilseydi süreç sayısı yine beş olurdu ama bağlantı yirmiye, havuz sekizde kırka çıkardı.

Bu, hesabın yönünü tersine çevirir: **depo sınırı bölü süreç sayısı** havuza tavan koyar, tavan
kapasiteyi, kapasite de süreç sayısını belirler.

Düğüm sayısı bir bölmedir: 90 MiB'lik süreç 256 MiB'lik düğüme ikişer sığar, beş süreç üç düğüm
eder. Burada bir kayma var — **pay süreç cinsinden hesaplandı, arıza düğüm cinsinden gelir**:
bir düğüm gidince iki süreç birden gider.

#### İki Yönlü Yanlış Tahmin

Hedef yükte üç ayar da aynı görünüyor: 270 isteğin 270'i tamamlandı, üçünde de hata sıfır.
Yanlış tahminin sessizliği burasıdır.

Az tahminin izi tek sütunda: iki süreçle en uzun kuyruk 23'e çıktı, doğru planda sıfır kaldı.
İki süreç saniyede yaklaşık 139 istek taşır, hedef 150'dir; biriken istek kuyruğu birkaç
saniyede doldurur, ölçüm penceresi bundan kısadır. Yığılmada aynı ayar 321 istek kaybetti. Kuyruk derinliği, düşen istekten önce gelen ve tek başına hiçbir
hata üretmeyen göstergedir.

Çok tahminin hedef yükteki tek izi bellektedir: yedi süreç 642 MiB, beş süreç 449 MiB tuttu.
Yığılmada yedi süreç depoya yirmi sekiz bağlantı istedi; depo yuvası on altıda doydu ve 249
istek 502 aldı. Tamamlanan 351 istek, doğru planın 460'ından **azdır**. Doğru plan taşıyamadığı
140 isteği kendi kuyruğunda 503 ile reddetti; çok tahmin paylaşılan kaynağı harcadı.

Üç girdi üç ayrı katmanda durur: süreç sayısı süreç yöneticisinde, havuz uygulamanın ortamında,
bağlantı sınırı depoda. Ölçülen şey ilk ikisinin çarpımıdır ve hiçbir dosyada yazmaz.

#### Özet

- Havuz boyu kapasiteyi (41,7'den 69,4 isteğe), süreç belleğini (83'ten 112 MiB'a) ve depoya
  düşen yuva sayısını (2'den 9'a) birden belirler; ikinin üstünde yalnız fiyat arttı.
- Ölçümden plana giden hesap üç adımdır — yükü karşılayan süreç, yayın ve arıza payı, bellekle
  bölünen düğüm: havuz 2, 5 süreç, 10 bağlantı, 3 düğüm.
- Depo bağlantı sınırı havuza tavan koyar: aynı süreç sayısıyla havuz 4 yirmi, havuz 8 kırk
  bağlantı ederdi.
- Pay süreç cinsinden hesaplanırken arıza düğüm cinsinden gelir; düğüme iki süreç sığdığı için
  bir düğümün kaybı, tek süreçlik arıza payının iki katıdır.
- Hedef yükte üç ayar da 270 isteğin 270'ini karşıladı; az tahminin izi 23'e çıkan kuyruk, çok
  tahminin izi boşta duran 193 MiB oldu. Yığılmada az tahmin 321 isteği reddetti, çok tahmin
  depo yuvası dolduğu için 249 istek kopardı.

#### Kurs Kapanışı

| Ders | Doğru ayarın ölçüsü | Yanlış ayarın sessiz sonucu | Ayarın yeri |
|---|---|---|---|
| Girdi Doğrulama | İzin listesinde 0 ve 1, engellemede 11 ve 4 yanlış | Üç yanlış ayarda hata 0 | Uçta 0, depoda 22 boşa adım |
| Enjeksiyon Sınıfı | Ayrı işlenen yolunda bozulma 0 | Kaçan birleştirme 10 girdinin 4'ünde sapıyor | Karar 12 çağrı yerinde ayrı |
| Kaynaklar Arası Erişim | Köken listesi 2 dış kökene açık | Joker ayar 200 döndürdü, istemci attı | 5 uçta ayrı, tek katmanda 1 |
| Güvenlik Başlıkları | Tek katmanda 1 kayıt noktası, yeni uç için 0 satır | Tek yanlış değer 10/10'u 0/10'a düşürdü | Uç başına 10 dosya, tek katmanda 1 |
| Gizli Değer Yönetimi | Pencere 390 saniye; penceresiz döndürme 36 ret | Sızan anahtar 900. saniyede 200 aldı | Anahtar 14 yerde yılda 56 adım |
| Bağımlılık Riski | Günlük yükseltme 113 açık gün, 237 müdahale günü | Gevşek aralıkta iki dağıtım 14 pakette ayrıştı | Sürüm aralığı bildirimde |
| Denetim Kaydı | Bütün karar noktaları 8 sorunun 8'ini yanıtladı | Son 25 kaydın silinmesi yakalanmadı | Çapa dönemi: çapasız 3.012, 500'de birle 499 |
| Kötüye Kullanım | Katmanlı anahtar 2.446 istek durdurdu, yanlış kalma 0 | Normalize edilmeyince durdurulan 2.266'ya indi | Anahtar seçimi sınırlayıcıda |
| Ters Vekil | Aktarım açıkken 16 isteğin 11'i geçti, kayıtta 4 kimlik | Kapalıyken 6 iyi istek durduruldu | Aktarım vekilde, gövde sınırı iki katmanda |
| Süreç Yöneticileri | Anında başlatma 24 isteğin 20'sini yanıtladı; 500 ms bekleme 6 isteği boşalttı | Başlamayan süreçte onlarca yeniden başlatma (o koşumda 43) | Politika ve bekleme süreç yöneticisinde |
| Yatay/Dikey Ölçekleme | Havuz ikiye inince 60 isteğin 60'ı yanıtlandı | Havuza dokunulmayınca bağlantı 32'ye çıktı, 30 istek yanıtsız | Süreç sayısı ve havuz ayrı |
| Durumsuz Uygulama | Tek kopyada 21 isteğin 21'i doğru | İki kopyada 12 istek yanlışlandı, kod değişmedi | 55 satırda 12 çağrı yeri |
| Oturum ve Yapışkanlık | Paylaşılan depoda kaybolan oturum 0 | Süreç belleğinde 8 ekleme biriktirmedi, hepsi 200 döndü | İmza anahtarı düğüm başına |
| Sıfır Kesintili Yayın | Mavi–yeşil ve aşamalı yayın 0 istek düşürdü | Sert kapanmada uçuştaki 8 istek düştü, sayaçta görünmedi | Yayın düzeni dağıtımda |
| Göç ile Yayın Sırası | Genişlet–daralt 120 isteğin 120'sini 5 adımda karşıladı | Çift yazma atlanınca alan boş kaldı, istek 200 döndü | Sıra yazılı listede |
| Kapasite Planlama | Havuz 2 ile 5 süreç, 10 bağlantı, 3 düğüm | Az tahminde kuyruk 23'e çıktı, 193 MiB boşta durdu | Süreç yöneticisi, uygulama ve depo |

Orta sütun kursun kuralını on altı kez yazıyor: **yanlış yapılandırma hata vermez; ölçü farkı
gösterir.** Eksik kalan başlık, kapatılmayan anahtar, atlanan çift yazma, fazla tutulan süreç —
hiçbiri istisna fırlatmadı. Doğruyu yanlıştan ayıran şey her derste sayılan bir fark oldu: geçen
istek, açılan köken, kurtarılamayan kayıt, boşta duran bellek. Her ayarın yanına ölçüsü de yazılır.

Bu ders **Sunucu Güvenliği ve Üretime Alma** kursunu ve müfredatı kapatıyor. Sunucu Tarafı
Temelleri isteğin sunucudaki yolunu, Web API Tasarımı sözleşmeyi, Kimlik Doğrulama ve Yetkilendirme erişimi, Veri
Erişim Katmanı kalıcılığı, Önbellekleme ve Eşzamansız İşleme isteğin dışına çıkan işi, Servis
Mimarileri sınırı, Gözlemlenebilirlik ve Güvenilirlik görünürlüğü kurdu; bu kurs da üretime alma
kararlarını. Sırada okurun kendi sistemi var: hangi ucun doğrulayıcısı eksik, kaç süreç koşuyor
ve o sayı hangi ölçümden çıktı.

