---
title: 'İlişkisel Olmayan Veri Modelleri'
source: 'https://academia.sh/tr/kurslar/nosql'
language: tr
updated: '2026-08-17T04:40:24+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'Belge, anahtar–değer, geniş sütun ve çizge modellerini kullanım alanlarıyla eşler'
    - 'Belge veritabanında gömme ve referans kararını gerekçelendirir'
    - 'Tutarlılık modeli seçiminin uygulama üzerindeki etkisini açıklar'
---

# İlişkisel Olmayan Veri Modelleri

İlişkisel modelin karşılayamadığı gereksinimler için kurulan aileler: anahtar–değer, belge, geniş sütun, çizge ve zaman serisi depolarının erişim örüntüsüyle eşlenmesi, belge modelinde gömme ile referans kararının sonuçlarının sayılması, sorgu işleçleri, toplama boru hattı, dizinleme ve şema doğrulama, çok belgeli işlemlerin kapsamı ve dağıtık davranışın kaçınılmaz soruları — çoğaltma kümeleri, parça anahtarı, okuma ile yazma ilgileri, tutarlılık modelleri ve güvenlik yapılandırması.

## Model Aileleri

Beş depo ailesinin erişim örüntüsüyle eşlenmesi: tek anahtarla erişilen anahtar–değer depoları, iç içe yapıyı tek okumada veren belge veritabanları, sütun ailesi modeliyle yazma başarımı sunan geniş sütun depoları, ilişkinin kendisini sorgu konusu yapan çizge veritabanları, zaman damgalı ölçüm verisine göre yerleşim kuran zaman serisi depoları ve erişim örüntüsünden modele giden seçim ölçütleri.

### Anahtar–Değer Depoları

Source: https://academia.sh/tr/kurslar/nosql/anahtar-deger-depolari

En basit erişim modelinin sözleşmesi ve bedeli: değerin depo için saydam olmaması, aynı üye sayfasının ilişkisel şemada ve anahtar–değer modelinde gidiş ile dokunulan kayıt cinsinden ölçülmesi, alan üzerinde sorgulama yeteneğinin kaybı ve uygulamada tutulan ikincil dizinin bütün halinde okunması, anahtar tasarımının yazma çarpanı ile okuma gidiş sayısı arasında kurduğu ödünleşim.

İlişkisel Veritabanı Yönetimi kursu, model üzerine tek bir soru sormadan kapandı: tablolar,
satırlar, şema ve işlemler verili kabul edildi, ölçülen her şey o modelin içinde ölçüldü. Bu
kurs verili kabul edilen şeyi konu yapıyor ve en yalın olanından başlıyor. Anahtar–değer
deposu (key–value store) tek bir sözleşme tanır: bir anahtar verirsin, bir değer alırsın.
Deponun değer hakkında bildiği hiçbir şey yoktur.

Ailelerin adı ve dört türün erişim örüntülerine göre karşılaştırması Veri Katmanı Ölçekleme
kursunda kurulmuştu; orada dönem taraması anahtar–değer deposunda 3001 istek isterken belge
ve geniş sütun deposunda tek istekte bitiyordu, üç adımlık bir dolaşma ise çizge deposunda
derinlikten bağımsız olarak tek istekte kalıyordu. O ölçüm burada tekrarlanmaz, girdi sayılır.
Bu dersin sorusu daha dardır ve **veri modelinin kendisine** bakar: değerin içine bakamayan bir
depo, kütüphane katalog ve ödünç verisini nasıl taşır, hangi işi ucuzlatır ve hangi yeteneği
kaybeder.

#### Modelin Sözleşmesi

Anahtar–değer modelinde bir kayıt iki parçadır: eşsiz bir anahtar ve depo için anlamı olmayan
bir bayt dizisi. Depo anahtarı bilir, değeri bilmez. Bu tek cümleden üç sonuç çıkar. Değer
üzerinde koşul yazılamaz ve değerin bir parçası güncellenemez, çünkü depo alan diye bir kavram
tanımaz. İki kaydın birleştirilmesi diye bir işlem de yoktur, çünkü birleştirme alanların
eşleşmesiyle tanımlıdır.

Karşılığında elde edilen şey erişimin sabit adımda kalmasıdır: anahtar bilindiğinde kayıt tek
gidişte gelir ve bu, kayıt sayısından bağımsızdır.

Ölçüm için kütüphane verisi iki modelde birden kurulur; ilişkisel taraf `node:sqlite` ile
gerçekten kurulur. **NS1:** katalog 20.000 üye taşır ve her üyenin 1 ile 11 arası ödüncü
vardır; toplam ödünç 119.993. **NS2:** değerler JSON ile seri hale getirilir; okunan ve yazılan
baytlar bu gösterimden sayılır, ilişkisel tarafta sonuç kümesi aynı gösterime çevrilir.

```js
// kv/model.mjs — ayni uye sayfasinin iliskisel semada ve anahtar-deger modelinde
// olculmesi. Iliskisel taraf node:sqlite ile kurulur; sayilar kosumdan bagimsizdir.
import { DatabaseSync } from "node:sqlite";

const UYE = 20_000, SEHIR = ["Ankara", "Istanbul", "Izmir", "Bursa", "Konya"];
function* uyeler() {                       // uye i'nin 1 + (i % 11) oduncu vardir
  for (let i = 1; i <= UYE; i += 1) {
    const odunc = Array.from({ length: 1 + (i % 11) }, (_, j) => ({
      odunc_id: i * 100 + j, kitap_id: 1 + ((i * 7 + j * 13) % 200_000),
      alis_tarihi: `2024-${String(1 + ((i + j) % 12)).padStart(2, "0")}-15`,
      ceza_kurus: (i * 37 + j * 11) % 900,
    }));
    yield { uye_id: i, ad: `Uye ${i}`, sehir: SEHIR[i % 5], odunc,
      ceza_kurus: odunc.reduce((t, o) => t + o.ceza_kurus, 0) };
  }
}

class AnahtarDegerDeposu {                 // deger deponun gozunde bir bayt dizisidir
  #tablo = new Map();
  gidis = 0; kayit = 0; bayt = 0;
  yaz(anahtar, deger) { this.gidis += 1; this.kayit += 1;
    const s = JSON.stringify(deger); this.bayt += Buffer.byteLength(s);
    this.#tablo.set(anahtar, s); }
  oku(anahtar) { this.gidis += 1; const s = this.#tablo.get(anahtar);
    if (s === undefined) return undefined;
    this.kayit += 1; this.bayt += Buffer.byteLength(s); return JSON.parse(s); }
  sayaclari_sifirla() { this.gidis = 0; this.kayit = 0; this.bayt = 0; }
}

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL,
           sehir TEXT NOT NULL, ceza_kurus INTEGER NOT NULL);
         CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, uye_id INTEGER NOT NULL,
           kitap_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, ceza_kurus INTEGER NOT NULL);`);
const ekleU = db.prepare("INSERT INTO uye VALUES (?,?,?,?)");
const ekleO = db.prepare("INSERT INTO odunc VALUES (?,?,?,?,?)");
const depo = new AnahtarDegerDeposu();
db.exec("BEGIN");
for (const u of uyeler()) {
  ekleU.run(u.uye_id, u.ad, u.sehir, u.ceza_kurus);
  for (const o of u.odunc) ekleO.run(o.odunc_id, u.uye_id, o.kitap_id, o.alis_tarihi, o.ceza_kurus);
  depo.yaz(`uye:${u.uye_id}`, u);          // ayni bilgi tek deger olarak
}
db.exec("COMMIT");
db.exec("CREATE INDEX odunc_uye ON odunc(uye_id); CREATE INDEX uye_ceza ON uye(ceza_kurus);");

const bayt = (x) => Buffer.byteLength(JSON.stringify(x));
const HEDEF = 4242;
console.log(`model: ${UYE} uye, ${db.prepare("SELECT count(*) n FROM odunc").get().n} odunc; ` +
  `hedef uye ${HEDEF}, odunc sayisi ${1 + (HEDEF % 11)}`);
console.log(db.prepare("EXPLAIN QUERY PLAN SELECT * FROM odunc WHERE uye_id = ?").all()[0].detail);

const satir = [];
{ const u = db.prepare("SELECT uye_id, ad, sehir, ceza_kurus FROM uye WHERE uye_id = ?").get(HEDEF);
  const o = db.prepare("SELECT odunc_id, kitap_id, alis_tarihi, ceza_kurus FROM odunc WHERE uye_id = ?").all(HEDEF);
  satir.push(["iliskisel, iki sorgu", 2, 1 + o.length, bayt(u) + bayt(o)]); }
{ const r = db.prepare(`SELECT u.ad, u.sehir, u.ceza_kurus, o.odunc_id, o.kitap_id,
    o.alis_tarihi FROM uye u JOIN odunc o ON o.uye_id = u.uye_id WHERE u.uye_id = ?`).all(HEDEF);
  satir.push(["iliskisel, tek birlestirme", 1, r.length, bayt(r)]); }
{ depo.sayaclari_sifirla(); const v = depo.oku(`uye:${HEDEF}`);
  satir.push([`anahtar-deger, tek anahtar (${v.odunc.length} odunc)`, depo.gidis, depo.kayit, depo.bayt]); }

console.log(`\n${"yol".padEnd(40)}${"gidis".padStart(7)}${"kayit".padStart(7)}${"bayt".padStart(7)}`);
for (const [a, g, k, b] of satir)
  console.log(a.padEnd(40) + String(g).padStart(7) + String(k).padStart(7) + String(b).padStart(7));
```

```
model: 20000 uye, 119993 odunc; hedef uye 4242, odunc sayisi 8
SEARCH odunc USING INDEX odunc_uye (uye_id=?)

yol                                       gidis  kayit   bayt
iliskisel, iki sorgu                          2      9    714
iliskisel, tek birlestirme                    1      8    913
anahtar-deger, tek anahtar (8 odunc)          1      1    723
```

Sayılar **ölçüm** sınıfındadır: gerçekten koşan iki motordan gelir, belirlenimlidir.

Üye sayfası anahtar–değer modelinde **tek gidiş, tek kayıt**tır. İlişkisel şemada aynı sayfa ya
iki sorgu ve dokuz kayıt ister, ya da tek birleştirmeye indirilir; birleştirme gidişi bire
düşürür ama üye alanlarını her ödünç satırında tekrarladığı için taşınan baytı 714'ten 913'e
çıkarır. Anahtar–değer değeri 723 bayttır: aynı bilgi, tekrarsız, tek parça.

Buradaki kazanç birleştirmenin yokluğundan değil, **modellemenin okuma birimine göre
yapılmasından** gelir. Değer, uygulamanın bir seferde istediği şeyin tamamıdır. Aynı şey
ilişkisel şemada da yapılabilir — Veri Modelleme ve İlişkisel Kuram kursunda ölçülen
denormalizasyondur — ama orada bir karardır; burada modelin tek biçimi budur.

#### Sorgulanabilirliğin Kaybı

Kütüphanenin ikinci sorusu şudur: cezası bir eşiği aşan üyeler kimlerdir. İlişkisel şemada bu,
üye tablosundaki bir alan üzerinde dizin gerektirir; anahtar–değer deposunda böyle bir alan
yoktur.

```js
// kv/sorgu.mjs — "cezasi esikten buyuk uyeler" sorusunun ayni veri uzerinde uc yolu.
// Uretec kv/model.mjs ile birebir aynidir; blok kendi basina calisir.
import { DatabaseSync } from "node:sqlite";

const UYE = 20_000, SEHIR = ["Ankara", "Istanbul", "Izmir", "Bursa", "Konya"], ESIK = 8000;
function* uyeler() {
  for (let i = 1; i <= UYE; i += 1) {
    const odunc = Array.from({ length: 1 + (i % 11) }, (_, j) => ({
      odunc_id: i * 100 + j, kitap_id: 1 + ((i * 7 + j * 13) % 200_000),
      alis_tarihi: `2024-${String(1 + ((i + j) % 12)).padStart(2, "0")}-15`,
      ceza_kurus: (i * 37 + j * 11) % 900,
    }));
    yield { uye_id: i, ad: `Uye ${i}`, sehir: SEHIR[i % 5], odunc,
      ceza_kurus: odunc.reduce((t, o) => t + o.ceza_kurus, 0) };
  }
}
class AnahtarDegerDeposu {
  #tablo = new Map();
  gidis = 0; kayit = 0; bayt = 0;
  yaz(a, d) { const s = JSON.stringify(d); this.#tablo.set(a, s); }
  oku(a) { this.gidis += 1; const s = this.#tablo.get(a);
    if (s === undefined) return undefined;
    this.kayit += 1; this.bayt += Buffer.byteLength(s); return JSON.parse(s); }
  anahtarlar() { return [...this.#tablo.keys()].filter((a) => a.startsWith("uye:")); }
  sifirla() { this.gidis = 0; this.kayit = 0; this.bayt = 0; }
}

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL,
           sehir TEXT NOT NULL, ceza_kurus INTEGER NOT NULL);`);
const ekle = db.prepare("INSERT INTO uye VALUES (?,?,?,?)");
const depo = new AnahtarDegerDeposu();
const dizin = [];                          // uygulamanin elle tuttugu ikincil dizin
db.exec("BEGIN");
for (const u of uyeler()) {
  ekle.run(u.uye_id, u.ad, u.sehir, u.ceza_kurus);
  depo.yaz(`uye:${u.uye_id}`, u);
  dizin.push([u.ceza_kurus, u.uye_id]);
}
db.exec("COMMIT");
db.exec("CREATE INDEX uye_ceza ON uye(ceza_kurus);");
dizin.sort((x, y) => x[0] - y[0]);
depo.yaz("dizin:ceza", dizin);             // dizin de bir degerdir: butun halinde okunur

const bayt = (x) => Buffer.byteLength(JSON.stringify(x));
const satir = [];
{ const r = db.prepare("SELECT uye_id FROM uye WHERE ceza_kurus > ?").all(ESIK);
  satir.push(["iliskisel, ceza dizini", 1, r.length, bayt(r), r.length]); }
{ depo.sifirla(); const bulunan = [];
  for (const a of depo.anahtarlar()) { const u = depo.oku(a); if (u.ceza_kurus > ESIK) bulunan.push(u.uye_id); }
  satir.push(["anahtar-deger, anahtar taramasi", depo.gidis, depo.kayit, depo.bayt, bulunan.length]); }
{ depo.sifirla(); const d = depo.oku("dizin:ceza");
  const bulunan = d.filter(([c]) => c > ESIK).map(([, id]) => depo.oku(`uye:${id}`).uye_id);
  satir.push(["anahtar-deger, uygulama dizini", depo.gidis, depo.kayit, depo.bayt, bulunan.length]); }

console.log(`model: ${UYE} uye, esik ${ESIK} kurus; ` +
  `iliskisel plan: ${db.prepare("EXPLAIN QUERY PLAN SELECT uye_id FROM uye WHERE ceza_kurus > ?").all()[0].detail}`);
console.log(`dizin:ceza degerinin boyutu ${bayt(dizin)} bayt, girdi sayisi ${dizin.length}`);
console.log(`\n${"yol".padEnd(32)}${"gidis".padStart(7)}${"kayit".padStart(7)}${"okunan bayt".padStart(13)}${"sonuc".padStart(7)}`);
for (const [a, g, k, b, n] of satir)
  console.log(a.padEnd(32) + String(g).padStart(7) + String(k).padStart(7) +
    String(b).padStart(13) + String(n).padStart(7));
```

```
model: 20000 uye, esik 8000 kurus; iliskisel plan: SEARCH uye USING COVERING INDEX uye_ceza (ceza_kurus>?)
dizin:ceza degerinin boyutu 243132 bayt, girdi sayisi 20000

yol                               gidis  kayit  okunan bayt  sonuc
iliskisel, ceza dizini                1    359         5903    359
anahtar-deger, anahtar taramasi   20000  20000     11311097    359
anahtar-deger, uygulama dizini      360    360       584880    359
```

Üç yol da aynı 359 üyeyi buluyor; ayrım işin nerede yapıldığındadır. İlişkisel motor kapsayan
bir dizin üzerinden **tek gidişte** yalnız eşleşen 359 kayda dokunuyor ve 5.903 bayt taşıyor.
Anahtar–değer deposunda süzme uygulamada olduğu için bütün anahtar uzayı geziliyor: 20.000
gidiş ve 11,3 MB — sonucun **iki bin katı** veri, sonucu bulabilmek için okunuyor.

Üçüncü satır asıl dersi taşır. Uygulama, ceza değerine göre sıralı bir liste tutup onu tek
anahtara yazdığında gidiş 20.000'den 360'a iniyor. Ama bu liste bir dizin değil bir
**değerdir**: her sorguda bütün halinde, 243.132 baytın tamamı okunur. İlişkisel dizin bir
ağaçtır ve motor onun içine inebilir; uygulama dizini yalnız okunup çözümlenebilir. Okunan bayt
bu yüzden 584.880'de kalıyor — ilişkisel yolun yüz katı. Üstelik iki anahtara yapılan iki yazma
tek bir işlem değildir; kayıt ile dizin arasında kalıcı bir tutarsızlık penceresi açılır.

#### Anahtar Tasarımı Tek Ayardır

İlişkisel şemada tasarımcının elinde tablo, sütun, kısıt ve dizin vardır. Anahtar–değer
modelinde tek bir ayar vardır: anahtarın ne olduğu ve değerin sınırının nereden geçtiği.

Bir ödüncün eklenmesi, bütünsel değer tasarımında **oku-değiştir-yaz (read-modify-write)**
demektir: bütün değer okunur, diziye bir kayıt eklenir, bütün değer geri yazılır. Ayrı anahtar
tasarımında yalnız yeni kayıt yazılır.

```js
// kv/anahtar.mjs — tek oduncun eklenmesi iki anahtar tasariminda, ve ayni tasarimlarin
// uye sayfasi okumasina bedeli. Butun bayt sayilari gercek seri hale getirmeden gelir.
class AnahtarDegerDeposu {
  #tablo = new Map(); #sirali = false;
  gidis = 0; kayit = 0; okunan = 0; yazilan = 0;
  constructor(sirali) { this.#sirali = sirali; }
  yaz(a, d) { const s = JSON.stringify(d); this.gidis += 1; this.kayit += 1;
    this.yazilan += Buffer.byteLength(a) + Buffer.byteLength(s); this.#tablo.set(a, s); }
  oku(a) { this.gidis += 1; const s = this.#tablo.get(a); if (s === undefined) return undefined;
    this.kayit += 1; this.okunan += Buffer.byteLength(s); return JSON.parse(s); }
  onek(p) {                                // yalniz sirali anahtar uzayinda vardir
    if (!this.#sirali) throw new Error("karma anahtar uzayinda onek taramasi yok");
    this.gidis += 1; const c = [];
    for (const [a, s] of [...this.#tablo].sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)))
      if (a.startsWith(p)) { this.kayit += 1; this.okunan += Buffer.byteLength(s); c.push(JSON.parse(s)); }
    return c; }
  sifirla() { this.gidis = 0; this.kayit = 0; this.okunan = 0; this.yazilan = 0; }
}
const odunc = (i, j) => ({ odunc_id: i * 1000 + j, kitap_id: 1 + ((i * 7 + j * 13) % 200_000),
  alis_tarihi: `2024-${String(1 + ((i + j) % 12)).padStart(2, "0")}-15`, ceza_kurus: (i * 37 + j * 11) % 900 });

console.log(`tek oduncun eklenmesi (yeni kaydin kendisi ${Buffer.byteLength(JSON.stringify(odunc(1, 0)))} bayt)`);
console.log(`${"odunc sayisi k".padStart(14)}${"butunsel deger: yazilan".padStart(25)}` +
  `${"carpan".padStart(8)}${"ayri anahtar: yazilan".padStart(23)}${"carpan".padStart(8)}`);
for (const k of [1, 8, 64, 512]) {
  const butun = new AnahtarDegerDeposu(true), ayri = new AnahtarDegerDeposu(true);
  const u = { uye_id: 7, ad: "Uye 7", sehir: "Izmir", odunc: Array.from({ length: k }, (_, j) => odunc(7, j)) };
  butun.yaz("uye:0007", u);
  for (let j = 0; j < k; j += 1) ayri.yaz(`uye:0007:odunc:${String(j).padStart(6, "0")}`, odunc(7, j));
  const yeni = odunc(7, k);
  butun.sifirla(); ayri.sifirla();
  const v = butun.oku("uye:0007"); v.odunc.push(yeni); butun.yaz("uye:0007", v);   // oku-degistir-yaz
  ayri.yaz(`uye:0007:odunc:${String(k).padStart(6, "0")}`, yeni);                  // yalniz yeni kayit
  console.log(String(k).padStart(14) + String(butun.yazilan).padStart(25) +
    (butun.yazilan / ayri.yazilan).toFixed(1).padStart(8) +
    String(ayri.yazilan).padStart(23) + "1.0".padStart(8));
}

const K = 64;
const kurulum = (sirali) => { const d = new AnahtarDegerDeposu(sirali);
  for (let j = 0; j < K; j += 1) d.yaz(`uye:0007:odunc:${String(j).padStart(6, "0")}`, odunc(7, j));
  d.yaz("uye:0007", { uye_id: 7, ad: "Uye 7", sehir: "Izmir" });
  d.yaz("uye:0007:odunc-listesi", Array.from({ length: K }, (_, j) => j));
  d.sifirla(); return d; };
console.log(`\nayni uyenin ${K} oduncluk sayfasinin okunmasi`);
console.log(`${"anahtar uzayi".padEnd(34)}${"gidis".padStart(7)}${"kayit".padStart(7)}${"okunan bayt".padStart(13)}`);
const s = kurulum(true); s.oku("uye:0007"); s.onek("uye:0007:odunc:");
console.log("sirali, onek taramasi".padEnd(34) + String(s.gidis).padStart(7) +
  String(s.kayit).padStart(7) + String(s.okunan).padStart(13));
const h = kurulum(false); h.oku("uye:0007");
for (const j of h.oku("uye:0007:odunc-listesi")) h.oku(`uye:0007:odunc:${String(j).padStart(6, "0")}`);
console.log("karma, uygulama anahtar listesi".padEnd(34) + String(h.gidis).padStart(7) +
  String(h.kayit).padStart(7) + String(h.okunan).padStart(13));
```

```
tek oduncun eklenmesi (yeni kaydin kendisi 73 bayt)
odunc sayisi k  butunsel deger: yazilan  carpan  ayri anahtar: yazilan  carpan
             1                      211     2.2                     96     1.0
             8                      748     7.7                     97     1.0
            64                     5053    52.6                     96     1.0
           512                    39936   407.5                     98     1.0

ayni uyenin 64 oduncluk sayfasinin okunmasi
anahtar uzayi                       gidis  kayit  okunan bayt
sirali, onek taramasi                   2     65         4895
karma, uygulama anahtar listesi        66     66         5078
```

Üst tablo **yazma çarpanını (write amplification)** gösteriyor: eklenen bilgi her satırda 73
baytken, bütünsel değer tasarımında yazılan bayt 211'den 39.936'ya çıkıyor. Çarpan üyenin ödünç
sayısıyla doğru orantılı büyür ve 512 ödünçte 407 kata varır. Ayrı anahtar tasarımında yazılan
bayt üye büyüdükçe değişmiyor: 96 ile 98 bayt arası, çarpan 1.

Alt tablo bunun bedelini gösteriyor. Ödünçler ayrı anahtarlara dağıldığında üye sayfası artık
tek gidişte gelmiyor. **Sıralı anahtar uzayında** — anahtarların sözlük sırasında tutulduğu
depolarda — bir önek taraması (prefix scan) tek istek sayılır ve 2 gidişte biter. **Karma
anahtar uzayında** önek diye bir kavram yoktur, çünkü ardışık anahtarlar ardışık yerlere
düşmez; uygulama ayrıca bir anahtar listesi tutmak ve her ödüncü tek tek çekmek zorunda kalır:
66 gidiş. Aynı 65 kayıt, aynı bayt mertebesi, otuz üç kat gidiş.

İki tablo birlikte okunduğunda modelin tek kuralı çıkar: **anahtar–değer deposunda değerin
sınırı, uygulamanın erişim biriminin sınırıdır.** Sınır geniş tutulursa okuma tek gidişe iner
ve yazma çarpanı büyür; dar tutulursa yazma ucuzlar ve okuma, anahtar uzayının sıralı olup
olmamasına bağımlı hâle gelir. Karar şemayla değil anahtar adlandırmasıyla verilir; sonradan
değiştirmek bütün veriyi yeniden yazmaktır.

#### Özet

- Anahtar–değer modeli tek bir sözleşme tanır: anahtar verilir, değer alınır. Depo değerin
  içini görmez; bu yüzden alan üzerinde koşul, kısmi güncelleme ve birleştirme yoktur.
- Üye sayfası anahtar–değer modelinde 1 gidiş ve 1 kayıt; ilişkisel şemada 2 gidiş ve 9 kayıt,
  ya da tek birleştirmeyle 1 gidiş ve 8 kayıt karşılığında 714 yerine 913 bayt.
- Alan üzerinde sorgulama kaybolur: cezası eşiği aşan 359 üye ilişkisel dizinle 1 gidişte ve
  5.903 baytta bulunurken, anahtar taramasıyla 20.000 gidiş ve 11,3 MB istiyor.
- Uygulamanın tuttuğu ikincil dizin bir ağaç değil bir değerdir: gidişi 360'a indirir ama
  243.132 baytlık listeyi her sorguda bütün halinde okutur ve kayıtla arasında tutarsızlık
  penceresi bırakır.
- Anahtar tasarımı tek ayardır: bütünsel değerde 73 baytlık bir ekleme 512 ödünçlü üyede 39.936
  bayt yazdırır (çarpan 407), ayrı anahtarda çarpan 1 kalır ama üye sayfası sıralı anahtar
  uzayında 2, karma anahtar uzayında 66 gidiş ister.

#### Sonraki Adım

Bu dersin bütün kazancı tek bir varsayıma dayanıyordu: uygulamanın istediği şey her seferinde
anahtarı bilinen tek bir bütündür. Varsayım tutmadığı anda model çöküyor — değerin içindeki bir
alana göre süzme, 20.000 gidiş demek. Ortada duran soru şudur: depo değerin içini **görebilse**
ne değişirdi. Sonraki ders bu aileyi ele alıyor ve iki şeyi ölçüyor: iç içe bir yapının tek
okumada gelmesiyle aynı verinin ilişkisel birleştirmeyle kurulması arasındaki fark ve şema
esnekliğinin okuma tarafına yıktığı yük — alanın var olup olmadığını her okumada denetlemenin
bedeli.

### Belge Veritabanları

Source: https://academia.sh/tr/kurslar/nosql/belge-veritabanlari

Değerin içini gören deponun kazandırdıkları ve ödettikleri: iç içe bir kitap kaydının tek belgede okunmasıyla dört ilişkisel tablodan birleştirilmesinin çarpım patlaması, alan yerinde güncellemenin yazma çarpanına etkisi ve şema esnekliğinin okuma tarafına yıktığı yük — aynı derlemde üç şema sürümü bulunduğunda dizinli sorgunun sessizce kaçırdığı kayıt sayısı ile savunmalı okumanın taradığı belge sayısı.

Önceki ders tek bir soruyla kapandı: depo değerin içini görebilse ne değişirdi. Belge
veritabanı (document database) tam olarak bu değişikliktir. Kayıt yine tek bir anahtarla
adlanır, ama depo artık değerin alanlarını tanır: alan üzerinde koşul yazılabilir, alan
üzerinde dizin kurulabilir ve alanın kendisi yerinde güncellenebilir.

Bu ders iki şeyi ölçer. Birincisi kazanç tarafıdır: iç içe bir kitap kaydının tek okumada
gelmesi ile aynı kaydın dört ilişkisel tablodan birleştirilmesi arasındaki fark. İkincisi
bedel tarafıdır: alanların şema tarafından zorunlu kılınmaması, denetimi yazma anından okuma
anına taşır ve bu taşımanın bir sayısı vardır.

#### Belge ve Derlem

Belge modelinde kayıt bir **belgedir (document)**: adlandırılmış alanlardan oluşan, alanları
kendi başına yine belge ya da dizi olabilen bir ağaç. Belgeler bir **derlem (collection)**
içinde toplanır. Derlem bir tablo değildir, çünkü içindeki belgelerin aynı alanları taşıması
zorunlu değildir; belge de bir satır değildir, çünkü alanı skaler olmak zorunda değildir.

Bu iki gevşetmenin doğrudan bir sonucu vardır: ilişkisel modelde ayrı tablolara dağılmak
zorunda olan çoklu değerler — bir kitabın yazarları, konuları, nüshaları — belgenin içinde
kalır. Birinci normal formun yasakladığı şey, belge modelinin tanımıdır.

**NS1:** katalog 50.000 kitap taşır; kitap `i`'nin 1 ile 3 arası yazarı, 2 ile 4 arası konusu
ve 1 ile 5 arası nüshası vardır. **NS2:** ilişkisel taraf `node:sqlite` ile kurulur ve
birleştirme sütunlarının hepsinde dizin vardır; baytlar iki tarafta da aynı JSON gösteriminden
sayılır.

```js
// belge/ic-ice.mjs — ayni kitap kaydinin dort iliskisel tabloda ve tek belgede okunmasi.
// Iliskisel taraf node:sqlite ile kurulur; sayilar kosumdan bagimsizdir.
import { DatabaseSync } from "node:sqlite";

const KITAP = 50_000, YAZARLAR = ["Atay", "Saramago", "Tanpinar", "Woolf", "Borges"];
const KONULAR = ["roman", "deneme", "siir", "tarih", "felsefe", "atlas"];
function* kitaplar() {                     // kitap i: 1+(i%3) yazar, 2+(i%3) konu, 1+(i%5) nusha
  for (let i = 1; i <= KITAP; i += 1) yield {
    kitap_id: i, baslik: `Kitap ${i}`,
    basim: { yil: 1960 + (i % 65), dil: i % 4 === 0 ? "en" : "tr" },
    yazarlar: Array.from({ length: 1 + (i % 3) }, (_, j) => YAZARLAR[(i + j) % 5]),
    konular: Array.from({ length: 2 + (i % 3) }, (_, j) => KONULAR[(i * 2 + j) % 6]),
    nushalar: Array.from({ length: 1 + (i % 5) }, (_, j) => ({
      nusha_id: i * 10 + j, sube_id: 1 + ((i + j) % 9), durum: j % 3 === 0 ? "rafta" : "oduncte" })),
  };
}

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
           basim_yili INTEGER NOT NULL, dil TEXT NOT NULL);
         CREATE TABLE kitap_yazar (kitap_id INTEGER NOT NULL, sira INTEGER NOT NULL, ad TEXT NOT NULL);
         CREATE TABLE kitap_konu (kitap_id INTEGER NOT NULL, konu TEXT NOT NULL);
         CREATE TABLE nusha (nusha_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
           sube_id INTEGER NOT NULL, durum TEXT NOT NULL);`);
const eK = db.prepare("INSERT INTO kitap VALUES (?,?,?,?)");
const eY = db.prepare("INSERT INTO kitap_yazar VALUES (?,?,?)");
const eT = db.prepare("INSERT INTO kitap_konu VALUES (?,?)");
const eN = db.prepare("INSERT INTO nusha VALUES (?,?,?,?)");
const derlem = new Map();                  // belge derlemi: kitap_id -> belge
db.exec("BEGIN");
for (const k of kitaplar()) {
  eK.run(k.kitap_id, k.baslik, k.basim.yil, k.basim.dil);
  k.yazarlar.forEach((a, j) => eY.run(k.kitap_id, j, a));
  for (const t of k.konular) eT.run(k.kitap_id, t);
  for (const n of k.nushalar) eN.run(n.nusha_id, k.kitap_id, n.sube_id, n.durum);
  derlem.set(k.kitap_id, JSON.stringify(k));
}
db.exec("COMMIT");
db.exec(`CREATE INDEX ky ON kitap_yazar(kitap_id); CREATE INDEX kt ON kitap_konu(kitap_id);
         CREATE INDEX nk ON nusha(kitap_id);`);

const bayt = (x) => Buffer.byteLength(JSON.stringify(x));
const H = 4244;
const b = JSON.parse(derlem.get(H));
console.log(`hedef kitap ${H}: ${b.yazarlar.length} yazar, ${b.konular.length} konu, ` +
  `${b.nushalar.length} nusha; carpim ${b.yazarlar.length * b.konular.length * b.nushalar.length}`);

const satir = [];
{ let n = 0, by = 0;
  for (const s of ["SELECT * FROM kitap WHERE kitap_id = ?", "SELECT ad FROM kitap_yazar WHERE kitap_id = ?",
                   "SELECT konu FROM kitap_konu WHERE kitap_id = ?", "SELECT * FROM nusha WHERE kitap_id = ?"]) {
    const r = db.prepare(s).all(H); n += r.length; by += bayt(r); }
  satir.push(["iliskisel, dort sorgu", 4, n, by]); }
{ const r = db.prepare(`SELECT k.baslik, k.basim_yili, k.dil, y.ad, t.konu, n.sube_id, n.durum
    FROM kitap k JOIN kitap_yazar y ON y.kitap_id = k.kitap_id
                 JOIN kitap_konu  t ON t.kitap_id = k.kitap_id
                 JOIN nusha       n ON n.kitap_id = k.kitap_id WHERE k.kitap_id = ?`).all(H);
  satir.push(["iliskisel, tek birlestirme", 1, r.length, bayt(r)]); }
{ satir.push(["belge, tek okuma", 1, 1, Buffer.byteLength(derlem.get(H))]); }

console.log(`\n${"yol".padEnd(28)}${"gidis".padStart(7)}${"satir/belge".padStart(13)}${"bayt".padStart(7)}`);
for (const [a, g, k, y] of satir)
  console.log(a.padEnd(28) + String(g).padStart(7) + String(k).padStart(13) + String(y).padStart(7));

// Ayni kaydin tek alanini degistirmek: nusha durumu "rafta" -> "oduncte"
const oncesi = Buffer.byteLength(derlem.get(H));
const alan = Buffer.byteLength(JSON.stringify({ durum: "oduncte" }));
console.log(`\ntek alan guncellemesi (nusha durumu)`);
console.log(`${"model".padEnd(28)}${"yazilan bayt".padStart(14)}${"carpan".padStart(8)}`);
console.log("iliskisel, tek satir".padEnd(28) + String(alan).padStart(14) + "1.0".padStart(8));
console.log("belge, alan yerinde".padEnd(28) + String(alan).padStart(14) + "1.0".padStart(8));
console.log("anahtar-deger, butun deger".padEnd(28) + String(oncesi).padStart(14) +
  (oncesi / alan).toFixed(1).padStart(8));
```

```
hedef kitap 4244: 3 yazar, 4 konu, 5 nusha; carpim 60

yol                           gidis  satir/belge   bayt
iliskisel, dort sorgu             4           13    513
iliskisel, tek birlestirme        1           60   6718
belge, tek okuma                  1            1    412

tek alan guncellemesi (nusha durumu)
model                         yazilan bayt  carpan
iliskisel, tek satir                    19     1.0
belge, alan yerinde                     19     1.0
anahtar-deger, butun deger             412    21.7
```

Sayılar **ölçüm** sınıfındadır ve belirlenimlidir.

Üst tablo ilişkisel tarafın gerçek ikilemini gösteriyor. Dört ayrı sorgu 13 satır ve 513 bayt
getiriyor, ama dört gidiş ediyor. Gidişi bire indirmek için üç birleştirme yazıldığında sonuç
**60 satır** oluyor: üç yazar, dört konu ve beş nüshanın çarpımı. Kitabın başlığı, basım yılı
ve dili altmış kez tekrarlanıyor ve taşınan bayt 513'ten 6.718'e, on üç katına çıkıyor. Bu,
birleştirmenin bir kusuru değil tanımıdır — ilişkisel sonuç kümesi düz bir tablodur ve
birbirinden bağımsız üç çoklu değer aynı düzlemde yan yana getirildiğinde çarpım kaçınılmazdır.

Belge tarafında aynı kayıt **tek gidiş, tek belge, 412 bayttır**. Tekrar yoktur, çünkü sonuç
düzleştirilmemiştir: yazarlar bir dizi, konular bir dizi, nüshalar belgelerden oluşan bir dizi
olarak kalır. Uygulamanın istediği ağacın kendisi diskte de ağaç olarak durur.

Alt tablo, önceki dersteki yazma çarpanı ölçümünün belge tarafındaki karşılığıdır. Bir nüshanın
durumunu değiştirmek 19 bayt bilgidir. İlişkisel motor tek satırı günceller. Belge veritabanı
alanı belgenin içinde yerinde günceller — depo alanı tanıdığı için bütün belgeyi geri yazmak
zorunda değildir. Anahtar–değer deposunda aynı değişiklik 412 baytın tamamının yeniden
yazılmasıdır: çarpan 21,7. Belge modeli, önceki dersin ödünleşiminin bir ucunu — geniş değer,
tek okuma — koruyup öteki ucunu, yazma çarpanını ödemeden alır.

#### Şemanın Okuma Tarafına Geçmesi

Bu kazancın bir bedeli vardır ve bedel şemanın kendisidir. İlişkisel şemada `NOT NULL` bir
yazma zamanı kuralıdır: alan yoksa kayıt reddedilir ve okuyan taraf alanın varlığını denetlemek
zorunda değildir. Belge derleminde böyle bir kural yoktur; alanın var olup olmadığı her okumada
sorulur.

Bu bir tasarım hatası değil, uygulamanın zamanla değişmesinin doğal sonucudur. Aynı derlemde
farklı zamanlarda yazılmış üç şema sürümü yan yana durur: ilk sürümde basım yılı üst düzeyde
`yil` alanıdır, ikinci sürümde `basim` alt belgesine taşınmıştır, üçüncü sürümde bir de
`dijital` alanı eklenmiştir. Soru değişmez — 2000 sonrası basılmış kitaplar hangileridir.

```js
// belge/sema.mjs — sema esnekliginin okuma tarafina yikilan yuku. Ayni derlemde uc sema
// surumu bulunur; ayni soru dort yolla sorulur. Iliskisel taraf node:sqlite ile kurulur.
import { DatabaseSync } from "node:sqlite";

const KITAP = 50_000, ESIK = 2000;
function surum(i) { return i <= 20_000 ? 1 : i <= 40_000 ? 2 : 3; }
function belge(i) {                        // v1: yil ust duzeyde; v2: basim.yil; v3: + dijital
  const yil = 1960 + (i % 65), ortak = { kitap_id: i, baslik: `Kitap ${i}` };
  if (surum(i) === 1) return { ...ortak, yil, dil: i % 4 === 0 ? "en" : "tr" };
  const b = { ...ortak, basim: { yil, dil: i % 4 === 0 ? "en" : "tr" } };
  return surum(i) === 3 ? { ...b, dijital: i % 2 === 0 } : b;
}

class BelgeDeposu {
  #belgeler = []; #dizin = new Map();
  gidis = 0; taranan = 0;
  ekle(b) { this.#belgeler.push(b); }
  al(b, yol) { return yol.split(".").reduce((o, k) => (o == null ? undefined : o[k]), b); }
  dizinKur(yol) { const g = [];
    for (const b of this.#belgeler) { const v = this.al(b, yol);
      if (v === undefined) continue;       // alani olmayan belge dizine hic girmez
      for (const x of Array.isArray(v) ? v : [v]) g.push([x, b.kitap_id]); }
    this.#dizin.set(yol, g.sort((p, q) => p[0] - q[0])); return g.length; }
  dizinliBuyuk(yol, esik) {                // sirali dizinde esigin ustundeki son ek okunur
    this.gidis += 1; const g = this.#dizin.get(yol);
    let a = 0, u = g.length;
    while (a < u) { const o = (a + u) >> 1; if (g[o][0] <= esik) a = o + 1; else u = o; }
    this.taranan += g.length - a; return g.slice(a).map(([, id]) => id); }
  taramaliBuyuk(sart) {                    // dizin yok: her belge tek tek bakilir
    this.gidis += 1; const c = [];
    for (const b of this.#belgeler) { this.taranan += 1; if (sart(b)) c.push(b.kitap_id); }
    return c; }
  sifirla() { this.gidis = 0; this.taranan = 0; }
}

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
           basim_yili INTEGER NOT NULL, dil TEXT NOT NULL);`);
const ekle = db.prepare("INSERT INTO kitap VALUES (?,?,?,?)");
const depo = new BelgeDeposu();
db.exec("BEGIN");
for (let i = 1; i <= KITAP; i += 1) {
  const b = belge(i);
  ekle.run(i, b.baslik, b.yil ?? b.basim.yil, b.dil ?? b.basim.dil);
  depo.ekle(b);
}
db.exec("COMMIT");
db.exec("CREATE INDEX kitap_yil ON kitap(basim_yili);");
const d2 = depo.dizinKur("basim.yil"), d1 = depo.dizinKur("yil");
console.log(`${KITAP} kitap, esik ${ESIK}; surum dagilimi 1:20000 2:20000 3:10000`);
console.log(`dizin girdi sayisi — basim.yil: ${d2}, yil: ${d1}`);

const dogru = db.prepare("SELECT kitap_id FROM kitap WHERE basim_yili > ?").all(ESIK).map((r) => r.kitap_id);
const yol = [];
yol.push(["iliskisel, basim_yili dizini", 1, dogru.length, dogru.length]);
depo.sifirla(); let r = depo.dizinliBuyuk("basim.yil", ESIK);
yol.push(["belge, basim.yil dizini", depo.gidis, depo.taranan, r.length]);
depo.sifirla(); r = [...depo.dizinliBuyuk("basim.yil", ESIK), ...depo.dizinliBuyuk("yil", ESIK)];
yol.push(["belge, iki dizin birlesimi", depo.gidis, depo.taranan, r.length]);
depo.sifirla(); r = depo.taramaliBuyuk((b) => (b.basim?.yil ?? b.yil) > ESIK);
yol.push(["belge, savunmali tarama", depo.gidis, depo.taranan, r.length]);

console.log(`\n${"yol".padEnd(30)}${"gidis".padStart(7)}${"taranan giris".padStart(15)}` +
  `${"sonuc".padStart(7)}${"eksik".padStart(7)}`);
for (const [a, g, t, n] of yol)
  console.log(a.padEnd(30) + String(g).padStart(7) + String(t).padStart(15) +
    String(n).padStart(7) + String(dogru.length - n).padStart(7));

let redU = 0, kabulB = 0;                  // yil alani olmayan 500 kayit
for (let i = KITAP + 1; i <= KITAP + 500; i += 1) {
  try { ekle.run(i, `Kitap ${i}`, null, "tr"); } catch { redU += 1; }
  depo.ekle({ kitap_id: i, baslik: `Kitap ${i}` }); kabulB += 1;
}
console.log(`\nyil alani olmayan 500 kayit — iliskisel reddetti: ${redU}, belge derlemi kabul etti: ${kabulB}`);
depo.sifirla(); const sonra = depo.taramaliBuyuk((b) => (b.basim?.yil ?? b.yil) > ESIK);
console.log(`savunmali tarama artik ${depo.taranan} belge geziyor, sonuc ${sonra.length}`);
```

```
50000 kitap, esik 2000; surum dagilimi 1:20000 2:20000 3:10000
dizin girdi sayisi — basim.yil: 30000, yil: 20000

yol                             gidis  taranan giris  sonuc  eksik
iliskisel, basim_yili dizini        1          18456  18456      0
belge, basim.yil dizini             1          11083  11083   7373
belge, iki dizin birlesimi          2          18456  18456      0
belge, savunmali tarama             1          50000  18456      0

yil alani olmayan 500 kayit — iliskisel reddetti: 500, belge derlemi kabul etti: 500
savunmali tarama artik 50500 belge geziyor, sonuc 18456
```

İkinci satır bu dersin en pahalı sayısıdır. Sorgu hata vermiyor, boş dönmüyor, yavaş
çalışmıyor: **1 gidişte 11.083 sonuç dönüyor ve 7.373 kitap sessizce kayboluyor.** Neden dizin
tanımının kendisindedir — `basim.yil` alanı olmayan belge dizine hiç girmez, dolayısıyla
dizinden yanıtlanan bir sorgu için o belgeler yoktur. İlişkisel şemada bu durum oluşamaz, çünkü
alanın yokluğu yazma anında reddedilmiştir; son satır bunu doğruluyor: aynı 500 eksik kayıt
ilişkisel tarafta reddedildi, belge derleminde kabul edildi.

Doğru sonuca iki yol var ve ikisinin de bir bedeli var. **İki dizin birleşimi** doğru 18.456
kaydı veriyor ama gidiş sayısı ikiye çıkıyor ve her şema sürümü için ayrı bir dizin
tutulmasını, sorguyu yazan tarafın da bütün sürümleri bilmesini gerektiriyor. **Savunmalı
tarama** tek gidişte doğru yanıtı veriyor, ama hiçbir dizin kullanamıyor: koşul iki ayrı yol
arasında seçim yaptığı için dizinlenebilir bir ifade değil. Taranan giriş 18.456'dan 50.000'e
çıkıyor — sonucun 2,7 katı. Eksik alanlı 500 kayıt eklendikten sonra tarama 50.500 belgeye
uzuyor; sonuç değişmiyor.

Ödünleşim tek cümlede toplanır: **belge modeli şema denetimini kaldırmaz, yazma anından okuma
anına taşır.** Yazma tarafında kazanılan esneklik, okuma tarafında ya bir doğruluk açığı ya bir
dizin çoğalması ya da bir tarama olarak geri gelir. Bu yükün tamamen ortadan kalkması için
şemanın derleme geri konması gerekir; bunun nasıl yapıldığı ve neye mal olduğu bu kursun
ilerleyen konusudur.

#### Özet

- Belge modelinde kayıt, alanları yine belge ya da dizi olabilen bir ağaçtır; derlem, belgelerin
  aynı alanları taşımasını zorunlu kılmaz.
- İç içe kitap kaydı belge tarafında 1 gidiş ve 412 bayt; ilişkisel tarafta ya 4 gidiş ve 513
  bayt, ya da tek birleştirmeyle 60 satır ve 6.718 bayttır — üç çoklu değerin çarpımı.
- Alanın yerinde güncellenmesi 19 bayt yazar; aynı değişiklik anahtar–değer modelinde bütün
  değerin yeniden yazılmasıdır, çarpan 21,7.
- Şema esnekliği denetimi okuma anına taşır: üç sürümlü bir derlemde `basim.yil` dizininden
  yanıtlanan sorgu 7.373 kaydı sessizce kaçırıyor, ilişkisel şema aynı 500 eksik kaydı yazma
  anında reddediyor.
- Doğru yanıtın iki yolu da ödetiyor: iki dizin birleşimi gidişi 2'ye çıkarıyor, savunmalı
  tarama dizini kullanamadığı için taranan girişi 18.456'dan 50.000'e çıkarıyor.

#### Sonraki Adım

Buraya kadar iki aile de kaydı bir bütün olarak ele aldı: anahtar–değer deposu değeri opak bir
bütün saydı, belge veritabanı ağacı tanıdı ama yine bütün belgeyi tek yerde tuttu. İkisinde de
bir kaydın bütün alanları diskte yan yana durur, dolayısıyla tek bir alan için yapılan okuma da
o kaydın tamamının bulunduğu yeri okur. Katalog kaydının kırk alanı varsa ve sorgu yalnız
ikisini istiyorsa, geri kalan otuz sekizi de diskten geçer. Sonraki ders yerleşimi alanlara
göre ayıran aileyi ele alıyor ve üç şey ölçüyor: aynı sorgunun sütun ailesine göre yerleşimde
okuduğu bayt, tek anahtarın altına binlerce sütun toplanmasının anlamı ve yazma yolunun
yerinde değiştirme yerine eklemeli olmasının silme işlemine ödettiği bedel.

### Geniş Sütun Depoları

Source: https://academia.sh/tr/kurslar/nosql/genis-sutun-depolari

Yerleşimin alanlara göre ayrılmasının iki yönlü bedeli: aynı verinin satır yerleşiminde ve sütun ailesi yerleşiminde okunan sayfa sayısı, tek anahtarın altında binlerce sütun toplayan geniş satırın aralık dilimi, bölüm anahtarının erişim örüntüsünü belirlemesi ve iki örüntüyü birden karşılamanın yazma çarpanı, eklemeli yazma yolunda silme işaretinin yer çarpanına ve okunan girdiye etkisi.

Önceki iki ailede kaydın bütün alanları diskte yan yana duruyordu. Anahtar–değer deposunda
değer tek bir bayt dizisiydi; belge veritabanı ağacı tanıdı ama belgeyi yine tek yerde tuttu.
Bunun bir sonucu vardı: yalnız iki alanı isteyen bir sorgu, o kayıtların tamamının bulunduğu
yeri okur. Geniş sütun deposu (wide column store) yerleşimi bu noktadan böler.

Model şudur: bir satır anahtarıyla adlanır, satırın sütunları önceden bildirilmiş
**sütun ailelerine (column family)** ayrılır ve aynı ailenin sütunları diskte birlikte durur.
Bu ders üç şeyi ölçer: yerleşimin okunan bayta etkisi, tek anahtarın altında binlerce sütunun
toplanması ve yazma yolunun yerinde değiştirme yerine eklemeli olması.

#### Yerleşim Neyi Değiştirir

Okuma birimi baytın kendisi değildir; depo diski sayfa sayfa okur. Bu yüzden yerleşim
sorusunun karşılığı "kaç alan okundu" değil, **kaç sayfaya dokunuldu**dur. **NS1:** katalog
20.000 kayıt taşır ve dört aileye ayrılır — `kimlik`, `basim`, `dolasim`, `metin`; özet alanı
kaydın en büyük parçasıdır. **NS2:** sayfa 4096 bayttır ve okuma bir sayfanın herhangi bir
baytına dokunduğunda o sayfanın tamamını okur.

```js
// sutun/yerlesim.mjs — ayni kayitlarin satir yerlesiminde ve sutun ailesi yerlesiminde
// diske serilmesi, sonra iki sorgunun dokundugu 4096 baytlik sayfalarin sayilmasi.
// Okuma birimi sayfadir; sutun ailesi yerlesiminde erisim birimi ailedir, sutun degil.
const KAYIT = 20_000, SAYFA = 4096;
const AILE = { kimlik: ["kitap_id", "baslik", "yazar"], basim: ["yil", "dil", "isbn"],
               dolasim: ["odunc_sayisi", "sube_id", "durum"], metin: ["ozet"] };
const kitap = (i) => ({ kitap_id: i, baslik: `Kitap ${i} ${"a".repeat(30)}`,
  yazar: `Yazar ${i % 500} ${"b".repeat(12)}`, yil: 1960 + (i % 65), dil: i % 4 === 0 ? "en" : "tr",
  isbn: `978-${String(1000000000 + i)}`, odunc_sayisi: i % 97, sube_id: 1 + (i % 9),
  durum: i % 3 === 0 ? "rafta" : "oduncte", ozet: `${"c".repeat(280)} ${i}` });

function seril(satirYerlesimi) {           // her alan icin [baslangic, bitis] bayt araligi
  const yer = new Map(); let ofset = 0;
  const koy = (i, alan) => { const n = Buffer.byteLength(String(kitap(i)[alan]));
    yer.set(`${i}|${alan}`, [ofset, ofset + n]); ofset += n; };
  if (satirYerlesimi) { for (let i = 1; i <= KAYIT; i += 1)
      for (const a of Object.values(AILE)) for (const alan of a) koy(i, alan); }
  else { for (const a of Object.values(AILE))
      for (let i = 1; i <= KAYIT; i += 1) for (const alan of a) koy(i, alan); }
  return { yer, boyut: ofset };
}
const sayfaSayisi = (araliklar) => { const s = new Set();
  for (const [b, u] of araliklar)
    for (let p = b / SAYFA | 0; p <= (u - 1) / SAYFA | 0; p += 1) s.add(p);
  return s.size; };

const satir = seril(true), aile = seril(false);
console.log(`${KAYIT} kayit, toplam ${satir.boyut} bayt, ${Math.ceil(satir.boyut / SAYFA)} sayfa`);
for (const [ad, alanlar] of Object.entries(AILE)) {
  let n = 0; for (let i = 1; i <= KAYIT; i += 1) for (const alan of alanlar)
    n += aile.yer.get(`${i}|${alan}`)[1] - aile.yer.get(`${i}|${alan}`)[0];
  console.log(`  aile ${ad.padEnd(8)} ${String(n).padStart(9)} bayt`);
}

const H = 4244;
const S1 = [], A1 = [];                    // S1: butun kayitlarda dolasim ailesi
for (let i = 1; i <= KAYIT; i += 1) for (const alan of AILE.dolasim) {
  S1.push(satir.yer.get(`${i}|${alan}`)); A1.push(aile.yer.get(`${i}|${alan}`)); }
const S2 = [], A2 = [];                    // S2: tek kaydin butun alanlari
for (const a of Object.values(AILE)) for (const alan of a) {
  S2.push(satir.yer.get(`${H}|${alan}`)); A2.push(aile.yer.get(`${H}|${alan}`)); }

console.log(`\n${"sorgu".padEnd(38)}${"satir yerlesimi".padStart(17)}${"aile yerlesimi".padStart(16)}${"oran".padStart(7)}`);
for (const [ad, s, a] of [["butun kitaplarin dolasim ozeti", S1, A1], [`tek kitabin (${H}) butun alanlari`, S2, A2]]) {
  const ps = sayfaSayisi(s), pa = sayfaSayisi(a);
  console.log(ad.padEnd(38) + `${ps} sayfa`.padStart(17) + `${pa} sayfa`.padStart(16) +
    (ps / pa).toFixed(2).padStart(7));
}
```

```
20000 kayit, toplam 7646881 bayt, 1867 sayfa
  aile kimlik     1353388 bayt
  aile basim       400000 bayt
  aile dolasim     184599 bayt
  aile metin      5708894 bayt

sorgu                                   satir yerlesimi  aile yerlesimi   oran
butun kitaplarin dolasim ozeti               1867 sayfa        46 sayfa  40.59
tek kitabin (4244) butun alanlari               1 sayfa         4 sayfa   0.25
```

Sayılar **ölçüm** sınıfındadır: iki yerleşim de gerçekten serildi, sayfalar gerçekten sayıldı.

İki satır birbirinin tersini söylüyor ve modelin tamamı bu iki satırdadır. Bütün kayıtların
dolaşım özetini almak satır yerleşiminde **1.867 sayfa**, aile yerleşiminde **46 sayfa** okuyor:
kırk kat fark. Neden basit bir orandır — dolaşım ailesi verinin 184.599 baytıdır, geri kalan
7,4 MB'ın çoğu özet metnidir ve satır yerleşiminde her dolaşım alanı bir özet metninin yanında
durduğu için o metin de sayfayla birlikte okunur.

İkinci satır bedeli gösteriyor. Tek bir kitabın bütün alanlarını okumak satır yerleşiminde
**tek sayfa**, aile yerleşiminde **dört sayfadır** — her aile ayrı bir yerdedir, dolayısıyla
kaydı toplamak için dört ayrı yere gitmek gerekir. Oran 0,25: aile yerleşimi bu sorguda dört
kat kötüdür. **Yerleşim kararı bir sorgu sınıfını ucuzlatırken ötekini pahalılaştırır**; ailenin
sınırı, birlikte okunan alanların sınırı olmalıdır.

#### Geniş Satır

Modelin ikinci ayırt edici yanı satırın genişliğinin sınırsız olmasıdır. Bir satır anahtarının
altında binlerce, on binlerce sütun toplanabilir ve sütun adı bir şema alanı değil, bir
**veridir**: kütüphane örneğinde her ödünç, kitap satırının altında bir sütundur ve sütun adı
ödüncün zaman damgasıdır. Sütunlar satır içinde ada göre sıralı durduğu için bir zaman aralığı
tek istekte dilimlenir. Bunun adı **geniş satırdır (wide row)**.

```js
// sutun/genis-satir.mjs — odunc gecmisinin genis satir olarak tutulmasi. Sutunlar satir
// icinde sirali durur, aralik dilimi tek istektir; ayni soru baska bir bolum anahtariyla
// sorulunca taramaya duser. Sayilar kosumdan bagimsizdir.
const KITAP = 5_000, POPULER = 4_000;      // ilk bes kitap POPULER kez odunc verilir
class GenisSutunDeposu {
  #satir = new Map();
  gidis = 0; sutun = 0; girdi = 0;
  yaz(anahtar, ad, deger) { if (!this.#satir.has(anahtar)) this.#satir.set(anahtar, new Map());
    this.#satir.get(anahtar).set(ad, deger); this.girdi += 1; }
  dilim(anahtar, alt, ust) {               // satir icinde sirali aralik: tek istek
    this.gidis += 1; const s = this.#satir.get(anahtar) ?? new Map(); const c = [];
    for (const [ad, d] of [...s].sort(([x], [y]) => (x < y ? -1 : 1)))
      if (ad >= alt && ad < ust) { this.sutun += 1; c.push(d); }
    return c; }
  tara(sart) { this.gidis += 1; const c = [];
    for (const [a, s] of this.#satir) for (const [ad, d] of s) { this.sutun += 1;
      if (sart(a, ad, d)) c.push(d); }
    return c; }
  enGenis() { let e = 0, k = null;
    for (const [a, s] of this.#satir) if (s.size > e) { e = s.size; k = a; }
    return [k, e]; }
  sifirla() { this.gidis = 0; this.sutun = 0; }
}

const kitapDepo = new GenisSutunDeposu(), uyeDepo = new GenisSutunDeposu();
for (let i = 1; i <= KITAP; i += 1)
  for (let j = 0, n = i <= 5 ? POPULER : 1 + (i % 97); j < n; j += 1) {
    const ay = 1 + ((i + j) % 12), gun = 1 + ((i * 3 + j * 7) % 28), uye = 1 + ((i * 13 + j) % 20_000);
    const t = `2024-${String(ay).padStart(2, "0")}-${String(gun).padStart(2, "0")}`;
    kitapDepo.yaz(`kitap:${i}`, `odunc|${t}|${j}`, uye);        // bolum anahtari kitap
    uyeDepo.yaz(`uye:${uye}`, `odunc|${t}|${i}|${j}`, i);       // bolum anahtari uye
  }
const [ea, es] = kitapDepo.enGenis();
console.log(`${KITAP} kitap, ${kitapDepo.girdi} odunc girdisi; ortalama satir ` +
  `${(kitapDepo.girdi / KITAP).toFixed(1)} sutun, en genis satir ${ea} ${es} sutun`);

const H = 3, ALT = "odunc|2024-03", UST = "odunc|2024-04";
const yaz = (ad, depo, sonuc) => console.log(ad.padEnd(36) + String(depo.gidis).padStart(7) +
  String(depo.sutun).padStart(14) + String(sonuc).padStart(7));
console.log(`\n${"yol".padEnd(36)}${"gidis".padStart(7)}${"okunan sutun".padStart(14)}${"sonuc".padStart(7)}`);
kitapDepo.sifirla(); let r = kitapDepo.dilim(`kitap:${H}`, ALT, UST);
yaz("kitap bolumu, aralik dilimi", kitapDepo, r.length);
kitapDepo.sifirla(); r = kitapDepo.dilim(`kitap:${H}`, "odunc|", "odunc}");
yaz("kitap bolumu, butun satir", kitapDepo, r.length);
uyeDepo.sifirla(); r = uyeDepo.tara((a, ad, d) => ad.startsWith(ALT) && d === H);
yaz("uye bolumu, ayni soru", uyeDepo, r.length);

console.log(`\niki erisim oruntusu birden: ${kitapDepo.girdi} + ${uyeDepo.girdi} = ` +
  `${kitapDepo.girdi + uyeDepo.girdi} girdi yazilir, yazma carpani ` +
  `${((kitapDepo.girdi + uyeDepo.girdi) / kitapDepo.girdi).toFixed(1)}`);
```

```
5000 kitap, 263867 odunc girdisi; ortalama satir 52.8 sutun, en genis satir kitap:1 4000 sutun

yol                                   gidis  okunan sutun  sonuc
kitap bolumu, aralik dilimi               1           333    333
kitap bolumu, butun satir                 1          4000   4000
uye bolumu, ayni soru                     1        263867    333

iki erisim oruntusu birden: 263867 + 263867 = 527734 girdi yazilir, yazma carpani 2.0
```

Bir ayın ödünçleri, dört bin sütunluk satırın içinden **333 sütun** okunarak alınıyor: sütunlar
sıralı olduğu için depo aralığın başına inip sonuna kadar yürüyor, satırın geri kalanına hiç
dokunmuyor. Aynı satırın tamamı istendiğinde okunan sütun 4.000'e çıkıyor — geniş satır bir
avantaj değil, doğru dilimlendiğinde avantajdır.

Üçüncü satır bölüm anahtarının ne kadar belirleyici olduğunu gösteriyor. Aynı veri üye
anahtarıyla bölümlendiğinde bir kitabın ödünçleri hiçbir satırın içinde bir arada değildir ve
soru 263.867 sütunluk bir taramaya düşer. Karşılığı bilinen bir karardır: aynı veri ikinci bir
bölüm anahtarıyla ikinci kez yazılır — Veri Katmanı Ölçekleme kursunda bu karar sorgu sayısı
üzerinden ölçülmüştü, buradaki karşılığı yazma tarafındadır ve tam olarak iki katıdır.

Geniş satırın kendi sınırı da bu tabloda duruyor. Satır bölümlemenin birimi olduğu için tek bir
satır sınırsız büyüyemez: en geniş satır 4.000 sütun taşırken ortalama satır 52,8 sütundur.
Sınırsız büyüyen bir satır, Veri Katmanı Ölçekleme kursunda adı konan sıcak noktanın ta
kendisidir; çözüm satır anahtarına bir zaman kutusu eklemektir.

#### Eklemeli Yazma Yolu

Bu ailenin yazma başarımı yerleşimden değil yazma yolundan gelir. Yazma yerinde değişiklik
yapmaz: her yazma **yeni bir girdidir** ve sıraya eklenir. Bellekteki tablo dolduğunda
değiştirilemez bir dosyaya çevrilir ve bir daha dokunulmaz. Silme de bir yazmadır — kaydın
üstüne **silme işareti (tombstone)** konur. Okuma, dosyaları yeniden eskiye doğru tarar ve ilk
bulduğu sürümü döndürür. Geçersiz sürümleri ve silme işaretlerini düşürmek ayrı bir işin —
**toparlamanın (compaction)** — konusudur.

```js
// sutun/eklemeli.mjs — eklemeli yazma yolu: her yazma yeni girdidir, silme bir silme
// isaretidir. Bellek tablosu dolunca degistirilemez bir dosyaya cevrilir. Okuma dosyalari
// yeniden eskiye tarar. Toparlama, gecersiz surumleri ve silme isaretlerini duser.
const DOSYA_BOYU = 25_000, SILME = Symbol("silme isareti");
class EklemeliDepo {
  #bellek = new Map(); #dosyalar = []; yazilanGirdi = 0;
  bakilanDosya = 0; okunanGirdi = 0;
  #cevir() { if (this.#bellek.size >= DOSYA_BOYU) { this.#dosyalar.push(this.#bellek); this.#bellek = new Map(); } }
  yaz(k, d) { this.#bellek.set(k, d); this.yazilanGirdi += 1; this.#cevir(); }
  sil(k) { this.yaz(k, SILME); }
  #yeniden() { return [this.#bellek, ...this.#dosyalar.slice().reverse()]; }   // yeniden eskiye
  oku(k) { const kaynak = this.#yeniden();
    for (const s of kaynak) { this.bakilanDosya += 1;
      if (s.has(k)) { this.okunanGirdi += 1; const d = s.get(k); return d === SILME ? undefined : d; } }
    return undefined; }
  tara() { const gorulen = new Set(), c = [];
    for (const s of this.#yeniden()) { this.bakilanDosya += 1;
      for (const [k, d] of s) { this.okunanGirdi += 1;
        if (gorulen.has(k)) continue; gorulen.add(k); if (d !== SILME) c.push(k); } }
    return c; }
  toparla() { const y = new Map();
    for (const s of [...this.#dosyalar, this.#bellek]) for (const [k, d] of s) y.set(k, d);
    for (const [k, d] of y) if (d === SILME) y.delete(k);
    this.#dosyalar = []; this.#bellek = new Map();
    for (const [k, d] of y) this.yaz(k, d);
    this.yazilanGirdi = y.size; return y.size; }
  durum() { return [this.#dosyalar.length + 1, this.yazilanGirdi]; }
  sifirla() { this.bakilanDosya = 0; this.okunanGirdi = 0; }
}

const depo = new EklemeliDepo(), N = 100_000;
for (let i = 1; i <= N; i += 1) depo.yaz(`odunc:${i}`, { kitap: i % 5000, iade: null });
for (let i = 1; i <= N; i += 1) if (i % 5 < 2) depo.yaz(`odunc:${i}`, { kitap: i % 5000, iade: "2024-06-01" });
for (let i = 1; i <= N; i += 1) if (i % 5 === 4) depo.sil(`odunc:${i}`);
const [dosya, girdi] = depo.durum();
const canli = N - Math.floor(N / 5);
console.log(`${N} kayit, ${girdi} yazilan girdi, ${canli} canli kayit; ` +
  `${dosya} dosya, yer carpani ${(girdi / canli).toFixed(2)}`);

const olcum = (ad, is) => { depo.sifirla(); const r = is();
  console.log(ad.padEnd(30) + String(depo.bakilanDosya).padStart(15) +
    String(depo.okunanGirdi).padStart(15) + String(r).padStart(9)); };
console.log(`\n${"okuma".padEnd(30)}${"bakilan dosya".padStart(15)}${"okunan girdi".padStart(15)}${"sonuc".padStart(9)}`);
olcum("guncellenen anahtar", () => (depo.oku("odunc:99996") ? "bulundu" : "yok"));
olcum("hic dokunulmamis anahtar", () => (depo.oku("odunc:2") ? "bulundu" : "yok"));
olcum("silinmis anahtar", () => (depo.oku("odunc:99994") ? "bulundu" : "yok"));
olcum("hic yazilmamis anahtar", () => (depo.oku("odunc:999999") ? "bulundu" : "yok"));
olcum("butun kayitlarin taranmasi", () => depo.tara().length);

const sonra = depo.toparla();
console.log(`\ntoparlama sonrasi: ${sonra} girdi, ${depo.durum()[0]} dosya, ` +
  `yer carpani ${(sonra / canli).toFixed(2)}`);
depo.sifirla(); const t = depo.tara();
console.log(`ayni tarama artik ${depo.okunanGirdi} girdi okuyor, sonuc ${t.length}`);
```

```
100000 kayit, 160000 yazilan girdi, 80000 canli kayit; 7 dosya, yer carpani 2.00

okuma                           bakilan dosya   okunan girdi    sonuc
guncellenen anahtar                         2              1  bulundu
hic dokunulmamis anahtar                    7              1  bulundu
silinmis anahtar                            1              1      yok
hic yazilmamis anahtar                      7              0      yok
butun kayitlarin taranmasi                  7         160000    80000

toparlama sonrasi: 80000 girdi, 4 dosya, yer carpani 1.00
ayni tarama artik 80000 girdi okuyor, sonuc 80000
```

Yazma tarafı ucuzdur ve bu tablo nedenini gösteriyor: hiçbir yazma bir okuma gerektirmedi,
hiçbir dosya değiştirilmedi. Bedel okuma tarafında ve yerdedir. 80.000 canlı kayıt için diskte
160.000 girdi duruyor; **yer çarpanı 2,00**. Silinen 20.000 kayıt hâlâ oradadır — silme işareti
yeri boşaltmaz, doldurur.

Okuma satırları maliyetin nereye dağıldığını gösteriyor. Yakın zamanda güncellenen bir anahtar
2 dosyada bulunuyor. Uzun süredir dokunulmamış bir anahtar 7 dosya gezdiriyor, çünkü en eski
dosyadadır ve ondan yenisinde olmadığı ancak hepsine bakılarak anlaşılır. En pahalısı **hiç
yazılmamış anahtardır**: 7 dosyanın hepsi taranır ve sonuç yoktur. Silinmiş anahtar ise tek
dosyada biter — silme işareti en yeni girdidir ve okuma orada durur.

Tarama satırı silme işaretinin asıl bedelini taşıyor: 80.000 canlı kayıt için 160.000 girdi
okunuyor. Toparlama bu iki sayıyı eşitliyor — girdi 80.000'e, yer çarpanı 1,00'e, dosya sayısı
7'den 4'e iniyor ve aynı tarama 80.000 girdi okuyor. Toparlamanın kendisi bütün veriyi bir kez
daha yazar; yani eklemeli yazma yolu yazma işini ucuzlatmaz, **arka plana erteler**.

#### Özet

- Geniş sütun modelinde satır bir anahtarla adlanır, sütunlar sütun ailelerine ayrılır ve aynı
  ailenin sütunları diskte birlikte durur.
- Yerleşim iki yönlü çalışıyor: dolaşım özeti satır yerleşiminde 1.867, aile yerleşiminde 46
  sayfa okuyor (40,6 kat); tek kaydın bütün alanları ise 1 sayfaya karşı 4 sayfadır.
- Geniş satırda sütun adı bir veridir ve sütunlar sıralıdır: 4.000 sütunluk satırdan bir ayın
  333 ödüncü tek istekte dilimleniyor, aynı soru başka bir bölüm anahtarıyla 263.867 sütunluk
  taramaya düşüyor.
- İki erişim örüntüsünü birden karşılamak aynı verinin ikinci kez yazılmasıdır: 263.867 girdi
  527.734'e çıkar, yazma çarpanı 2,0.
- Eklemeli yazma yolunda hiçbir yazma okuma gerektirmez; bedeli yer çarpanı 2,00 ve hiç
  yazılmamış bir anahtarın 7 dosya gezdirmesidir. Silme işareti yeri boşaltmaz.
- Toparlama girdi sayısını 160.000'den 80.000'e, yer çarpanını 1,00'e indirir; karşılığında
  bütün veriyi bir kez daha yazar.

#### Sonraki Adım

Üç ailenin üçü de aynı varsayımı paylaşıyordu: sorgunun konusu kaydın kendisidir. Anahtar
biliniyorsa kayıt gelir, bir alan üzerinde koşul varsa dizin kurulur, bir aralık isteniyorsa
yerleşim ona göre seçilir. Kütüphane verisinde ise bazı sorular kayıtlarla değil **kayıtlar
arasındaki bağlarla** ilgilidir: aynı kitabı ödünç almış üyeler üzerinden iki üye arasındaki
uzaklık, bir yazarın ortak çalıştığı yazarlardan yürüyerek ulaşılan yazarlar. İlişkisel modelde
bu bağ bir birleştirme tablosudur ve her adım yeni bir birleştirme demektir. Sonraki ders bağın
kendisini birinci sınıf nesne yapan aileyi ele alıyor ve şunu ölçüyor: aynı soruda komşuluk
listesinin ve birleştirme tablosunun dokunduğu kayıt sayısı ve bu sayının derinlik arttıkça
nasıl büyüdüğü.

### Çizge Veritabanları

Source: https://academia.sh/tr/kurslar/nosql/cizge-veritabanlari

Bağın kendisinin bir kayıt olmasının ölçülen karşılığı: aynı ilişki sorgusunun birleştirme tablosunda ve komşuluk listesinde dokunduğu kayıt sayısı, bu sayının derinlikle yol sayısı gibi mi düğüm sayısı gibi mi büyüdüğü, adım adım tekilleştirmenin ilişkisel tarafta neye mal olduğu ve derinliği bilinmeyen en kısa zincir sorusunda iki uçtan dolaşmanın kazancı.

Önceki üç ailede sorgunun konusu kaydın kendisiydi: anahtar biliniyorsa değer gelir, bir alan
üzerinde koşul varsa dizin kurulur, bir aralık isteniyorsa yerleşim ona göre seçilir. Kütüphane
verisinde bazı sorular ise kayıtlarla değil, **kayıtlar arasındaki bağlarla** ilgilidir: bu
kitabı ödünç alanların ayrıca aldığı kitaplar, iki üye arasındaki en kısa ortak okuma zinciri.
İlişkisel modelde bu bağ bir birleştirme tablosudur ve her adım yeni bir birleştirmedir. Çizge
veritabanı ise bağı birinci sınıf bir nesne yapar. Bu ders modeli `node` ile kurar, aynı sorunun
iki modelde dokunduğu kayıt sayısını derinliğe göre ölçer ve derinliğin önceden bilinmediği bir
soruda dolaşmanın bedelini sayar.

#### Bağın Kendisi Bir Kayıt

Model **özellikli çizgedir (property graph)**: veri düğümlerden ve kenarlardan oluşur, her
düğümün ve **her kenarın** kendi özellikleri vardır. Kütüphanede düğümler kitap ve üyedir;
ödünç bir satır değil, bir kenardır ve ödüncün tarihi ile süresi o kenarın üstünde durur.
Erişim yolu da farklıdır: bir düğümün komşuları o düğümün yanında tutulan bir listedir —
**komşuluk listesi (adjacency list)**. Bir düğümden komşularına yürümenin adı **dolaşmadır
(traversal)**; ağaç ve çizge veri yapılarında `gezinme` adıyla geçen kavramla aynı şeydir, bu
kursta `dolaşma` biçimi kullanılır.

**NS3:** veri 20.000 üye, 5.000 kitap ve üye başına 2–12 ödünçten üretilir; kitap seçimi çarpık
bir dağılımdan gelir, yani birkaç kitap çok, çoğu kitap az ödünç alınır. Üreteç kendi yazılır,
tohum kodda görünür. **NS4:** ödünç kenarı yönsüzdür, iki yönde de aynı kenardan yürünür.

```js
// cizge/model.mjs — ozellikli cizge: dugum ve kenar, her ikisinde ozellik. Komsuluk listesi
// gercek bir veri yapisidir; kenar ozelligi kenarin ustunde durur, ayri bir tabloda degil.
// Uretec disari acilir: sonraki bloklar ayni kumeyi kurar.
const TOHUM = 20240517, UYE = 20_000, KITAP = 5_000;
function* akis(t) { let x = t >>> 0; for (;;) { x = (Math.imul(x, 1103515245) + 12345) >>> 0; yield x / 4294967296; } }
export function oduncler() {                 // deterministik odunc listesi, tohum yukarida
  const r = akis(TOHUM), c = [];
  for (let u = 1; u <= UYE; u += 1) {
    const n = 2 + Math.floor(r.next().value * 11), gorulen = new Set();
    for (let j = 0; j < n; j += 1) {
      const p = r.next().value, k = 1 + Math.floor(KITAP * p * p);   // carpik populerlik
      if (gorulen.has(k)) continue; gorulen.add(k);
      c.push({ uye: u, kitap: k, gun: 1 + Math.floor(r.next().value * 364),
               sure: 3 + Math.floor(r.next().value * 40) });
    }
  }
  return c;
}

class OzellikliCizge {
  #dugum = new Map(); #komsu = new Map();
  dugum(id, ozellik) { this.#dugum.set(id, ozellik); this.#komsu.set(id, []); }
  kenar(a, b, ozellik) {                     // yonsuz: iki komsuluk listesine de yazilir
    this.#komsu.get(a).push({ hedef: b, ...ozellik });
    this.#komsu.get(b).push({ hedef: a, ...ozellik });
  }
  ozellik(id) { return this.#dugum.get(id); }
  komsular(id, sart) { const l = this.#komsu.get(id) ?? []; return sart ? l.filter(sart) : l; }
  derece(id) { return (this.#komsu.get(id) ?? []).length; }
}

if (import.meta.main) {                      // dogrudan calistirildiginda olcum basilir
  const g = new OzellikliCizge(), H = "kitap:2500", veri = oduncler();
  for (let k = 1; k <= KITAP; k += 1) g.dugum(`kitap:${k}`, { tur: "kitap", baslik: `Kitap ${k}`, yil: 1960 + (k % 65) });
  for (let u = 1; u <= UYE; u += 1) g.dugum(`uye:${u}`, { tur: "uye", ad: `Uye ${u}`, sube: 1 + (u % 9) });
  for (const o of veri) g.kenar(`uye:${o.uye}`, `kitap:${o.kitap}`, { tur: "odunc", gun: o.gun, sure: o.sure });

  let enYuksek = null, e = 0, toplam = 0;
  for (let k = 1; k <= KITAP; k += 1) { const d = g.derece(`kitap:${k}`); toplam += d;
    if (d > e) { e = d; enYuksek = `kitap:${k}`; } }
  console.log(`${KITAP + UYE} dugum (${KITAP} kitap + ${UYE} uye), ${veri.length} kenar (odunc)`);
  console.log(`ortalama derece: kitap ${(toplam / KITAP).toFixed(1)}, uye ${(veri.length / UYE).toFixed(1)}; ` +
    `en yuksek ${enYuksek} ${e}`);

  const hepsi = g.komsular(H), uzun = g.komsular(H, (k) => k.sure > 21);
  console.log(`\n${H} (${g.ozellik(H).baslik}, ${g.ozellik(H).yil}): komsuluk listesi ${hepsi.length} kenar, ` +
    `kenar ozelligiyle sure>21 olan ${uzun.length}`);
  console.log(`  ilk uc kenar: ${hepsi.slice(0, 3).map((k) => `${k.hedef}(gun ${k.gun}, sure ${k.sure})`).join(", ")}`);
}
```

```
25000 dugum (5000 kitap + 20000 uye), 139504 kenar (odunc)
ortalama derece: kitap 27.9, uye 7.0; en yuksek kitap:1 1825

kitap:2500 (Kitap 2500, 1990): komsuluk listesi 22 kenar, kenar ozelligiyle sure>21 olan 11
  ilk uc kenar: uye:202(gun 157, sure 19), uye:3845(gun 73, sure 25), uye:4394(gun 21, sure 11)
```

Sayılar **ölçüm** sınıfındadır. Kenar özelliği bir yan tabloda değil kenarın kendisindedir:
süresi 21 günü aşan ödünçler, komşuluk listesi üzerinde tek süzgeçle 22 kenardan 11'ine iniyor —
dolaşma sırasında kenar da bir koşul taşıyabilir. Derece dağılımı ise çarpıktır: kitap düğümünde
ortalama derece 27,9 iken en yüksek derece 1.825'tir; bu sayı dersin sonunda geri gelecek.

Burada henüz bir üstünlük yoktur. Bir kitabın ödünç alanlarını bulmak çizgede 22 kenar okumaktır;
ilişkisel modelde aynı iş, `kitap_id` üzerinde dizinli bir aramayla 22 satır okumaktır. **Birinci
derecede iki model aynı işi yapar.** Fark ikinci adımda başlar.

#### Aynı Soru, İki Model

Soru şudur: bir kitabı ödünç alan üyelerin ayrıca aldığı kitaplar — ve aynı sorunun daha derin
dereceleri. İlişkisel modelde bu, ödünç tablosunun kendisiyle art arda birleştirilmesidir ve her
derece bir birleştirme daha ekler; çizgede aynı soru komşuluk listesinde bir adım daha yürümektir.

Ölçü iki tarafta da **dokunulan ara kayıttır** ve birikimlidir: birleştirmede her katmanın
ürettiği satır, dolaşmada geçilen kenar. **NS5:** ilişkisel taraf `kitap_id` ve `uye_id`
üzerinde dizinlidir; birleştirme tam tarama yapmaz, ölçülen fark dizin yokluğundan gelmez.

```js
// cizge/iki-model.mjs — ayni soru iki modelde: iliskisel tarafta birlestirme tablosu
// (node:sqlite, iki dizinli), cizge tarafinda komsuluk listesinde dolasma. Dokunulan ara kayit
// iki tarafta da birikimli sayilir. Sayilarin hepsi kosumdan bagimsizdir.
import { DatabaseSync } from "node:sqlite";
import { oduncler } from "./model.mjs";      // ayni veri kumesi, ayni tohum
const H = 2500, veri = oduncler();

const db = new DatabaseSync(":memory:");
db.exec("CREATE TABLE odunc(uye_id INT, kitap_id INT, gun INT, sure INT)");
const ekle = db.prepare("INSERT INTO odunc VALUES(?,?,?,?)");
db.exec("BEGIN"); for (const o of veri) ekle.run(o.uye, o.kitap, o.gun, o.sure); db.exec("COMMIT");
db.exec("CREATE INDEX i_kitap ON odunc(kitap_id)"); db.exec("CREATE INDEX i_uye ON odunc(uye_id)");

function birlestirme(d) {                    // d katmanli birlestirme; COUNT(*) = d adimlik yol sayisi
  let sql = `SELECT COUNT(*) ara, COUNT(DISTINCT o${d}.${d % 2 ? "uye_id" : "kitap_id"}) tekil FROM odunc o1`;
  for (let i = 2; i <= d; i += 1)
    sql += i % 2 === 0 ? ` JOIN odunc o${i} ON o${i}.uye_id = o${i - 1}.uye_id`
                       : ` JOIN odunc o${i} ON o${i}.kitap_id = o${i - 1}.kitap_id`;
  return db.prepare(`${sql} WHERE o1.kitap_id = ?`).get(H);
}

const komsu = new Map();                     // komsuluk listesi: dugum -> komsu dizisi
const bagla = (a, b) => { if (!komsu.has(a)) komsu.set(a, []); komsu.get(a).push(b); };
for (const o of veri) { bagla(`k:${o.kitap}`, `u:${o.uye}`); bagla(`u:${o.uye}`, `k:${o.kitap}`); }

function dolasma(d) {                        // katman katman; her katman tekil dugum kumesidir
  let kat = new Set([`k:${H}`]), dugum = 0, kenar = 0;
  for (let i = 1; i <= d; i += 1) { const yeni = new Set();
    for (const v of kat) { dugum += 1; for (const w of komsu.get(v)) { kenar += 1; yeni.add(w); } }
    kat = yeni; }
  return { dugum, kenar, tekil: kat.size };
}

console.log(`${veri.length} odunc kaydi; baslangic kitap:${H}, derecesi ${komsu.get(`k:${H}`).length}\n`);
console.log("derinlik  birlestirme satiri   ziyaret edilen dugum   gecilen kenar   tekil sonuc");
let birikim = 0;
for (const d of [1, 2, 3, 4, 5]) {
  const b = birlestirme(d), c = dolasma(d); birikim += b.ara;
  if (b.tekil !== c.tekil) throw new Error(`sonuclar ayristi: ${b.tekil} != ${c.tekil}`);
  console.log(String(d).padStart(5) + String(birikim).padStart(19) + String(c.dugum).padStart(23) +
    String(c.kenar).padStart(16) + String(c.tekil).padStart(14));
}
let adim = new Set([H]), ara = 0;            // iliskisel tarafta adim adim tekillestirme
for (let i = 1; i <= 5; i += 1) {
  const alan = i % 2 ? "uye_id" : "kitap_id", giren = i % 2 ? "kitap_id" : "uye_id";
  const r = db.prepare(`SELECT ${alan} a FROM odunc WHERE ${giren} IN (${[...adim].map(() => "?").join(",")})`).all(...adim);
  ara += r.length; adim = new Set(r.map((x) => x.a));
}
console.log(`\nadim adim tekillestirilen birlestirme: ${ara} satir, ${adim.size} tekil sonuc, bes ayri ifade`);
```

```
139504 odunc kaydi; baslangic kitap:2500, derecesi 22

derinlik  birlestirme satiri   ziyaret edilen dugum   gecilen kenar   tekil sonuc
    1                 22                      1              22            22
    2                218                     23             218           166
    3              13200                    189           11219          8295
    4             122105                   8484           78082          4996
    5           14190839                  13480          217544         20000

adim adim tekillestirilen birlestirme: 217544 satir, 20000 tekil sonuc, bes ayri ifade
```

İki modelin sonucu her derinlikte birebir aynıdır; kod bunu her satırda sınıyor ve ayrışsaydı
hata verirdi. Aynı olmayan şey maliyettir.

**Birinci ve ikinci derecede iki taraf da aynı sayıya dokunuyor:** 22 ve 218. Ayrışma üçüncü
derecede başlıyor (13.200'e karşı 11.219), dördüncüde açılıyor (122.105'e karşı 78.082) ve
beşincide bambaşka bir ölçeğe geçiyor: **14.190.839 satıra karşı 217.544 kenar**, altmış beş
kat. Nedeni tek cümledir: **birleştirmenin bir katmanı yol sayar, dolaşmanın bir katmanı düğüm
sayar.** Bir kitaba yüz farklı yoldan varılıyorsa birleştirme o kitabın komşularını yüz kez
üretir; dolaşma o kitabı katmanın kümesine bir kez koyar ve komşularına bir kez bakar. Sonuçtaki
`DISTINCT` yalnızca en sonda temizlik yapar, ara satırları üretilmekten kurtarmaz.

Son satır sınırın nerede olduğunu söylüyor. İlişkisel motor bu işi dolaşma gibi de yapabilir:
her adımda ara sonucu tekilleştirip bir sonrakine yalnız tekil kümeyi vermek 217.544 satır
okuyor — **dolaşmanın geçtiği kenar sayısıyla birebir aynı**. Eksik olan veriyi tutma biçimi
değil, sorgunun biçimidir: sorgu tek ifade olmaktan çıkıp **beş ayrı ifadeye** bölünür ve her
yeni derece bir ifade daha ister. Çizge deposunun yaptığı tam olarak budur; farkı, sorguyu
yazanın yapmamasıdır. Veri Katmanı Ölçekleme kursunda aynı karşıtlık istek sayısı üzerinden
ölçülmüştü; buradaki ölçü dokunulan kayıttır.

Tablonun son sütunu ise dolaşmanın kendi sınırıdır: dördüncü derecede sonuç 4.996 kitap,
beşincide 20.000 üye — neredeyse bütün katalog ve bütün üye kütüğü. Sorgu, pahalılaşmadan önce
**anlamını yitiriyor**.

#### En Kısa Zincir

Bazı sorularda derinlik önceden bilinmez: iki üye arasındaki en kısa ortak okuma zinciri kaç
adımdır? Bu soru sabit sayıda birleştirmeyle yazılamaz, çünkü kaç birleştirme yazılacağı cevabın
kendisidir. Dolaşmanın iki biçimi vardır: tek uçtan yürümek ya da iki uçtan yürüyüp buluşmak.

```js
// cizge/zincir.mjs — iki uye arasindaki en kisa ortak okuma zinciri: derinlik onceden
// bilinmez. Dolasma tek uctan ve iki uctan yurutulur, ziyaret edilen dugum sayilir.
import { oduncler } from "./model.mjs";      // ayni veri kumesi, ayni tohum

const komsu = new Map();
const bagla = (a, b) => { if (!komsu.has(a)) komsu.set(a, []); komsu.get(a).push(b); };
for (const o of oduncler()) { bagla(`k:${o.kitap}`, `u:${o.uye}`); bagla(`u:${o.uye}`, `k:${o.kitap}`); }

function tekUctan(a, b) {                    // en kisa yolda bir dugume ikinci kez ugranmaz
  const uzak = new Map([[a, 0]]); let kat = [a], dugum = 0, kenar = 0, enCok = [null, 0];
  const dur = () => ({ uzunluk: uzak.get(b), dugum, kenar, enCok });
  while (kat.length) { const yeni = [];
    for (const v of kat) { dugum += 1; const d = komsu.get(v).length;
      if (d > enCok[1]) enCok = [v, d];
      for (const w of komsu.get(v)) { kenar += 1;
        if (uzak.has(w)) continue; uzak.set(w, uzak.get(v) + 1);
        if (w === b) return dur(); yeni.push(w); } }
    kat = yeni; }
  return null;
}
function ikiUctan(a, b) {                    // her turda kucuk olan cephe genisletilir
  const uA = new Map([[a, 0]]), uB = new Map([[b, 0]]);
  let cA = [a], cB = [b], dugum = 0, kenar = 0;
  while (cA.length && cB.length) {
    const ileri = cA.length <= cB.length;
    const kat = ileri ? cA : cB, bu = ileri ? uA : uB, karsi = ileri ? uB : uA, yeni = [];
    for (const v of kat) { dugum += 1;
      for (const w of komsu.get(v)) { kenar += 1;
        if (bu.has(w)) continue; bu.set(w, bu.get(v) + 1);
        if (karsi.has(w)) return { uzunluk: bu.get(w) + karsi.get(w), dugum, kenar }; yeni.push(w); } }
    if (ileri) cA = yeni; else cB = yeni; }
  return null;
}

console.log(`${"uye cifti".padEnd(22)}${"zincir".padStart(7)}${"tek uctan dugum".padStart(17)}${"iki uctan dugum".padStart(17)}${"kazanc".padStart(8)}`);
for (const [a, b] of [[7, 19_842], [1_337, 15_004], [42, 9_999]]) {
  const t = tekUctan(`u:${a}`, `u:${b}`), i = ikiUctan(`u:${a}`, `u:${b}`);
  if (t.uzunluk !== i.uzunluk) throw new Error(`uzunluklar ayristi: ${t.uzunluk} != ${i.uzunluk}`);
  console.log(`uye:${a} — uye:${b}`.padEnd(22) + String(t.uzunluk).padStart(7) +
    String(t.dugum).padStart(17) + String(i.dugum).padStart(17) +
    `${(t.dugum / i.dugum).toFixed(1)}x`.padStart(8));
}

const t7 = tekUctan("u:7", "u:19842");       // aramada en cok kenar katan dugum
const [ad, derece] = t7.enCok;
console.log(`\nilk aramada en cok kenar katan dugum ${ad}, derecesi ${derece}: gecilen ` +
  `${t7.kenar} kenarin %${(100 * derece / t7.kenar).toFixed(1)}'i tek bu dugumden gecti`);
```

```
uye cifti              zincir  tek uctan dugum  iki uctan dugum  kazanc
uye:7 — uye:19842           4              426               11   38.7x
uye:1337 — uye:15004        2                8                2    4.0x
uye:42 — uye:9999           4              227                7   32.4x

ilk aramada en cok kenar katan dugum k:3, derecesi 611: gecilen 9477 kenarin %6.4'i tek bu dugumden gecti
```

İki yürüyüş de aynı uzunluğu buluyor ve kod bunu her çiftte sınıyor. Ziyaret edilen düğüm ise
dört adımlık zincirlerde 426'ya karşı 11 ve 227'ye karşı 7 — otuz ila kırk kat. Neden yapısaldır:
her katman bir öncekinden ortalama derece katı büyür, dolayısıyla maliyet son katmandadır. İki
uçtan yürümek dört adımı iki artı iki yapar ve iki küçük katmanın toplamı bir büyük katmandan
küçüktür. Zincir iki adım olduğunda kazanç 4,0 kata iniyor: bölünecek bir derinlik yoktur.

Son satır dolaşmanın kendi bedelini gösteriyor. Aramada en çok kenar katan düğüm 611 dereceli
bir kitaptır ve geçilen 9.477 kenarın %6,4'ünü tek başına o üretmiştir. Derece dağılımının
çarpıklığı burada maliyete dönüşüyor: bu ailede erişim ucuzdur, ama **yüksek dereceli bir
düğümden geçen her dolaşma o düğümün derecesi kadar iş yapar** — katalogdaki en yüksek derece
1.825'tir ve bedeli o düğüme uğrandığı anda tek adımda ödenir. Çözüm modelin dışındadır: kenarın
türüne ya da özelliğine göre süzmek, ya da o düğümü dolaşmaya hiç sokmamak.

#### Özet

- Özellikli çizgede veri düğüm ve kenardır, özellik her ikisinin üstünde durur ve komşuluk
  listesi düğümün yanında tutulan gerçek bir veri yapısıdır: ödüncün süresi kenarın özelliğidir
  ve 22 kenardan 21 günü aşan 11'i tek süzgeçle ayrılıyor.
- Birinci ve ikinci derecede iki model aynı sayıya dokunuyor (22 ve 218); ayrışma üçüncü
  derecede başlıyor (13.200'e karşı 11.219), dördüncüde 122.105'e karşı 78.082 oluyor ve
  beşincide 14.190.839 satıra karşı 217.544 kenara çıkıyor — altmış beş kat.
- Fark veriyi tutma biçiminden değil sorgu biçiminden gelir: birleştirmenin katmanı yol sayar,
  dolaşmanınki düğüm sayar. Aynı iş ilişkisel tarafta adım adım tekilleştirilirse 217.544 satıra
  iner, ama sorgu tek ifade olmaktan çıkıp beş ayrı ifade olur.
- Derinliği önceden bilinmeyen sorular sabit sayıda birleştirmeyle yazılamaz. En kısa zincirde
  iki uçtan dolaşma tek uçtandan 38,7 ve 32,4 kat az düğüm ziyaret ediyor; zincir kısaldığında
  kazanç 4,0 kata düşüyor.
- Dolaşmanın bedeli derece dağılımına bağlıdır: geçilen 9.477 kenarın %6,4'ü 611 dereceli tek
  bir düğümden geçiyor, katalogdaki en yüksek derece ise 1.825'tir.

#### Sonraki Adım

Dört ailenin dördü de bir şeyi paylaşıyor: verinin bir kimliği var — anahtar, belge kimliği,
satır anahtarı, düğüm kimliği. Sorgu her durumda bir kimlikten başlıyor ya da bir kimliğe
varıyor. Kütüphanenin işletme verisinde ise kimliği olmayan bir akış vardır: her dakika ölçülen
raf sıcaklığı, saatlik kapı geçiş sayacı, ödünç işlemlerinin dakikalık sayımı. Tekil bir ölçüm
tek başına sorulmaz; sorular aralıklar üzerinedir, veri yalnız eklenir ve hiç güncellenmez,
eskiyen kısmı bir noktada tam çözünürlükte tutulmaz. Sonraki ders bu erişim örüntüsünün depoya
ne yaptığını ölçüyor: zamana göre yerleşimin aralık sorgusunda okuduğu blok, sıralı ölçümlerin
sıkıştırmada kazandırdığı yer ve saklama politikasının hangi soruyu cevaplanamaz hâle
getirdiği.

### Zaman Serisi Depoları

Source: https://academia.sh/tr/kurslar/nosql/zaman-serisi-depolari

Zaman damgalı ölçüm verisinin iki yerleşimde saklanmasının bedeli: satır başına tam damga ile bölüt başına taban damga, fark kodlaması ve blok sıkıştırmanın saklanan bayt farkı, aralık sorgusunun taradığı bölüt ve giriş sayısının aralık daraldıkça tersine dönmesi, saklama politikasının bölüt sınırına hizalandığında dokunulan girişi sıfıra indirmesi.

Buraya kadarki dört ailede kaydın kendisi bir varlıktı — bir kitap, bir üye, bir ödünç — ve en
son ele alınan model bağın kendisini birinci sınıf nesne yaptı. Kütüphanenin verisinin bir
bölümü ise ne varlıktır ne bağ: raf bölgelerinin sıcaklığı, turnikenin saydığı kişi, ödünç
sayacının değeri. Bunlar **zaman serisidir (time series)** — bir kaynağa ait, zaman damgalı,
sırayla gelen ölçümler.

Bu verinin yük profili öteki ailelerinkine benzemez. Yazma yalnız sona eklemedir; geçmiş bir
ölçüm neredeyse hiç güncellenmez. Okuma tekil kayda değil **aralığa** gelir. Ve verinin bir son
kullanma tarihi vardır: üç ay önceki dakikalık ölçüm hiçbir soruya girmez. Ders bu üç özelliğin
depo tarafındaki karşılığını ölçüyor: yerleşimin saklanan bayta etkisi, aralık sorgusunun
taradığı birim, saklama politikasının bedeli.

#### İki Yerleşim, Aynı Ölçümler

Ölçüm satırı küçüktür: bir kaynak kimliği, bir zaman damgası ve tek bir sayı — damga, yanında
taşıdığı değerden büyük olabilir. Zaman serisi deposunun ilk kararı buradan gelir: ölçümler tek
tek değil, **bölütler (segment)** hâlinde saklanır; her bölüt bir kaynağın bir zaman kutusudur,
**taban damgasını** bir kez yazar, geri kalanı fark olarak tutar.

**NS25:** on iki raf bölgesinde birer sıcaklık ölçeri vardır, her biri 60 saniyede bir ölçüm
yazar ve otuz günlük veri incelenir. **NS26:** satır yerleşiminde bir ölçüm 2 bayt kaynak
kimliği, 8 bayt zaman damgası ve 8 bayt değer taşır. **NS6:** bölüt birimi bir kaynak–gün
çiftidir.

```js
// zaman/yerlesim.mjs — ayni olcum dizisi iki yerlesimde seriliyor: (a) satir basina tam
// zaman damgasi + deger, (b) bolut basina taban damga + fark kodlamasi + blok sikistirma.
// Sayilan sey saklanan bayttir; sikistirma gercekten calistirilir, tohum gorunurdur.
import { deflateSync } from "node:zlib";
const SERI = 12, GUN = 30, ARALIK = 60_000, T0 = Date.UTC(2024, 5, 1);
const NOKTA = 86_400_000 / ARALIK;               // bolut basina giris: gunluk olcum sayisi
let tohum = 20240601;                            // gorunur tohum; uretec kendi yazildi
const gurultu = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648 - 0.5);
const olcum = (s, i) =>                          // ondalik derece cinsinden tamsayi: 18,4 -> 184
  Math.round((18 + s * 0.25 + 3 * Math.sin((i / NOKTA) * 2 * Math.PI) + gurultu() * 0.6) * 10);
const varint = (n) => { const b = []; let x = ((n << 1) ^ (n >> 31)) >>> 0;   // zigzag + varint
  do { let y = x & 0x7f; x >>>= 7; if (x) y |= 0x80; b.push(y); } while (x); return b; };

const satir = Buffer.alloc(SERI * GUN * NOKTA * 18);       // 2 bayt seri + 8 damga + 8 deger
let ofset = 0, hamBayt = 0, bolutBayt = 0, bolutSayisi = 0;
for (let s = 0; s < SERI; s += 1) for (let g = 0; g < GUN; g += 1) {
  const govde = [];                              // bolut govdesi: farklarin varinti
  let oncekiFark = 0, oncekiDeger = null;
  for (let i = 0; i < NOKTA; i += 1) {
    const t = T0 + g * 86_400_000 + i * ARALIK, d = olcum(s, g * NOKTA + i);
    satir.writeUInt16LE(s, ofset); satir.writeDoubleLE(t, ofset + 2);
    satir.writeDoubleLE(d / 10, ofset + 10); ofset += 18;
    if (oncekiDeger === null) { govde.push(...varint(d)); oncekiDeger = d; continue; }
    govde.push(...varint(ARALIK - oncekiFark)); oncekiFark = ARALIK;   // damga farkinin farki
    govde.push(...varint(d - oncekiDeger)); oncekiDeger = d;           // deger farki
  }
  const bas = Buffer.alloc(14);                  // taban damga + seri + giris sayisi
  bas.writeDoubleLE(T0 + g * 86_400_000, 0); bas.writeUInt16LE(s, 8); bas.writeUInt32LE(NOKTA, 10);
  hamBayt += bas.length + govde.length;
  bolutBayt += bas.length + deflateSync(Buffer.from(govde)).length;
  bolutSayisi += 1;
}

const N = SERI * GUN * NOKTA;
console.log(`${SERI} seri x ${GUN} gun x ${NOKTA} olcum = ${N} olcum; ` +
  `${bolutSayisi} bolut, bolut basina ${NOKTA} giris`);
console.log(`\n${"yerlesim".padEnd(34)}${"bayt".padStart(11)}${"olcum basina".padStart(14)}${"kat".padStart(7)}`);
for (const [ad, b] of [["satir basina tam damga + deger", satir.length],
    ["bolut, yalniz fark kodlamasi", hamBayt], ["bolut, fark + blok sikistirma", bolutBayt]])
  console.log(ad.padEnd(34) + String(b).padStart(11) + (b / N).toFixed(2).padStart(14) +
    (satir.length / b).toFixed(2).padStart(7));
```

```
12 seri x 30 gun x 1440 olcum = 518400 olcum; 360 bolut, bolut basina 1440 giris

yerlesim                                 bayt  olcum basina    kat
satir basina tam damga + deger        9331200         18.00   1.00
bolut, yalniz fark kodlamasi          1042560          2.01   8.95
bolut, fark + blok sikistirma          329013          0.63  28.36
```

Sayılar **ölçüm** sınıfındadır: iki yerleşim de gerçekten serildi, sıkıştırma gerçekten
çalıştırıldı.

Aynı 518.400 ölçüm satır yerleşiminde **9.331.200 bayt**, bölütlenmiş yerleşimde **329.013
bayttır**: ölçüm başına 18 bayta karşı **0,63 bayt**, yirmi sekiz kat. Ara satır kazancın nereden
geldiğini ayırıyor. Yalnız fark kodlaması, blok sıkıştırma olmadan, ölçüm başına 2,01 bayta
iniyor — dokuz kat. Nedeni verinin biçimidir: ölçüm aralığı sabit olduğu için ardışık damgaların
farkı hep aynıdır, farkın farkı sıfırdır ve tek baytla yazılır; değer farkı da küçük bir
tamsayıdır. Sekiz baytlık damga ve sekiz baytlık değer, iki bayta düşer. Blok sıkıştırma bunun
üstüne üç kat daha ekliyor: tekrarlayan sıfır ve küçük fark dizisi sıkıştırıcının en verimli
çalıştığı girdidir.

Bedel aynı kararın içindedir. Bölüt sıkıştırılmış bir bloktur; içindeki tek bir ölçüme bölütün
tamamı çözülmeden erişilemez — **erişimin birimi ölçüm değil, bölüttür**. Kazanç da verinin
sıralı ve düzenli aralıklı olmasına bağlıdır: ölçümler gecikmeli ve karışık sırada gelirse damga
farkının farkı sıfır olmaz ve bölüt şişer.

#### Zaman Aralığı Sorgusu

Bu ailenin sorguları hemen her zaman bir zaman aralığı taşır. Satır yerleşimi zamana göre sıralı
tek dizidir: aralığın başı ikili aramayla bulunur, ama pencere içindeki **bütün kaynakların**
ölçümleri okunur. Bölüt yerleşiminde üstveri (kaynak kimliği ve taban damga) sorguyla kesişmeyen
bölütleri hiç açmadan eler.

```js
// zaman/aralik.mjs — ayni olcum kumesi iki yerlesimde sorgulaniyor. Satir yerlesimi zamana
// gore sirali tek dizidir: aralik ikili aramayla bulunur, pencere icindeki butun seriler
// taranir. Bolut yerlesiminde ustveri (seri, taban damga) eleme yapar, ama acilan bolut
// butunuyle cozulur. Sayilar kosumdan bagimsizdir.
const SERI = 12, GUN = 30, ARALIK = 60_000, T0 = Date.UTC(2024, 5, 1), NOKTA = 1440;
const seri = [], damga = [];                     // satir yerlesimi: zamana gore sirali
for (let g = 0; g < GUN; g += 1) for (let i = 0; i < NOKTA; i += 1)
  for (let s = 0; s < SERI; s += 1) { seri.push(s); damga.push(T0 + g * 86_400_000 + i * ARALIK); }
const bolut = [];                                // bolut yerlesimi: bolut = (seri, gun)
for (let s = 0; s < SERI; s += 1) for (let g = 0; g < GUN; g += 1)
  bolut.push({ seri: s, taban: T0 + g * 86_400_000, son: T0 + (g + 1) * 86_400_000, giris: NOKTA });

function satirSorgu(s, alt, ust) {               // ikili arama + pencere taramasi
  let a = 0, b = damga.length;
  while (a < b) { const o = (a + b) >> 1; if (damga[o] < alt) a = o + 1; else b = o; }
  let taranan = 0, sonuc = 0;
  for (let i = a; i < damga.length && damga[i] < ust; i += 1) {
    taranan += 1; if (s === null || seri[i] === s) sonuc += 1; }
  return { birim: 0, taranan, sonuc };
}
function bolutSorgu(s, alt, ust) {               // ustveriyle eleme, sonra bolutu ac
  let acilan = 0, taranan = 0, sonuc = 0;
  for (const b of bolut) {
    if (s !== null && b.seri !== s) continue;
    if (b.son <= alt || b.taban >= ust) continue;
    acilan += 1; taranan += b.giris;             // bolut butunuyle cozulur
    for (let i = 0; i < b.giris; i += 1) { const t = b.taban + i * ARALIK;
      if (t >= alt && t < ust) sonuc += 1; }
  }
  return { birim: acilan, taranan, sonuc };
}

const G12 = T0 + 11 * 86_400_000;
const sorgular = [["3 nolu raf, 12 Haziran (tam gun)", 3, G12, G12 + 86_400_000],
  ["butun raflar, 12-14 Haziran", null, G12, G12 + 3 * 86_400_000],
  ["3 nolu raf, 12 Haziran 09:00-10:00", 3, G12 + 9 * 3_600_000, G12 + 10 * 3_600_000],
  ["3 nolu raf, butun ay", 3, T0, T0 + GUN * 86_400_000]];
console.log(`satir yerlesimi ${damga.length} giris; bolut yerlesimi ${bolut.length} bolut`);
console.log(`\n${"sorgu".padEnd(35)}${"satir: taranan".padStart(15)}${"bolut".padStart(7)}` +
  `${"bolut: taranan".padStart(15)}${"sonuc".padStart(8)}`);
for (const [ad, s, alt, ust] of sorgular) {
  const x = satirSorgu(s, alt, ust), y = bolutSorgu(s, alt, ust);
  if (x.sonuc !== y.sonuc) throw new Error("iki yol ayni sonucu vermedi");
  console.log(ad.padEnd(35) + String(x.taranan).padStart(15) + String(y.birim).padStart(7) +
    String(y.taranan).padStart(15) + String(x.sonuc).padStart(8));
}
```

```
satir yerlesimi 518400 giris; bolut yerlesimi 360 bolut

sorgu                               satir: taranan  bolut bolut: taranan   sonuc
3 nolu raf, 12 Haziran (tam gun)             17280      1           1440    1440
butun raflar, 12-14 Haziran                  51840     36          51840   51840
3 nolu raf, 12 Haziran 09:00-10:00             720      1           1440      60
3 nolu raf, butun ay                        518400     30          43200   43200
```

Dört satırın üçü bölüt yerleşiminin lehinedir; modelin sınırı dördüncüsündedir.

Tek bir rafın bir günü satır yerleşiminde **17.280 giriş** taratıyor, bölüt yerleşiminde **tek
bölüt** açılıyor ve 1.440 giriş okunuyor. Oran tam olarak kaynak sayısıdır: sıralı dizide bir
rafın ölçümleri on iki rafınkiyle iç içe durur ve aralık daraltılsa da o iç içelik kalır. Aynı
rafın bütün ayında katsayı korunuyor: 518.400 giriş taramaya karşı 30 bölüt ve 43.200 giriş.

İkinci satır bölütlemenin **hiçbir şey kazandırmadığı** durumu gösteriyor: sorgu bütün rafları
istediğinde eleyecek üstveri kalmaz, iki yol da 51.840 giriş okur ve bölüt yoluna yalnız 36
bölüt açma işi eklenir.

Üçüncü satır bedeli taşıyor. Bir saatlik pencere satır yerleşiminde **720 giriş** taratıyor;
bölüt yerleşiminde aynı soru **1.440 giriş** çözdürüyor, çünkü açılan bölüt bir günlüktür ve
sıkıştırılmış blok kısmen çözülemez. Sonuç iki yolda da 60 ölçümdür.
**Bölütün süresi, aralık sorgusunun en küçük okuma birimidir**: bölüt
büyüdükçe sıkıştırma kazancı artar, dar aralık sorgusunun israfı da artar. Dar aralıklar sık
soruluyorsa karşılığı ölçümü daha kaba çözünürlükte ikinci kez saklamaktır — bu kararın adı
**seyreltmedir** ve Vaka Çalışmaları kursunda ölçülmüştür.

#### Saklama Politikası

Zaman serisi verisinin ötekilerden ayrıldığı son nokta silinmesidir. Bir kitap kaydı silinmez;
bir dakikalık ölçüm belirli bir yaştan sonra hiçbir soruya girmez ve **saklama (retention)**
politikası onu düşürür. Soru bu düşürmenin neye mal olduğudur. **NS27:** saklama süresi yedi
gündür.

```js
// zaman/saklama.mjs — saklama politikasinin iki yerlesimde uygulanmasi. Satir yerlesiminde
// suresi dolan her kayit tek tek dusurulur. Bolut yerlesiminde tamami suresi dolmus bolut
// birim olarak dusurulur; yalniz siniri kesen bolut yeniden yazilir. Sayilar kosumdan
// bagimsizdir.
const SERI = 12, GUN = 30, ARALIK = 60_000, T0 = Date.UTC(2024, 5, 1), NOKTA = 1440;
const SIMDI = T0 + GUN * 86_400_000;
const damga = [];                                // satir yerlesimi: zamana gore sirali
for (let g = 0; g < GUN; g += 1) for (let i = 0; i < NOKTA; i += 1)
  for (let s = 0; s < SERI; s += 1) damga.push(T0 + g * 86_400_000 + i * ARALIK);
const bolut = [];
for (let s = 0; s < SERI; s += 1) for (let g = 0; g < GUN; g += 1)
  bolut.push({ taban: T0 + g * 86_400_000, son: T0 + (g + 1) * 86_400_000, giris: NOKTA });

function satirSaklama(sinir) {                   // her kayit tek tek dusurulur
  let dokunulan = 0, dusen = 0;
  for (const t of damga) { dokunulan += 1; if (t < sinir) dusen += 1; else break; }
  return { birim: "kayit", dokunulan, dusen, kalan: damga.length - dusen };
}
function bolutSaklama(sinir) {                   // ustveri okunur, bolut birim olarak duser
  let dokunulan = 0, dusen = 0, yeniden = 0;
  for (const b of bolut) {
    if (b.son <= sinir) { dusen += b.giris; continue; }          // hicbir giris acilmaz
    if (b.taban >= sinir) continue;
    dokunulan += b.giris;                                        // siniri kesen bolut
    for (let i = 0; i < b.giris; i += 1)
      if (b.taban + i * ARALIK < sinir) dusen += 1; else yeniden += 1;
  }
  return { birim: "bolut", dokunulan, dusen, kalan: SERI * GUN * NOKTA - dusen, yeniden };
}

console.log(`${damga.length} olcum, ${bolut.length} bolut, bolut basina ${NOKTA} giris`);
console.log(`\n${"saklama".padEnd(22)}${"yerlesim".padEnd(9)}${"dokunulan giris".padStart(16)}` +
  `${"dusen kayit".padStart(13)}${"kalan".padStart(8)}`);
for (const [ad, sure] of [["7 gun (bolut siniri)", 7 * 86_400_000],
    ["7 gun 12 saat", 7.5 * 86_400_000]]) {
  const sinir = SIMDI - sure;
  for (const r of [satirSaklama(sinir), bolutSaklama(sinir)])
    console.log(ad.padEnd(22) + r.birim.padEnd(9) + String(r.dokunulan).padStart(16) +
      String(r.dusen).padStart(13) + String(r.kalan).padStart(8));
}
const y = bolutSaklama(SIMDI - 7.5 * 86_400_000);
console.log(`\nsiniri kesen bolutler: ${y.dokunulan / NOKTA} bolut acildi, ` +
  `${y.yeniden} giris yeniden yazildi`);
```

```
518400 olcum, 360 bolut, bolut basina 1440 giris

saklama               yerlesim  dokunulan giris  dusen kayit   kalan
7 gun (bolut siniri)  kayit              397441       397440  120960
7 gun (bolut siniri)  bolut                   0       397440  120960
7 gun 12 saat         kayit              388801       388800  129600
7 gun 12 saat         bolut               17280       388800  129600

siniri kesen bolutler: 12 bolut acildi, 8640 giris yeniden yazildi
```

Birinci çift bölütlemenin en keskin kazancıdır. İki yerleşim de aynı **397.440 kaydı** düşürüp
arkasında aynı 120.960 kaydı bırakıyor, ama satır yerleşimi bunun için 397.441 girişe dokunuyor:
düşen her kayda bir kez, artı sınırı gösteren bir kayda daha. Bölüt yerleşiminde dokunulan giriş
**sıfırdır** — yalnız 360 bölüt üstverisi okunur ve süresi dolmuş 276 bölüt (397.440 ÷ 1.440,
**hesap**) dosya olarak düşürülür. Silme burada veri işlemi değil, dosya işlemidir.

Sıfır, saklama sınırının bölüt sınırına denk gelmesine bağlıdır. İkinci çift bunu bozuyor: yedi
buçuk günlük saklamada sınır bölütlerin ortasından geçer, on iki bölüt açılır, **17.280 girişe**
dokunulur ve bunun 8.640'ı bölütler yeniden yazıldığı için ikinci kez saklanır. Düşen kayıt
sayısı gene iki yolda aynıdır; değişen yalnız iş miktarıdır. Kural şudur: **saklama süresi bölüt
süresinin katı seçildiğinde silme bedeli sıfıra iner**; seçilmediğinde bedel, sınırı kesen bölüt
sayısı çarpı bölüt boyudur.

Bölüt süresi kararının üçüncü yüzü budur: bölüt büyüdükçe saklanan bayt düşer, dar aralık
sorgusunun israfı ve saklama sınırındaki yeniden yazma artar. Üçünü birden en iyileyen bir değer
yoktur.

#### Özet

- Zaman serisi verisi sona eklemeli yazılır, aralıkla okunur ve yaşlandığında düşürülür; depo bu
  üç özelliği veri modelinin içine alır.
- Satır başına tam damga ve değer 18 bayt tutarken, taban damga + fark kodlaması 2,01 bayta, blok
  sıkıştırma eklendiğinde 0,63 bayta iniyor — 9.331.200 bayta karşı 329.013 bayt, 28,4 kat.
  Kazanç verinin sıralı ve düzenli aralıklı olmasına bağlıdır; bedeli, erişim biriminin ölçüm
  değil bölüt olmasıdır.
- Bölüt üstverisi aralık sorgusunda eleme yapar: bir rafın bir günü 17.280 giriş taraması yerine
  tek bölüt ve 1.440 giriştir. Sorgu bütün kaynakları istediğinde eleme yoktur, iki yol da 51.840
  giriş okur.
- Bölüt süresi en küçük okuma birimidir: bir saatlik pencere satır yerleşiminde 720 giriş, bölüt
  yerleşiminde 1.440 giriş çözdürür ve sonuç 60 ölçümdür.
- Saklama sınırı bölüt sınırına denk geldiğinde 397.440 kayıt hiçbir girişe dokunulmadan, 276
  bölüt dosya olarak düşürülerek silinir; denk gelmediğinde 12 bölüt açılır, 17.280 girişe
  dokunulur, 8.640 giriş yeniden yazılır.

#### Sonraki Adım

Beş aile de aynı sırayla anlatıldı: modelin ayırt edici mekaniği, ucuzlattığı ve pahalılaştırdığı
sorgu sınıfları. Ortaya çıkan tablo bir sıralama değildir — her aile bir erişim örüntüsünü
ucuzlatmak için başkasını pahalılaştırdı ve her ölçümde ters yönlü satır da göründü.

Geriye karar kalıyor: eldeki iş için hangi aile. Sonraki ders bu soruyu tercihle değil ölçütle
karşılıyor — erişim örüntüsünün hangi özelliği hangi aileye işaret eder, aynı iş yükü iki ailede
koşturulduğunda fark hangi eşikte anlam kazanır ve bir aile yanlış seçildiğinde bedel nerede
görünür.

### Model Seçim Ölçütleri

Source: https://academia.sh/tr/kurslar/nosql/model-secim-olcutleri

Erişim örüntüsünden aileye giden yolun ölçütleri ve her ölçütün bağlandığı sayı: yedi örüntünün hangi ailede ne kadar ucuzladığı ve karşılığında hangi güvenceden vazgeçildiği, aynı beş işin beş ailede puanlanması ve iş karışımının oranı değiştiğinde kazananın hangi eşikte el değiştirdiği — dolaşma için üç yüz elli binde bir, zaman aralığı için beşte bir.

Beş aile de aynı sırayla ölçüldü ve ortaya bir sıralama değil bir dağılım çıktı. Geriye tek soru
kalıyor: eldeki iş için hangi aile. Bu ders o soruyu tercihle değil ölçütle karşılıyor.

Ölçütün işi, elde bulunan bilgiyi — bir uygulamanın hangi sorguları hangi sıklıkla sorduğunu —
bir aile adına çevirmektir. Bunun iki koşulu var. Birincisi, her ölçütün bir **sayıya** bağlı
olması: "duruma göre" cümlesi karar taşımaz, "derinlik üçün altında iki model aynı sayıya
dokunuyor" cümlesi taşır. İkincisi, ölçütlerin bir **karışım** üzerinde birlikte çalıştırılması:
tek bir sorgu için en ucuz aile ile bütün iş yükü için en ucuz aile aynı olmak zorunda değildir.
Ders önce ölçütleri sayılara bağlıyor, sonra beş işi beş ailede puanlayıp karışım oranı
değiştiğinde kazananın el değiştirdiği eşiği hesaplıyor.

#### Beş Ölçüt ve Bağlandıkları Sayı

**Erişim örüntüsü** en güçlü ölçüttür, ama gücü örüntüye göre değişir. Çizge dersinde birinci ve
ikinci derecede iki model **aynı** sayıya dokundu (22 ve 218); ayrışma üçüncü derecede 1,18 kat,
dördüncüde 1,56 kat, beşincide 65,2 kattı. Ölçüt buradan çıkar: ilişki dolaşması bir aile adına
işaret etmeye ancak derinlik üçten sonra başlar, ondan öncesi karar taşımaz.

**Sorgu yüzeyi genişliği**, kaç ayrı alanın süzgeç taşıdığıdır. Tek alan varsa — ve o alan
anahtarsa — anahtar sözleşmesi yeter. İkinci alan geldiği anda anahtar–değer dersinin ölçtüğü
fark açılır: cezası eşiği aşan 359 üye, ilişkisel dizinle 1 gidiş ve 5.903 bayt,
anahtar taramasıyla 20.000 gidiş ve 11,3 MB istiyor.

**Yazma/okuma oranı** ölçütü, okuma tarafında kazanılanı yazma tarafında ödenene bölerek çalışır.
Anahtar–değer dersi bütünsel değerde 73 baytlık bir eklemenin yazma çarpanını 407'ye çıkardığını,
ayrı anahtarda çarpanın 1 kaldığını ölçtü; okuma tarafında ise bütünsel değer 1 gidiş, ayrı
anahtar sıralı anahtar uzayında 2, karma anahtar uzayında 66 gidiş istiyor. Başabaş noktası
bölmeyle çıkar (**hesap**): sıralı anahtar uzayında bütünsel değer her **406 okumaya bir yazma**
oranına kadar kârda, karma anahtar uzayında **her 6,2 okumaya bir yazma** oranına kadar.

**Tutarlılık gereksinimi**, dizinin nerede tutulacağını belirler. Uygulamanın kendi tuttuğu
ikincil dizin gidişi 20.000'den 360'a indiriyor, ama 243.132 baytlık listeyi her sorguda bütün
halinde okutuyor — ilişkisel dizinin okuduğu 5.903 baytın 41,2 katı — ve kayıtla arasında
bir tutarsızlık penceresi bırakıyor.

**Şema değişim sıklığı** ölçütü, denetimin yazma anında mı okuma anında mı yapılacağını sorar.
Belge dersi üç şema sürümü yan yana duran bir derlemde dizinden yanıtlanan sorgunun 7.373 kaydı
sessizce kaçırdığını ölçtü; savunmalı okuma doğru yanıtı veriyor ama taranan girişi 18.456'dan
50.000'e, yani **2,71 kata** çıkarıyor. Şema sık değişiyorsa bu 2,71 kat sürekli ödenen bir
maliyettir.

#### Erişim Örüntüsünden Aileye

Tablonun her satırı bir erişim örüntüsüdür; sayılar beş dersin koşumlarından okundu.

| Erişim örüntüsü | Ucuzlatan aile | Ölçülen fark | Vazgeçilen güvence |
|---|---|---|---|
| Tek anahtarla nokta okuma | anahtar–değer | 1 gidiş, 1 kayıt; ilişkisel tarafta 2 gidiş ve 9 kayıt, tek birleştirmeyle 714 yerine 913 bayt | Alan üzerinde koşul, kısmi güncelleme ve birleştirme sözleşmeden düşer |
| İç içe yapının tümünü okuma | belge | 1 gidiş, 412 bayt; ilişkisel tarafta 4 gidiş ve 513 bayt, tek birleştirmede 60 satır ve 6.718 bayt | Yazma anındaki şema denetimi: üç sürümlü derlemde 7.373 kayıt sessizce kaçırılıyor |
| Geniş satırdan aralık dilimi | geniş sütun | 4.000 sütunluk satırdan bir ayın 333 ödüncü tek istekte; örüntüye uymayan bölüm anahtarıyla 263.867 sütun taranıyor | Bölüm anahtarı tek örüntüye hizmet eder; ikincisini karşılamak girdiyi 527.734'e çıkarır, yazma çarpanı 2,0 |
| İlişki dolaşma (derinlik ≥ 3) | çizge | Beşinci derecede 217.544 kenar; birleştirme 14.190.839 ara satır üretiyor — 65,2 kat | Tek ifadelik bildirimsel sorgu: aynı iş ilişkisel tarafta beş ayrı ifadeye bölünüyor |
| Zaman aralığı taraması | zaman serisi | Bir rafın bir günü tek bölüt ve 1.440 giriş; satır yerleşiminde 17.280 giriş | Erişim birimi ölçüm değil bölüttür: bir saatlik pencere 1.440 giriş çözdürüp 60 ölçüm veriyor |
| Alan altkümesi üzerinde tam tarama | geniş sütun | Dolaşım özeti aile yerleşiminde 46, satır yerleşiminde 1.867 sayfa okuyor — 40,6 kat | Tek kaydın bütün alanları 1 sayfa yerine 4 sayfa; yerleşim iki yönlü çalışıyor |
| İkincil alana göre süzme | belge (motorun tuttuğu dizin) | Anahtar–değer dersinde ilişkisel dizin eşiği aşan 359 üyeyi 1 gidiş ve 5.903 baytta buluyor; anahtar taraması 20.000 gidiş ve 11,3 MB | Dizin bir şema varsayar; sürüm karışıklığında iki dizin birleşimi 2 gidiş, savunmalı tarama 50.000 giriş |

Tablonun okunma yönü tek yönlüdür: soldan sağa. Elde bir aile adı varmış gibi başlanıp ona uyan
örüntü aranmaz; elde erişim örüntüsü vardır ve tablo onu bir aileye çevirir. Dördüncü sütun da
sonuca dahildir — her satırda kazanılan şeyin bedeli aynı satırda yazılıdır.

#### Aynı İş Kümesi, Beş Aile

Tablo tek tek örüntüler için karar veriyor. Gerçek bir uygulama ise tek örüntü değil, bir
**karışım** koşturur. Aşağıdaki düzenek kütüphane alanının beş işini beş ailede puanlıyor.

**NS30:** katalog 50.000 kitap ve 20.000 üye taşır, bir üyenin ödünç geçmişi geniş satırda 4.000
sütundur ve bir ayın dilimi 333 ödünçtür, on iki raf ölçeri 60 saniyede bir yazar. **NS31:**
puanlama birimi "dokunulan birim"dir ve bir sayfa, bir kayıt, bir sütun, bir kenar ve bir bölüt
girişi eşit sayılır; bir aile bir örüntünün mekaniğini taşımıyorsa o iş, o ailede alttaki
derlemin taranmasıdır. **NS32:** karışım payları toplamı 1'dir ve maliyet istek başına ağırlıklı
ortalamadır.

Yirmi beş hücrenin on sekizi beş dersin koşumlarından okundu (**ölçüm**), yedisi NS31 kuralıyla
dolduruldu (**varsayım**): nokta okumada belge ve çizge, iç içe okumada anahtar–değer, çizge ve
zaman serisi, aralık diliminde belge ve çizge. Ağırlıklı toplamlar ve eşikler **hesap**
sınıfındadır.

```js
// secim/puanlama.mjs — kutuphane alaninin bes isi bes model ailesinde puanlaniyor. Hucreler
// bes dersin kosumlarindan okundu; olculmemis hucreler NS31 kuraliyla dolduruldu. Birim
// "dokunulan birim"dir (kayit, sayfa, sutun, kenar, giris). Toplam agirlikli ortalamadir,
// esik ikili aramayla cozulur; girdi sabittir, cikti kosumdan bagimsizdir.
const AILE = ["a-deger", "belge", "g-sutun", "cizge", "z-serisi"];
const IS = [                                        // [ad, bes ailenin maliyeti]
  ["nokta okuma",          [1, 1, 4, 1, 1440]],     // 1 gidis / 4 sayfa / erisim birimi bolut
  ["ic ice okuma",         [1, 1, 4, 22, 1440]],    // belge 1 gidis; cizgede birinci derece
  ["aralik dilimi",        [263867, 4000, 333, 4000, 263867]],   // bolum anahtari uyuyor: 333
  ["dolasma (5. derece)",  [14190839, 14190839, 14190839, 217544, 14190839]],
  ["zaman araligi",        [17280, 17280, 17280, 17280, 1440]],  // bolut elemesi
];
const KARISIM = [                                   // paylar bir karisimda 1'e toplanir
  ["katalog agirlikli", [0.70, 0.29, 0.01, 0, 0]],
  ["gecmis agirlikli",  [0.30, 0.10, 0.58, 0, 0.02]],
  ["izleme agirlikli",  [0.15, 0.05, 0, 0, 0.80]],
];

const toplam = (k) => AILE.map((_, a) => IS.reduce((s, [, n], i) => s + k[i] * n[a], 0));
const enucuz = (t) => AILE.filter((_, a) => t[a] === Math.min(...t)).join("/");
function esik(taban, hedef) {                       // hedef isin payi p; kalan taban oraninda
  const kur = (p) => { const k = taban.map((w) => w * (1 - p)); k[hedef] += p; return k; };
  const ilk = enucuz(toplam(kur(0)));
  let a = 0, b = 1;
  for (let i = 0; i < 200; i += 1) {
    const p = (a + b) / 2;
    if (enucuz(toplam(kur(p))) === ilk) a = p; else b = p;
  }
  return { pay: (a + b) / 2, once: ilk, sonra: enucuz(toplam(kur(Math.min(1, b * 1.01)))) };
}
const satir = (ad, h, ek) => ad.padEnd(22) + h.map((v) => v.padStart(11)).join("") + "  " + ek;

console.log("matris — is basina dokunulan birim" +
  `\n${"is".padEnd(22)}${AILE.map((a) => a.padStart(11)).join("")}  ${"yayilim".padEnd(12)}en ucuz`);
for (const [ad, n] of IS)
  console.log(satir(ad, n.map(String),
    `${(Math.max(...n) / Math.min(...n)).toFixed(1) + " kat"}`.padEnd(12) + enucuz(n)));

console.log(`\nkarisim — istek basina agirlikli toplam` +
  `\n${"karisim".padEnd(22)}${AILE.map((a) => a.padStart(11)).join("")}  kazanan`);
for (const [ad, k] of KARISIM) {
  const t = toplam(k);
  console.log(satir(ad, t.map((v) => v.toFixed(2)), enucuz(t)));
}

console.log("\nesik — katalog karisimina tek bir is eklenince kazanan ne zaman degisiyor");
for (const [ad, i] of [["dolasma (5. derece)", 3], ["zaman araligi", 4]]) {
  const e = esik(KARISIM[0][1], i);
  console.log(ad.padEnd(22) + `pay ${(e.pay * 100).toFixed(6).padStart(9)} %` +
    `  (her ${String(Math.round(1 / e.pay)).padStart(6)} istekte bir)` +
    `  ${e.once} -> ${e.sonra}`);
}
```

```
matris — is basina dokunulan birim
is                        a-deger      belge    g-sutun      cizge   z-serisi  yayilim     en ucuz
nokta okuma                     1          1          4          1       1440  1440.0 kat  a-deger/belge/cizge
ic ice okuma                    1          1          4         22       1440  1440.0 kat  a-deger/belge
aralik dilimi              263867       4000        333       4000     263867  792.4 kat   g-sutun
dolasma (5. derece)      14190839   14190839   14190839     217544   14190839  65.2 kat    cizge
zaman araligi               17280      17280      17280      17280       1440  12.0 kat    z-serisi

karisim — istek basina agirlikli toplam
karisim                   a-deger      belge    g-sutun      cizge   z-serisi  kazanan
katalog agirlikli         2639.66      40.99       7.29      47.08    4064.27  g-sutun
gecmis agirlikli        153388.86    2666.00     540.34    2668.10  153647.66  g-sutun
izleme agirlikli         13824.20   13824.20   13824.80   13825.25    1440.00  z-serisi

esik — katalog karisimina tek bir is eklenince kazanan ne zaman degisiyor
dolasma (5. derece)   pay  0.000285 %  (her 351177 istekte bir)  g-sutun -> cizge
zaman araligi         pay 20.389929 %  (her      5 istekte bir)  g-sutun -> z-serisi
```

Matris satırlarının ilk okunuşu ölçütün gücünü veriyor. Nokta okuma ve iç içe okumada
anahtar–değer ile belge **berabere**: ikisi de 1 birime dokunuyor. Bu iki iş, aralarında seçim
yapmak için hiçbir bilgi taşımıyor — karar, okumanın değil yazmanın tarafında, yukarıdaki 406 ve
6,2 sayılarıyla veriliyor. Aynı satırda çizge de 1 birimdedir; ailenin ayırt ediciliği nokta
okumada değil, dördüncü satırdadır.

#### Kazananı Değiştiren Karışım

Karışım tablosu tek örüntü kararının nasıl bozulduğunu gösteriyor. Katalog ağırlıklı karışımda
kazanan **geniş sütun** (7,29 birim); onu belge 40,99 ile izliyor. Geçmiş ağırlıklı karışımda
kazanan yine geniş sütun ama arayı 540,34'e karşı 2.666,00 ile açıyor. İzleme ağırlıklı karışımda
sıralama tersine dönüyor: zaman serisi 1.440,00 birimle, geniş sütunun 13.824,80'inin **dokuz
katı** altında kalıyor. Üç karışım, iki farklı kazanan.

Eşik satırları asıl kararı taşıyor. Katalog ağırlıklı karışıma beşinci dereceden bir zincir
sorusu eklendiğinde kazanan, bu sorunun payı **yüzde 0,000285**'i geçtiğinde geniş sütundan
çizgeye geçiyor — **her 351.177 istekte bir**. Aynı karışıma zaman aralığı sorgusu eklendiğinde
eşik **yüzde 20,4**, yani her beş istekten biri. İki eşik arasında beş büyüklük mertebesi var.

Bunun nedeni matriste görünüyor: dolaşma satırının en pahalı hücresi 14.190.839, zaman aralığı
satırınınki 17.280. Bir işin karışımdaki payı ne kadar küçük olursa olsun, o iş yanlış ailede
milyonlarca birime mal oluyorsa toplamı tek başına belirler. Ölçüt şu biçimi alıyor: bir erişim
örüntüsünün payı değil, **payı ile yanlış ailedeki maliyetinin çarpımı** karar verir. Nadir ama
çok pahalı bir sorgu, sık ama ucuz bir sorgudan daha belirleyicidir.

#### Modelin Saymadıkları

Puanlama modeli, sınırları yazılmadan kullanılamaz. Üçü önemli.

Birincisi birim: NS31 bir sayfayı, bir kaydı ve bir bölüt girişini eşit sayıyor, oysa bunlar farklı
büyüklüklerdir. Bu yüzden model **büyüklük mertebesi** farkını taşır, sıralamanın kendisini
taşımaz — katalog karışımındaki 7,29'a karşı 40,99 farkı modelin hata payının içindedir, izleme
karışımındaki 1.440'a karşı 13.824 farkı değildir.

İkincisi yazma: model yalnız okumayı sayıyor. Geniş sütunun iki karışımda kazanması, bölüm
anahtarının tam da o karışımın erişim örüntüsüne göre seçilmiş olmasındandır. Geniş sütun dersi
ikinci bir örüntüyü de karşılamanın aynı veriyi ikinci kez yazmak olduğunu, girdinin 263.867'den
527.734'e çıktığını ve yazma çarpanının 2,0 olduğunu ölçtü. Model bu bedeli saymıyor; kazanç
orada geri ödeniyor.

Üçüncüsü, matrisin yedi varsayım hücresi ölçülmüş değil kuraldan türetilmiştir. Sonucu değiştiren
hücreler ölçüm sınıfındadır (dolaşma satırının tamamı, zaman aralığı satırının tamamı, aralık
diliminde geniş sütunun 333'ü), ama bir sonraki adım her zaman aynıdır: karar bir aileye
daralınca, o ailenin kendi düzeneğinde ölçüm tekrarlanır.

#### Özet

- Ölçüt bir sayıya bağlanmadıkça karar taşımaz: dolaşma ancak derinlik üçten sonra aile adına
  işaret ediyor (1,18 → 1,56 → 65,2 kat), bütünsel değer sıralı anahtar uzayında her 406 okumaya
  bir yazma oranına kadar kârda kalıyor, şema sürümlerinin yan yana durması doğru yanıtı 2,71 kat
  pahalılaştırıyor.
- Yedi erişim örüntüsünün her biri bir aileyi işaret ediyor ve her satırda kazanılan şeyin bedeli
  aynı satırda yazılı: nokta okuma anahtar–değer (alan üzerinde koşul yok), iç içe okuma belge
  (yazma anında denetim yok), aralık dilimi ve tam tarama geniş sütun (tek bölüm anahtarı, tek
  örüntü), dolaşma çizge (tek ifadelik sorgu yok), zaman aralığı zaman serisi (erişim birimi
  bölüt).
- Beş işin puanlanmasında nokta okuma ve iç içe okuma anahtar–değer ile belgeyi **berabere**
  bırakıyor (ikisi de 1 birim); bu iki iş aralarında seçim yapmak için bilgi taşımıyor, karar
  yazma tarafında veriliyor.
- Tek bir örüntü için en ucuz aile ile bütün karışım için en ucuz aile aynı değildir: katalog ve
  geçmiş ağırlıklı karışımlarda geniş sütun (7,29 ve 540,34), izleme ağırlıklı karışımda zaman
  serisi (1.440,00) kazanıyor.
- Kazananı payın kendisi değil, payın yanlış ailedeki maliyetle çarpımı belirliyor: dolaşma
  sorgusu her 351.177 istekte bir sorulduğunda kazanan değişiyor, zaman aralığı sorgusu ise ancak
  her beş istekten biri olduğunda — iki eşik arasında beş büyüklük mertebesi var.
- Puanlama modeli birimleri eşit saydığı ve yalnız okumayı ölçtüğü için büyüklük mertebesi
  farkını taşır, sıralamanın kendisini taşımaz; geniş sütunun kazancı, modelin saymadığı 2,0'lık
  yazma çarpanıyla geri ödenir.

#### Sonraki Adım

Ölçütler bir aileye işaret ediyor. Karar buraya kadar geldiğinde, örneğin katalog ve ödünç
verisinin belge modelinde tutulmasına, sorunun bittiği sanılabilir — oysa yeni başlıyor.

Aile seçmek şema seçmek değildir. Aynı kütüphane alanı belge modelinde birbirinden çok farklı
biçimlerde kurulabilir: ödünç kayıtları üye belgesinin içine gömülebilir ya da ayrı bir derlemde
referansla tutulabilir, kitabın nüshaları bir dizi olabilir ya da kendi belgeleri, tarih bir
metin ya da bir zaman tipi olabilir. Bu dersin puanlama matrisi "belge" sütununu tek bir sayı
olarak yazdı; gerçekte o sütun, seçilen şemaya göre kat kat değişen bir aralıktır. Bu aralığın
genişliği henüz ölçülmedi. Sonraki konu belge modelinin içine giriyor ve önce en alttan
başlıyor: bir belgenin alanları hangi tipleri taşır, bu tipler diskte nasıl gösterilir ve tip
seçimi saklanan bayta ne yapar.

## Belge Modeliyle Çalışma

Belge modelinin günlük kararları: ikili gösterim ile tip sisteminin sonuçları, gömme ile referans arasındaki kararın okuma ve yazma yollarına etkisi, karşılaştırma, mantıksal ve dizi işleçleri, aşamalı dönüşüm ile gruplama yapan toplama boru hattı, tek alan, bileşik ve özel dizinlerin seçimi, esnek şemaya kural eklemenin bedeli ve çok belgeli işlemlerin kapsamı.

### Belge Veri Tipleri

Source: https://academia.sh/tr/kurslar/nosql/belge-veri-tipleri

Belgenin kendi alan adlarını ve tip etiketlerini taşımasının bayt cinsinden bedeli: aynı 20.000 kitap kaydının açık adlı, kısa adlı ve sabit şemalı üç gösterimde kapladığı yer, kendini tanımlamanın payı ve aynı alanın iki tipte yazılmasının aralık sorgusunda ürettiği fark.

Önceki konu seçim ölçütleriyle kapandı ve arkasında tamamlanmamış bir karar bıraktı:
erişim örüntüsü bir aileye işaret ettiğinde iş bitmiyor. Aile seçmek şema seçmek
değildir. Belge modeli aynı alan için birbirinden çok farklı şemalar kurmaya izin verir
ve o izin bedelsiz değildir.

Bu konu o bedeli sayar; ilk soru en alttan başlar. İlişkisel bir tabloda alanın adı ve
tipi tablo tanımında bir kez yazılır, satırda yalnız değer durur. Belge modelinde böyle
bir tanım yoktur: her kayıt kendi alan adlarını ve tiplerini kendisi taşır. Bu dersin iki
ölçüsü budur — o taşımanın bayt cinsinden tuttuğu yer ve aynı alanın iki farklı tipte
yazılabilmesinin bir aralık sorgusunda ürettiği fark.

#### Kendini Tanımlayan Kayıt

**Belge** (document), adlandırılmış alanların sıralı listesidir; bir alanın değeri sayı
ya da metin olabildiği gibi iç içe belge veya dizi de olabilir. Belgeler bir **derlem**
(collection) içinde durur. Depo bu yapıyı **ikili belge gösterimiyle** (binary document
representation) saklar: her alan bir **tip etiketiyle** (type tag) başlar, adını
sonlandırıcı bayta kadar yazar, değeri tipin gerektirdiği biçimde koyar. Bu gösterime
**kendini tanımlayan** denir; kaydı çözmek için dışarıdan bir şema gerekmez.

Gösterimin üç kuralı vardır. Sabit boylu tipler (tam sayı, ondalık, mantıksal, zaman)
uzunluk taşımaz, boyu etiketinden bilinir. Değişken boylu tipler (metin, iç içe belge,
dizi) kendi uzunluklarını dört baytlık bir ön ekle yazar; böylece okuyucu ilgilenmediği
bir alanı çözmeden atlayabilir. Dizi, alan adları `"0"`, `"1"` diye giden bir iç
belgedir — dizinin öge sayısı da bu adlar üzerinden taşınır.

```js
// ikilibelge.mjs — kendini tanimlayan ikili belge gosterimi.
// Kodlama: [4 bayt uzunluk] (tip etiketi + alan adi + NUL + deger)* [0x00]
export const ETIKET = { bos: 0x0a, tam32: 0x10, ondalik: 0x01, metin: 0x02,
  belge: 0x03, dizi: 0x04, mantik: 0x08, zaman: 0x09 };
const SIRA = ["bos", "tam32", "ondalik", "metin", "belge", "dizi", "mantik", "zaman"];

export function tipAdi(d) {
  if (d === null) return "bos";
  if (Array.isArray(d)) return "dizi";
  if (d instanceof Date) return "zaman";
  if (typeof d === "boolean") return "mantik";
  if (typeof d === "number") return Number.isInteger(d) ? "tam32" : "ondalik";
  return typeof d === "string" ? "metin" : "belge";
}

export function kodla(belge, s = { ad: 0, etiket: 0, uzunluk: 0, deger: 0 }) {
  const parca = [];
  for (const [ad, d] of Object.entries(belge)) {
    parca.push(Buffer.from([ETIKET[tipAdi(d)]]), Buffer.from(ad + "\0", "utf8"), deger(d, s));
    s.etiket += 1;
    s.ad += Buffer.byteLength(ad) + 1;
  }
  const govde = Buffer.concat([...parca, Buffer.from([0])]);
  const bas = Buffer.alloc(4);
  bas.writeInt32LE(govde.length + 4);
  s.uzunluk += 4;
  s.deger += 1;                                  // kapanis NUL
  return Buffer.concat([bas, govde]);
}

function deger(d, s) {
  const t = tipAdi(d), n = { bos: 0, mantik: 1, tam32: 4, ondalik: 8, zaman: 8 }[t];
  if (n !== undefined) {
    s.deger += n;
    const b = Buffer.alloc(n);
    if (t === "mantik") b[0] = d ? 1 : 0;
    if (t === "tam32") b.writeInt32LE(d);
    if (t === "ondalik") b.writeDoubleLE(d);
    if (t === "zaman") b.writeBigInt64LE(BigInt(d.getTime()));
    return b;
  }
  if (t === "metin") {
    const m = Buffer.from(d + "\0", "utf8"), b = Buffer.alloc(4);
    b.writeInt32LE(m.length);
    s.uzunluk += 4;
    s.deger += m.length;
    return Buffer.concat([b, m]);
  }
  return kodla(t === "dizi" ? { ...d } : d, s);   // dizi: alan adlari "0","1",...
}

export function coz(t, konum = 0) {
  const son = konum + t.readInt32LE(konum), cikan = {};
  let i = konum + 4;
  while (i < son - 1) {
    const et = t[i], kes = t.indexOf(0, i + 1), ad = t.toString("utf8", i + 1, kes);
    i = kes + 1;
    if (et === ETIKET.bos) cikan[ad] = null;
    else if (et === ETIKET.mantik) cikan[ad] = t[i++] === 1;
    else if (et === ETIKET.tam32) { cikan[ad] = t.readInt32LE(i); i += 4; }
    else if (et === ETIKET.ondalik) { cikan[ad] = t.readDoubleLE(i); i += 8; }
    else if (et === ETIKET.zaman) { cikan[ad] = new Date(Number(t.readBigInt64LE(i))); i += 8; }
    else if (et === ETIKET.metin) {
      const n = t.readInt32LE(i);
      cikan[ad] = t.toString("utf8", i + 4, i + 3 + n);
      i += 4 + n;
    } else {
      const ic = coz(t, i);
      cikan[ad] = et === ETIKET.dizi ? Object.values(ic) : ic;
      i += t.readInt32LE(i);
    }
  }
  return cikan;
}

// Siralama tipe, sonra degere bakar: tip sirasi.
export function kiyas(a, b) {
  const f = SIRA.indexOf(tipAdi(a)) - SIRA.indexOf(tipAdi(b));
  if (f !== 0) return f;
  if (a === null) return 0;
  if (a instanceof Date) return a.getTime() - b.getTime();
  return a < b ? -1 : a > b ? 1 : 0;
}
```

#### Adın Bayt Bedeli

Ölçüm, önceki kursların kütüphane katalogunu belge biçiminde kurar. **NS7 (varsayım):**
katalogda 20.000 kitap belgesi vardır; her kitabın 1–5 kopyası ve 2–4 etiketi bulunur,
üretim tohumu 424242'dir. Aynı veri üç kararla kodlanır: alan adları açık yazılır, alan
adları kısaltılır, ya da adlar ve tipler kayıttan çıkarılıp sabit bir şemaya taşınır.
Üçüncüsü ilişkisel satırın yaptığı şeydir ve burada karşılaştırma tabanı olarak durur.

```js
// ad-maliyeti.mjs — ayni 20.000 kitap belgesi iki semada kodlanir: acik adlar ve kisa
// adlar. Ayni veri, ucuncu bir karar olarak sabit semali satirda da kodlanir.
// Ayni dizinde ikilibelge.mjs bulunur.
import { kodla, coz } from "./ikilibelge.mjs";

let cekirdek = 424242;                                  // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;

const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"];

function kitaplar(n) {
  const cikan = [];
  for (let i = 1; i <= n; i += 1) {
    const kopyaSayisi = 1 + Math.floor(rast() * 5);
    const kopya = [];
    for (let j = 0; j < kopyaSayisi; j += 1)
      kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
        sube: SUBE[Math.floor(rast() * SUBE.length)],
        durum: DURUM[Math.floor(rast() * DURUM.length)] });
    const etiketSayisi = 2 + Math.floor(rast() * 3);
    const etiket = [];
    for (let j = 0; j < etiketSayisi; j += 1) etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
    cikan.push({ anahtar: `K-${String(i).padStart(5, "0")}`,
      baslik: `Kitap ${i} uzerine incelemeler`, yazar: `Yazar ${i % 4000}`,
      yayin_yili: 1950 + (i % 75), etiket, kopya });
  }
  return cikan;
}

const KISA = { anahtar: "_k", baslik: "b", yazar: "y", yayin_yili: "yy",
  etiket: "e", kopya: "k", barkod: "bk", sube: "s", durum: "d" };
const kisalt = (b) => Object.fromEntries(Object.entries(b).map(([a, d]) => [KISA[a] ?? a,
  Array.isArray(d) ? d.map((x) => (typeof x === "object" ? kisalt(x) : x)) : d]));

const KAYIT = kitaplar(20000);
const olc = (kume) => {
  const s = { etiket: 0, ad: 0, uzunluk: 0, deger: 0 };
  let bayt = 0;
  for (const b of kume) bayt += kodla(b, s).length;
  return { bayt, ...s };
};
const acik = olc(KAYIT), kisa = olc(KAYIT.map(kisalt));

// Sabit semali satir: tip ve alan adi semada bir kez durur, kayitta yalniz deger vardir.
function sabitSema(kume) {
  let bayt = 0;
  for (const b of kume) {
    bayt += 4;                                          // yayin_yili: tam32
    for (const alan of ["anahtar", "baslik", "yazar"]) bayt += 2 + Buffer.byteLength(b[alan]);
    bayt += 2 + b.etiket.reduce((t, e) => t + 1 + Buffer.byteLength(e), 0);
    bayt += 2;
    for (const k of b.kopya)
      bayt += 2 + Buffer.byteLength(k.barkod) + 1 + Buffer.byteLength(k.sube)
        + 1 + Buffer.byteLength(k.durum);
  }
  return bayt;
}
const sabit = sabitSema(KAYIT);
const alan = KAYIT.reduce((t, b) => t + 6 + b.etiket.length + b.kopya.length * 4, 0);

console.log(`belge sayisi                 ${KAYIT.length}`);
console.log(`toplam alan (ic ice dahil)   ${alan}`);
console.log(`acik adli sema      ${acik.bayt} bayt   belge basina ${(acik.bayt / 20000).toFixed(1)}`);
console.log(`kisa adli sema      ${kisa.bayt} bayt   belge basina ${(kisa.bayt / 20000).toFixed(1)}`);
console.log(`sabit semali satir  ${sabit} bayt   belge basina ${(sabit / 20000).toFixed(1)}`);
console.log(`acik semada bayt dagilimi: ad ${acik.ad}  tip etiketi ${acik.etiket}  ` +
  `uzunluk alani ${acik.uzunluk}  deger ${acik.deger}`);
console.log(`kendini tanimlama payi  acik %${(100 * (acik.bayt - sabit) / acik.bayt).toFixed(1)}` +
  `   kisa %${(100 * (kisa.bayt - sabit) / kisa.bayt).toFixed(1)}`);

const tur = kodla(KAYIT[0]);
console.log(`ilk belge ${tur.length} bayt, cozuldugunde ilk kopya: ` +
  JSON.stringify(coz(tur).kopya[0]));
```

```
belge sayisi                 20000
toplam alan (ic ice dahil)   417706
acik adli sema      7357008 bayt   belge basina 367.9
kisa adli sema      6082574 bayt   belge basina 304.1
sabit semali satir  3139138 bayt   belge basina 157.0
acik semada bayt dagilimi: ad 2209340  tip etiketi 417706  uzunluk alani 1670824  deger 3059138
kendini tanimlama payi  acik %57.3   kisa %48.4
ilk belge 492 bayt, cozuldugunde ilk kopya: {"barkod":"B0000010","sube":"Beyoglu","durum":"rafta"}
```

Ölçüm 417.706 alan üzerinden yapılır — bir kitap belgesi ortalama 20,9 alan taşır, çünkü
her kopya ve her etiket kendi alanıdır. Açık adlı gösterim 7.357.008 bayt tutar, sabit
şemalı satır 3.139.138 bayt. Fark 4.217.870 bayttır ve bunun tamamı verinin kendisi
değil, verinin kendini anlatmasıdır: 2.209.340 bayt alan adlarına, 417.706 bayt tip
etiketlerine, 1.670.824 bayt uzunluk ön eklerine gider. Hesap: açık gösterimde saklanan
her üç bayttan yaklaşık ikisi (%57,3) veri değil tanımdır.

Kısaltma bu payı %48,4'e düşürür. Aradaki 1.274.434 bayt yalnız alan adlarının
kısalmasından gelir; belge başına 63,7 bayt, ölçekle çarpıldığında 20.000 belgede 1,2 MB
civarı. Kararın öteki yüzü de sayılabilir: kısa adlı belge kendi başına okunduğunda `bk`
alanının barkod olduğunu söylemez, o bilgi artık depoda değil uygulamadadır. Kendini
tanımlama, tam da bu bilginin kayıtta durmasıdır ve bedeli ölçülmüş durumdadır.

#### Aynı Alan İki Tipte

Kayıtta tip taşımanın karşılığı, aynı alanın iki belgede iki farklı tipte olabilmesidir.
İlişkisel şema bunu tanım anında engeller; belge modelinde engel yoktur ve engelin
yokluğu sorgu tarafında ortaya çıkar. **NS8 (varsayım):** dış kaynaktan alınan kayıtların
sekizde birinde yayın yılı metin olarak yazılmıştır — `2010` yerine `"2010"`.

Bir aralık sorgusunun bu belgelere ne yapacağı, işlecin semantiğine bağlıdır ve iki
seçenek vardır. **Tip sırası** (type ordering), farklı tipteki değerleri de karşılaştırır:
önce tipler, tip eşitse değerler sıralanır. **Tip parantezi** (type bracketing) ise
karşılaştırmayı yalnız işlecin sağ tarafıyla aynı tipteki değerlere uygular, ötekiler
eşleşmez.

```js
// tip-karisikligi.mjs — ayni alan iki tipte: yayin_yili kayitlarin bir bolumunde tam
// sayi, bir bolumunde metin. Ayni araligi iki islec semantigi ile sayar.
// Ayni dizinde ikilibelge.mjs bulunur.
import { kiyas, tipAdi, kodla } from "./ikilibelge.mjs";

const N = 20000;
const kume = [];
for (let i = 1; i <= N; i += 1) {
  const yil = 1950 + (i % 75);
  kume.push({ anahtar: `K-${String(i).padStart(5, "0")}`,
    yayin_yili: i % 8 === 0 ? String(yil) : yil });      // her sekizinci belge metin yazar
}
const metinli = kume.filter((b) => tipAdi(b.yayin_yili) === "metin").length;

// A karari: karsilastirma tip sirasini kullanir (siralamadaki toplam duzen).
const A = (b, d, yon) => (yon === "ge" ? kiyas(b.yayin_yili, d) >= 0 : kiyas(b.yayin_yili, d) < 0);
// B karari: karsilastirma tip parantezi uygular, farkli tipteki deger eslesmez.
const B = (b, d, yon) => tipAdi(b.yayin_yili) === tipAdi(d) && A(b, d, yon);
const say = (f, d, yon) => kume.filter((b) => f(b, d, yon)).length;

console.log(`belge ${N}, yayin_yili metin olan ${metinli} (%${(100 * metinli / N).toFixed(1)})`);
console.log(`A tip sirasi     >= 2010 -> ${say(A, 2010, "ge")}   < 2010 -> ${say(A, 2010, "lt")}` +
  `   toplam ${say(A, 2010, "ge") + say(A, 2010, "lt")}`);
console.log(`B tip parantezi  >= 2010 -> ${say(B, 2010, "ge")}   < 2010 -> ${say(B, 2010, "lt")}` +
  `   toplam ${say(B, 2010, "ge") + say(B, 2010, "lt")}`);
console.log(`tek tipli sema   >= 2010 -> ` +
  `${kume.filter((b) => Number(b.yayin_yili) >= 2010).length}`);
console.log(`B ile hicbir yana dusmeyen belge ${N - say(B, 2010, "ge") - say(B, 2010, "lt")}`);
console.log(`B, operand metin olursa: >= "2010" -> ${say(B, "2010", "ge")}`);

// Siralama her zaman toplam duzeni kullanir: sonuc tipe gore obeklenir.
const dizili = [...kume].sort((a, b) => kiyas(a.yayin_yili, b.yayin_yili));
const gecis = dizili.findIndex((b) => tipAdi(b.yayin_yili) === "metin");
console.log(`siralamada ilk metin ${gecis}. sirada ${JSON.stringify(dizili[gecis].yayin_yili)}` +
  `, bir oncesi ${JSON.stringify(dizili[gecis - 1].yayin_yili)}`);

const bayt = (d) => kodla({ yayin_yili: d }).length;
console.log(`tek alanli belge: tam sayi ${bayt(2010)} bayt, metin ${bayt("2010")} bayt`);
```

```
belge 20000, yayin_yili metin olan 2500 (%12.5)
A tip sirasi     >= 2010 -> 5991   < 2010 -> 14009   toplam 20000
B tip parantezi  >= 2010 -> 3491   < 2010 -> 14009   toplam 17500
tek tipli sema   >= 2010 -> 3990
B ile hicbir yana dusmeyen belge 2500
B, operand metin olursa: >= "2010" -> 499
siralamada ilk metin 17500. sirada "1950", bir oncesi 2024
tek alanli belge: tam sayi 21 bayt, metin 26 bayt
```

Doğru yanıt 3.990'dır: alanın tamamı tam sayı olsaydı 2010 ve sonrası için bu kadar belge
dönerdi. İki semantik de bu sayıyı vermez, ama farklı biçimde vermez. Tip sırası 5.991
döndürür; fazlalık 2.001 belgenin tamamı metin yazılmış kayıtlardır ve içlerinde 1950
yılına ait olanlar da vardır — metin tipi sırada tam sayıdan sonra geldiği için her metin
değer her tam sayıdan büyük sayılır. Tip parantezi 3.491 döndürür; eksik 499 belge, yılı
metin yazılmış ve gerçekte 2010 ya da sonrası olan kayıtlardır.

Tip parantezinin daha sinsi sonucu son satırda görünür. `>= 2010` ile `< 2010` bir
aralığın iki yarısıdır ve toplamları 20.000 olmalıdır; ölçüm 17.500 verir. 2.500 belge iki
sorgunun hiçbirine düşmez. Bu belgeler bir raporda eksik, ters sorguda da eksik görünür;
hiçbir yanıt onları göstermediği için hata fark edilmez. Sıralama ise her zaman toplam
düzeni kullanır: 17.500. sıraya kadar tam sayılar, oradan sonra metinler gelir, yani
`"1950"` değeri `2024` değerinden sonraya düşer.

Bayt tarafı da aynı yöne işaret eder: tek alanlı bir belge yıl tam sayı olduğunda 21,
metin olduğunda 26 bayt tutar. 2.500 belgede fark 12.500 bayttır — küçük bir sayı, ama
tipin belgede taşınması yalnız yer değil, anlam da ayırır. Ders kuralı şudur: belge modeli
alanın tipini yazma anında bağlamaz, bu yüzden tipi bağlama işi ya yazan uygulamaya ya da
sonradan konacak bir kurala kalır. Bu kuralın nasıl konacağı ve neyi yakalayıp neyi
kaçırdığı bu konunun altıncı dersinin sorusudur.

#### Özet

- İkili belge gösterimi her alanı tip etiketi, alan adı ve gerekiyorsa uzunluk ön ekiyle
  saklar; kayıt kendini tanımlar, çözmek için dış şema gerekmez.
- Aynı 20.000 kitap belgesi açık adlarla 7.357.008, kısa adlarla 6.082.574, sabit şemalı
  satırda 3.139.138 bayt tutar; açık gösterimde baytın %57,3'ü veri değil tanımdır.
- Alan adlarını kısaltmak 1.274.434 bayt (belge başına 63,7 bayt) kazandırır ve alanın ne
  olduğu bilgisini depodan uygulamaya taşır.
- Aynı alanın iki tipte yazılması aralık sorgusunu bozar: tip sırası 3.990 yerine 5.991,
  tip parantezi 3.491 belge döndürür.
- Tip parantezinde `>= 2010` ile `< 2010` toplamı 20.000 değil 17.500'dür; 2.500 belge iki
  yanıtın hiçbirinde görünmez.

#### Sonraki Adım

Bu ders tek bir belgenin içine baktı ve alanın adıyla tipinin bedelini saydı. Katalog ise
tek bir belgeden ibaret değil: kitap, kopya, üye ve ödünç kayıtları birbirine bağlıdır ve
belge modeli bu bağı iki farklı yoldan kurmaya izin verir — ilgili veriyi belgenin içine
gömmek ya da ayrı bir derlemde tutup anahtarıyla göstermek. İkisi de aynı katalogu
saklar, ama aynı okumayı farklı sayıda gidişle karşılar, aynı güncellemede farklı sayıda
belgeyi yeniden yazar ve belge boyutu sınırına farklı hızda yaklaşır. Sonraki ders bu iki
kararı aynı iş üstünde koşturup farkı sayar.

### Gömme ve Referans

Source: https://academia.sh/tr/kurslar/nosql/gomme-ve-referans

Aynı katalogun kopyaları gömülü ve referanslı iki şemada kurulması ve üç işin sayılması: kitap sayfasının gidiş sayısı, şube sorgusunun okuduğu bayt, beş bin durum değişikliğinin yazdığı bayt ve ödünç geçmişi gömüldüğünde belge boyutu sınırına dayanan kitap sayısı.

Önceki ders tek bir belgenin içine baktı: alan adının ve tip etiketinin bayt bedeli
sayıldı, aynı alanın iki tipte yazılmasının aralık sorgusunda 3.990 yerine 3.491 ya da
5.991 belge döndürdüğü görüldü. Katalog ise tek bir belgeden ibaret değildir. Kitap,
kopya, üye ve ödünç kayıtları birbirine bağlıdır ve belge modeli bu bağı iki yoldan
kurmaya izin verir.

**Gömme** (embedding), ilgili veriyi belgenin içine, iç içe bir belge ya da dizi olarak
koymaktır. **Referans** (referencing), veriyi ayrı bir derlemde tutup belgede yalnız
anahtarını taşımaktır. İkisi de aynı katalogu saklar; ayrıldıkları yer okuma yolunda kaç
gidiş yapıldığı, ne kadar bayt okunduğu, bir güncellemede kaç belgenin yeniden yazıldığı
ve belgenin ne kadar büyüdüğüdür. Bu ders o dört sayıyı üretir. Normalleştirme ile
denormalizasyonun aritmetiği Veri Modelleme ve İlişkisel Kuram ile Veri Katmanı Ölçekleme
kurslarında ölçüldü; burada tekrarlanmaz, ölçülen şey belge modeline özgü olan yandır.

#### Sayan Bir Depo

Ölçüm için küçük bir belge deposu yazılır. Depo derlemleri tutar, anahtarla erişimi ve tam
taramayı ayırır, her erişimde **gidiş** sayısını, okunan belge sayısını ve okunan baytı
sayar. Bir de belge boyutu sınırı vardır: sınırı aşan yazma reddedilir. Eşitlik eşlemesi
yalnız karşılaştırmanın adil olması için bulunur; dizinin kendi aritmetiği bu konunun
beşinci dersinin konusudur.

```js
// belgedepo.mjs — kucuk bir belge deposu: derlemler, anahtarla erisim, tarama, esitlik
// eslemesi ve belge boyutu siniri. Her erisim gidis, okunan belge ve bayt olarak sayilir.

// 01. dersin kodlamasinin bayt kurali: sabit boylu tip boyunu etiketinden alir,
// degisken boylu tip 4 baytlik uzunluk on eki tasir, her alan 1 bayt etiket + ad + NUL.
export function bayt(d) {
  if (d === null) return 0;
  if (typeof d === "boolean") return 1;
  if (typeof d === "number") return Number.isInteger(d) ? 4 : 8;
  if (d instanceof Date) return 8;
  if (typeof d === "string") return 4 + Buffer.byteLength(d) + 1;
  const oge = Array.isArray(d) ? d.map((v, i) => [String(i), v]) : Object.entries(d);
  return 5 + oge.reduce((t, [a, v]) => t + 2 + Buffer.byteLength(a) + bayt(v), 0);
}

export class Depo {
  constructor(sinir = 16384) {
    this.derlem = new Map();
    this.dizin = new Map();
    this.sinir = sinir;
    this.sifirla();
  }
  sifirla() {
    this.o = { gidis: 0, okunanBelge: 0, okunanBayt: 0, yazilanBelge: 0, yazilanBayt: 0 };
  }
  d(ad) {
    if (!this.derlem.has(ad)) this.derlem.set(ad, new Map());
    return this.derlem.get(ad);
  }
  yaz(ad, belge) {
    const n = bayt(belge);
    if (n > this.sinir) throw new Error(`belge boyutu siniri asildi: ${n} > ${this.sinir}`);
    this.d(ad).set(belge._k, belge);
    this.o.yazilanBelge += 1;
    this.o.yazilanBayt += n;
    return n;
  }
  oku(b) {
    this.o.okunanBelge += 1;
    this.o.okunanBayt += bayt(b);
    return b;
  }
  bul(ad, k) {                                   // anahtarla erisim: bir gidis, bir belge
    this.o.gidis += 1;
    const b = this.d(ad).get(k);
    return b ? this.oku(b) : undefined;
  }
  tara(ad, kosul) {                              // tam tarama: derlemin tamami okunur
    this.o.gidis += 1;
    return [...this.d(ad).values()].filter((b) => kosul(this.oku(b)));
  }
  esitlikEslemesi(ad, alan) {                    // dizin aritmetigi 05. dersin konusu
    const m = new Map();
    for (const b of this.d(ad).values()) {
      const v = b[alan];
      if (!m.has(v)) m.set(v, []);
      m.get(v).push(b._k);
    }
    this.dizin.set(`${ad}.${alan}`, m);
  }
  ara(ad, alan, deger) {
    this.o.gidis += 1;
    const anahtarlar = this.dizin.get(`${ad}.${alan}`).get(deger) ?? [];
    return anahtarlar.map((k) => this.oku(this.d(ad).get(k)));
  }
}
```

#### Üç İş, İki Şema

**NS7 (varsayım):** katalogda 20.000 kitap vardır, her kitabın 1–5 kopyası bulunur, tohum
424242'dir. **NS9 (varsayım):** kütüphanenin üç işi vardır — kitap sayfasını açmak (bir
kitap ve bütün kopyaları), bir şubedeki onarımdaki kopyaları listelemek ve bir kopyanın
durumunu değiştirmek. Üçü de iki şemada koşturulur.

```js
// gomme-referans.mjs — ayni katalog iki semada kurulur: kopyalar kitaba gomulu, ya da
// ayri derlemde referansli. Uc is her iki semada kosturulur ve fark sayilir.
// Ayni dizinde belgedepo.mjs bulunur.
import { Depo, bayt } from "./belgedepo.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const N = 20000;

const ham = [];
for (let i = 1; i <= N; i += 1) {
  const kopya = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * SUBE.length)],
      durum: DURUM[Math.floor(rast() * DURUM.length)] });
  ham.push({ _k: `K-${String(i).padStart(5, "0")}`, baslik: `Kitap ${i} uzerine incelemeler`,
    yazar: `Yazar ${i % 4000}`, yayin_yili: 1950 + (i % 75), kopya });
}
const KOPYA_SAYISI = ham.reduce((t, b) => t + b.kopya.length, 0);

const gomulu = new Depo();
for (const b of ham) gomulu.yaz("kitap", b);
const referansli = new Depo();
for (const b of ham) {
  referansli.yaz("kitap", { _k: b._k, baslik: b.baslik, yazar: b.yazar, yayin_yili: b.yayin_yili });
  for (const k of b.kopya)
    referansli.yaz("kopya", { _k: k.barkod, kitap: b._k, sube: k.sube, durum: k.durum });
}
referansli.esitlikEslemesi("kopya", "kitap");
referansli.esitlikEslemesi("kopya", "sube");

const yaz = (baslik, o) => console.log(`${baslik.padEnd(26)} gidis ${String(o.gidis).padStart(2)}` +
  `  okunan belge ${String(o.okunanBelge).padStart(6)}  okunan bayt ${String(o.okunanBayt).padStart(8)}`);

console.log(`kitap ${N}, kopya ${KOPYA_SAYISI} (kitap basina ${(KOPYA_SAYISI / N).toFixed(2)})`);
console.log(`derlem boyutu: gomulu kitap ${[...gomulu.d("kitap").values()].reduce((t, b) => t + bayt(b), 0)}` +
  `  referansli kitap+kopya ${[...referansli.d("kitap").values()].reduce((t, b) => t + bayt(b), 0)
  + [...referansli.d("kopya").values()].reduce((t, b) => t + bayt(b), 0)}`);

// IS 1 — kitap sayfasi: bir kitap ve butun kopyalari.
gomulu.sifirla(); referansli.sifirla();
gomulu.bul("kitap", "K-04242");
yaz("is1 gomulu", gomulu.o);
referansli.bul("kitap", "K-04242");
referansli.ara("kopya", "kitap", "K-04242");
yaz("is1 referansli", referansli.o);

// IS 2 — bir subedeki onarimdaki kopyalar.
gomulu.sifirla(); referansli.sifirla();
const g2 = gomulu.tara("kitap", (b) => b.kopya.some((k) => k.sube === "Kadikoy" && k.durum === "onarimda"));
yaz("is2 gomulu", gomulu.o);
const r2 = referansli.ara("kopya", "sube", "Kadikoy").filter((k) => k.durum === "onarimda");
yaz("is2 referansli", referansli.o);
console.log(`is2 sonuc: gomulu ${g2.length} kitap belgesi, referansli ${r2.length} kopya belgesi`);

// IS 3 — 5.000 kopyanin durumu degisir.
gomulu.sifirla(); referansli.sifirla();
for (let i = 0; i < 5000; i += 1) {
  const kitap = ham[(i * 7) % N];
  const hedef = kitap.kopya[i % kitap.kopya.length];
  const g = gomulu.bul("kitap", kitap._k);
  g.kopya.find((k) => k.barkod === hedef.barkod).durum = "rafta";
  gomulu.yaz("kitap", g);
  const r = referansli.bul("kopya", hedef.barkod);
  referansli.yaz("kopya", { ...r, durum: "rafta" });
}
console.log(`is3 gomulu     yazilan belge ${gomulu.o.yazilanBelge}  yazilan bayt ${gomulu.o.yazilanBayt}`);
console.log(`is3 referansli yazilan belge ${referansli.o.yazilanBelge}  yazilan bayt ${referansli.o.yazilanBayt}`);
```

```
kitap 20000, kopya 58595 (kitap basina 2.93)
derlem boyutu: gomulu kitap 6149233  referansli kitap+kopya 6612373
is1 gomulu                 gidis  1  okunan belge      1  okunan bayt      454
is1 referansli             gidis  2  okunan belge      6  okunan bayt      502
is2 gomulu                 gidis  1  okunan belge  20000  okunan bayt  6149233
is2 referansli             gidis  1  okunan belge  10066  okunan bayt   781096
is2 sonuc: gomulu 2934 kitap belgesi, referansli 3092 kopya belgesi
is3 gomulu     yazilan belge 5000  yazilan bayt 1541395
is3 referansli yazilan belge 5000  yazilan bayt 381255
```

Üç iş üç ayrı yöne işaret eder. Kitap sayfasında gömme kazanır: tek gidişte 454 bayt
okunur, referanslı şemada aynı bilgi iki gidiş ve altı belge ile, 502 bayt okunarak
toplanır. Fark bayt tarafında küçüktür (%10,6), gidiş tarafında iki katıdır — ve gidiş,
depoya uzaktan bağlanan bir uygulama için baytdan pahalı bir birimdir.

Şube sorgusunda yön tersine döner. Gömülü şemada kopya, kitabın içindedir; bir şubenin
kopyalarını bulmak için 20.000 kitap belgesinin tamamı okunur, 6.149.233 bayt. Referanslı
şemada kopya kendi derlemindedir ve şubeye göre eşlenebilir: 10.066 belge, 781.096 bayt.
Aynı soruya yanıt için okunan bayt 7,9 kat düşer. İki sonucun farklı sayıda olması da
öğreticidir: gömülü tarama 2.934 **kitap** döndürür, referanslı sorgu 3.092 **kopya** —
aynı kitabın aynı şubede onarımdaki iki kopyası olabilir ve gömülü şemada bu ayrım
belgenin içinde kalır.

Yazma yolu üçüncü yönü verir. Bir kopyanın durumu değiştiğinde gömülü şemada kitabın
tamamı yeniden yazılır: 5.000 değişiklik 1.541.395 bayt yazar, değişiklik başına 308 bayt.
Referanslı şemada yalnız kopya belgesi yeniden yazılır: 381.255 bayt, değişiklik başına 76
bayt. Oran 4,04'tür ve kitabın alan sayısı arttıkça büyür, çünkü değişen alanın yanındaki
her alan da yeniden yazılır. Saklanan yer tarafında karar tersine çalışır: referanslı şema
6.612.373 bayt tutar, gömülü şema 6.149.233 — fark, her kopya belgesinin kendi anahtarını
ve kitap anahtarını tekrar taşımasıdır.

#### Sınırsız Büyüyen Dizi

Gömmenin en pahalı biçimi, sonu olmayan bir diziyi gömmektir. Ödünç geçmişi böyledir:
kitap sabit kalır, geçmiş sürekli uzar. **NS10 (varsayım):** deponun belge boyutu sınırı
16.384 bayttır. **NS11 (varsayım):** üç yılda 1.406.280 ödünç verilir ve dağılım Zipf
biçimindedir — r. sıradaki kitabın payı `1/r` ile orantılıdır.

```js
// buyume.mjs — odunc gecmisi kitap belgesine gomulurse belge buyur. Sinirsiz gomme ile
// son 20 kaydi gomup gerisini ayri derleme birakan karar karsilastirilir.
// Ayni dizinde belgedepo.mjs bulunur.
import { Depo, bayt } from "./belgedepo.mjs";

const SINIR = 16384, N = 20000, ODUNC = 1406280, SON = 20;
const taban = (i) => ({ _k: `K-${String(i).padStart(5, "0")}`,
  baslik: `Kitap ${i} uzerine incelemeler`, yazar: `Yazar ${i % 4000}`,
  yayin_yili: 1950 + (i % 75), odunc: [] });
const kayit = (n) => ({ uye: `U-${String(n % 90000).padStart(5, "0")}`,
  alis: new Date(2023, 0, 1 + (n % 1095)), iade: new Date(2023, 0, 15 + (n % 1095)) });

// Zipf dagilimi: r. siradaki kitabin payi 1/r ile orantili. Rastgelelik yok.
const H = Array.from({ length: N }, (_, i) => 1 / (i + 1)).reduce((a, b) => a + b);
const pay = Array.from({ length: N }, (_, i) => Math.round(ODUNC / ((i + 1) * H)));
const bosBelge = bayt(taban(1));
const birKayit = bayt({ ...taban(1), odunc: [kayit(1)] }) - bosBelge;
const derlemBayt = (d) => [...d.values()].reduce((t, b) => t + bayt(b), 0);
console.log(`bos kitap belgesi ${bosBelge} bayt, bir odunc kaydi ${birKayit} bayt`);
console.log(`sinir ${SINIR} bayt, kaba hesap: ${Math.floor((SINIR - bosBelge) / birKayit)} kayit`);

// A karari: gecmisin tamami gomulur. Boyut artimli izlenir; n. oge diziye
// 2 + sira adinin uzunlugu + kayit bayti ekler.
const depoA = new Depo(SINIR);
let dayanan = 0, ilk = null, sigmayan = 0;
for (let i = 1; i <= N; i += 1) {
  const b = taban(i);
  let boyut = bosBelge, sigan = 0;
  for (let n = 0; n < pay[i - 1]; n += 1) {
    const ek = String(n).length + birKayit - 1;
    if (boyut + ek > SINIR) break;
    boyut += ek;
    b.odunc.push(kayit(n));
    sigan += 1;
  }
  if (sigan < pay[i - 1]) {
    dayanan += 1;
    sigmayan += pay[i - 1] - sigan;
    ilk ??= { sira: i, kayit: sigan, boyut: bayt(b), istenen: pay[i - 1] };
  }
  depoA.yaz("kitap", b);
}
console.log(`A sinirsiz gomme: sinira dayanan kitap ${dayanan}, sigmayan odunc kaydi ${sigmayan}`);
console.log(`  ilk dayanan ${ilk.sira}. kitap: ${ilk.kayit} kayit ${ilk.boyut} bayt, istenen ${ilk.istenen}`);
console.log(`  kitap derlemi ${derlemBayt(depoA.d("kitap"))} bayt`);

// B karari: son 20 kayit gomulur, gerisi odunc derleminde durur.
const depoB = new Depo(SINIR);
for (let i = 1; i <= N; i += 1) {
  const b = taban(i);
  for (let n = Math.max(0, pay[i - 1] - SON); n < pay[i - 1]; n += 1) b.odunc.push(kayit(n));
  depoB.yaz("kitap", b);
}
const enBuyuk = Math.max(...[...depoB.d("kitap").values()].map(bayt));
console.log(`B son ${SON} kayit gomulu: en buyuk belge ${enBuyuk} bayt, sinira uzaklik ${SINIR - enBuyuk}`);
console.log(`  kitap derlemi ${derlemBayt(depoB.d("kitap"))} bayt, ayri derleme kalan kayit ` +
  `${pay.reduce((t, p) => t + Math.max(0, p - SON), 0)}`);
```

```
bos kitap belgesi 108 bayt, bir odunc kaydi 53 bayt
sinir 16384 bayt, kaba hesap: 307 kayit
A sinirsiz gomme: sinira dayanan kitap 451, sigmayan odunc kaydi 763673
  ilk dayanan 1. kitap: 297 kayit 16333 bayt, istenen 134178
  kitap derlemi 36942774 bayt
B son 20 kayit gomulu: en buyuk belge 1184 bayt, sinira uzaklik 15200
  kitap derlemi 17256316 bayt, ayri derleme kalan kayit 1125534
```

Kaba hesap 307 kayıt der, gerçekte 297 kayıt sığar. Aradaki 10 kayıtlık fark, dizinin
kendi alan adlarından gelir: sıra adı 10. ögeden sonra iki, 100. ögeden sonra üç haneye
çıkar ve her basamak ögeye bir bayt ekler. Bu, önceki dersin bulgusunun bir sonucudur —
dizide bile alan adı
saklanır.

Asıl sayı ikinci satırdadır. 20.000 kitabın 451'i üç yıl içinde sınıra dayanır ve 763.673
ödünç kaydı belgeye yazılamaz. En çok ödünç alınan kitap 134.178 kayıt ister, 297 tanesini
alabilir. Sınır bir başarım sorunu değil, bir veri kaybı sorunudur: gömme kararı burada
sessizce geri döndürülemez bir sınıra çarpar. İkinci karar sınırı ortadan kaldırır — son 20
kayıt gömülür, gerisi ayrı derlemde durur. En büyük kitap belgesi 1.184 bayta iner, sınıra
15.200 bayt uzaklık kalır, ayrı derleme 1.125.534 kayıt taşınır. Kitap sayfası hâlâ tek
gidişte son 20 ödüncü gösterir; tam geçmiş isteyen ekran ikinci bir gidiş öder.

Kararın kuralı bu üç ölçümden çıkar: birlikte okunan ve birlikte değişen veri gömülür,
ayrı sorgulanan veya sınırsız büyüyen veri referansla tutulur. "Daha esnek" ya da "daha
doğal" cümleleri bu kararı taşımaz; taşıyan şey gidiş sayısı, okunan bayt, yeniden yazılan
bayt ve sınıra uzaklıktır.

#### Özet

- Gömme ilgili veriyi belgenin içine koyar, referans ayrı derlemde tutup anahtarını taşır;
  ikisi aynı katalogu farklı erişim maliyetiyle saklar.
- Kitap sayfası gömülü şemada 1 gidiş ve 454 bayt, referanslı şemada 2 gidiş ve 502 bayt
  tutar; kazanç bayt değil gidiştir.
- Şube sorgusu gömülü şemada 20.000 belge ve 6.149.233 bayt okur, referanslı şemada 10.066
  belge ve 781.096 bayt; oran 7,9 kattır.
- Bir kopyanın durum değişikliği gömülü şemada kitabın tamamını yeniden yazar: 5.000
  değişiklik 1.541.395 bayt, referanslı şemada 381.255 bayt (oran 4,04).
- Sınırsız büyüyen dizi belge boyutu sınırına dayanır: 20.000 kitabın 451'i sınırı aşar ve
  763.673 ödünç kaydı yazılamaz; son 20 kaydı gömen karar en büyük belgeyi 1.184 bayta indirir.

#### Sonraki Adım

Şemanın kurulduğu iki karar sayıldı; sıra o şema üzerinde soru sormaya geldi. Şube
sorgusu bu derste elle yazılmış bir koşul işleviyle çalıştırıldı, oysa depo koşulu kendisi
değerlendirir ve bunu bir işleç kümesiyle yapar: karşılaştırma, mantıksal bağlaç ve dizi
işleçleri. Dizi işleçleri bu derste ortaya çıkan bir belirsizliği taşır — gömülü kopya
dizisine iki koşul birden sorulduğunda koşulların **aynı** ögede mi yoksa belgenin
herhangi iki ögesinde mi karşılanması gerektiği, aynı sorgunun iki farklı yanıtı demektir.
Sonraki ders işleçleri kendi değerlendiricisinde kurar ve bu farkı sayar.

### Sorgu İşleçleri

Source: https://academia.sh/tr/kurslar/nosql/sorgu-islecleri

Karşılaştırma, mantıksal ve dizi işleçlerinin semantiği kendi değerlendiricisinde kurulur: bir alan yolunun dizinin içine inmesi, koşulların belge düzeyinde mi öge düzeyinde mi karşılandığı, olumsuzlamanın dizide ve eksik alanda ne yaptığı, her biri dönen belge sayısıyla ölçülür.

Önceki ders şemayı kurdu ve gömme kararının bedelini saydı; şube sorgusu ise elle yazılmış
bir koşul işleviyle çalıştırıldı. Depo bu işi kendisi yapar ve bir işleç kümesiyle yapar:
karşılaştırma işleçleri, mantıksal bağlaçlar ve dizilere özgü işleçler. Bu ders o kümenin
söz dizimini ezberletmez — söz dizimi depodan depoya değişir ve zaten değişir. Değişmeyen
şey semantiktir ve semantik burada kendi değerlendiricisinde kurulup ölçülür.

Ölçülecek asıl soru önceki dersin sonunda ortaya çıktı. Gömülü kopya dizisine iki koşul
birden sorulduğunda koşulların aynı kopyada mı, yoksa belgenin herhangi iki kopyasında mı
karşılanması gerektiği belirsizdir. İki okuma da tutarlıdır, ikisi de gerçekten kullanılır
ve ikisi farklı sayıda belge döndürür.

#### Yolun Dizinin İçine İnmesi

İlişkisel bir koşul tek bir hücreye bakar. Belge modelinde bir alan yolu birden çok değere
ulaşabilir: `kopya.sube` yolu, üç kopyası olan bir kitapta üç değer üretir. Değerlendirici
bu durumda **varoluşsal** davranır — koşul, ulaşılan değerlerden **herhangi biri**
sağladığında doğar. Bu tek kural, dersin bütün ölçümlerinin kaynağıdır.

Değerlendirici üç işleç sınıfı taşır. Karşılaştırma işleçleri (`=`, `<>`, `>`, `>=`, `<`,
`<=`, `icinde`) bir değeri bir sabitle kıyaslar. Mantıksal bağlaçlar (`ve`, `veya`,
`degil`) koşulları birleştirir. Dizi işleçleri (`ogede`, `tumu`, `hicbiri`) ise ötekilerden
ayrılır: değerlere değil, **ögelere** bakar ve alt koşulu her ögeye ayrı ayrı uygular.

```js
// islecler.mjs — kosul degerlendirici. Yol dizilerin icine iner: bir alan yolu birden
// cok degere ulasabilir ve kosul o degerlerden herhangi biri saglarsa dogar.
const KIYAS = {
  "=": (a, b) => a === b,
  "<>": (a, b) => a !== b,
  ">": (a, b) => a > b,
  ">=": (a, b) => a >= b,
  "<": (a, b) => a < b,
  "<=": (a, b) => a <= b,
  icinde: (a, b) => b.includes(a),
};

// Yoldaki degerler. Dizi hem kendisi hem ogeleri olarak aday olur.
export function degerler(kok, yol) {
  let kume = [kok];
  for (const ad of yol.split(".")) {
    const sonraki = [];
    for (const d of kume)
      for (const o of Array.isArray(d) ? d : [d])
        if (o && typeof o === "object" && !Array.isArray(o) && ad in o) sonraki.push(o[ad]);
    kume = sonraki;
  }
  return kume.flatMap((v) => (Array.isArray(v) ? [v, ...v] : [v]));
}

// Yoldaki dizilerin kendisi: oge duzeyi islecleri bunun uzerinde calisir.
const diziler = (kok, yol) => degerler(kok, yol).filter(Array.isArray);

export function eslesir(belge, kosul, s = { kiyas: 0, oge: 0 }) {
  for (const [ad, beklenen] of Object.entries(kosul)) {
    if (ad === "ve") { if (beklenen.every((k) => eslesir(belge, k, s))) continue; return false; }
    if (ad === "veya") { if (beklenen.some((k) => eslesir(belge, k, s))) continue; return false; }
    if (ad === "degil") { if (eslesir(belge, beklenen, s)) return false; continue; }

    const sade = beklenen === null || typeof beklenen !== "object" || Array.isArray(beklenen);
    const kosullar = sade ? { "=": beklenen } : beklenen;
    for (const [islec, operand] of Object.entries(kosullar)) {
      if (islec === "var") {
        if (degerler(belge, ad).length > 0 !== operand) return false;
        continue;
      }
      if (islec === "ogede" || islec === "tumu" || islec === "hicbiri") {
        const oge = diziler(belge, ad).flat();
        s.oge += oge.length;
        const say = oge.filter((o) => eslesir(o, operand, s)).length;
        const dogru = islec === "ogede" ? say > 0
          : islec === "tumu" ? say === oge.length && oge.length > 0 : say === 0;
        if (dogru) continue;
        return false;
      }
      const aday = degerler(belge, ad);
      s.kiyas += aday.length;
      if (aday.some((d) => KIYAS[islec](d, operand))) continue;
      return false;
    }
  }
  return true;
}

export const suz = (kume, kosul, s) => kume.filter((b) => eslesir(b, kosul, s));
```

#### Aynı Soru, İki Semantik

**NS7 (varsayım):** katalog 20.000 kitap belgesidir, her kitabın 1–5 kopyası ve 2–4 etiketi
vardır, tohum 424242'dir. **NS12 (varsayım):** esnek şemanın sonucu olarak belgelerin
kırkta birinde `yayin_yili` alanı hiç yoktur. Sorular önce belge düzeyinde, sonra öge
düzeyinde sorulur; her koşumda dönen belge sayısı, yapılan karşılaştırma sayısı ve
incelenen öge sayısı yazılır.

```js
// islec-olcum.mjs — ayni uc soru iki islec bicimiyle sorulur: belge duzeyinde baglac ve
// oge duzeyinde eslestirme. Donen belge sayisi ile yapilan karsilastirma sayilir.
// Ayni dizinde islecler.mjs bulunur.
import { suz } from "./islecler.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"];

const KATALOG = [];
for (let i = 1; i <= 20000; i += 1) {
  const kopya = [], etiket = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] });
  for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1)
    etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
  const b = { _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), etiket, kopya };
  if (i % 40 === 0) delete b.yayin_yili;                 // esnek sema: alan eksik olabilir
  KATALOG.push(b);
}

function calistir(ad, kosul) {
  const s = { kiyas: 0, oge: 0 };
  const sonuc = suz(KATALOG, kosul, s);
  console.log(`${ad.padEnd(34)} ${String(sonuc.length).padStart(5)} belge  ` +
    `karsilastirma ${String(s.kiyas).padStart(6)}  incelenen oge ${String(s.oge).padStart(6)}`);
  return sonuc.length;
}

console.log(`katalog ${KATALOG.length} belge`);
const a1 = calistir("belge duzeyi baglac", { "kopya.sube": "Kadikoy", "kopya.durum": "onarimda" });
const a2 = calistir("oge duzeyi eslestirme", { kopya: { ogede: { sube: "Kadikoy", durum: "onarimda" } } });
console.log(`  aradaki ${a1 - a2} belgede kosullar ayri kopyalarda saglaniyor`);

const b1 = calistir("kopya.durum <> oduncte", { "kopya.durum": { "<>": "oduncte" } });
const b2 = calistir("hicbir kopya oduncte degil", { kopya: { hicbiri: { durum: "oduncte" } } });
console.log(`  aradaki ${b1 - b2} belgede en az bir kopya odunctedir`);

const c1 = calistir("etiket icinde [tarih, bilim]", { etiket: { icinde: ["tarih", "bilim"] } });
const c2 = calistir("hem tarih hem bilim", { ve: [{ etiket: "tarih" }, { etiket: "bilim" }] });
const c3 = calistir("butun kopyalar rafta", { kopya: { tumu: { durum: "rafta" } } });
console.log(`  icinde ile iki kosullu baglac arasindaki fark ${c1 - c2} belge`);

calistir("yayin_yili alani yok", { yayin_yili: { var: false } });
calistir("yayin_yili >= 2010", { yayin_yili: { ">=": 2010 } });
calistir("yayin_yili < 2010 degil", { degil: { yayin_yili: { "<": 2010 } } });
```

```
katalog 20000 belge
belge duzeyi baglac                 6033 belge  karsilastirma  88749  incelenen oge      0
oge duzeyi eslestirme               3340 belge  karsilastirma  70018  incelenen oge  59494
  aradaki 2693 belgede kosullar ayri kopyalarda saglaniyor
kopya.durum <> oduncte             17787 belge  karsilastirma  59494  incelenen oge      0
hicbir kopya oduncte degil          7111 belge  karsilastirma  59494  incelenen oge  59494
  aradaki 10676 belgede en az bir kopya odunctedir
etiket icinde [tarih, bilim]       14033 belge  karsilastirma  79730  incelenen oge      0
hem tarih hem bilim                 2777 belge  karsilastirma 115287  incelenen oge      0
butun kopyalar rafta                1919 belge  karsilastirma  59494  incelenen oge  59494
  icinde ile iki kosullu baglac arasindaki fark 11256 belge
yayin_yili alani yok                 500 belge  karsilastirma      0  incelenen oge      0
yayin_yili >= 2010                  3891 belge  karsilastirma  19500  incelenen oge      0
yayin_yili < 2010 degil             4391 belge  karsilastirma  19500  incelenen oge      0
```

#### Sayıların Söylediği

İlk çift dersin asıl bulgusudur. "Kadıköy şubesinde onarımda kopyası olan kitap" sorusu
belge düzeyinde sorulduğunda 6.033 belge döner, öge düzeyinde sorulduğunda 3.340. Aradaki
2.693 belgede Kadıköy'de bir kopya vardır ve onarımda bir kopya vardır, ama bunlar farklı
kopyalardır — Kadıköy'deki raftadır, onarımdaki başka şubededir. Belge düzeyi bağlaç
yanlış değildir; sorusu farklıdır. Yanlış olan, öge düzeyinde bir soruyu belge düzeyinde
sormaktır ve sonuç %80,6 fazla belgedir. Ölçüm bu farkı sessizce üretir: iki sorgu da
çalışır, ikisi de hata vermez.

İkinci çift olumsuzlamanın dizideki davranışını gösterir. `kopya.durum <> "oduncte"` koşulu
17.787 belge döndürür; buradaki niyet neredeyse her zaman "hiçbir kopyası ödünçte olmayan
kitap"tır ve o soruya doğru yanıt 7.111'dir. Fark 10.676 belgedir — bu kitapların en az
bir kopyası ödünçtedir, ama en az bir kopyası da ödünçte değildir ve varoluşsal kural
koşulu doğurur. Olumsuzlama dizide, sezginin tersine, kapsayıcı çalışır: "değildir" ifadesi
"hiçbiri değildir" anlamına gelmez. Doğru ifade `hicbiri` işlecidir ve bu, öge düzeyinde
çalıştığı için 59.494 ögenin tamamını inceler.

Üçüncü çift, aynı yanlışın ters yönünü gösterir. `icinde` işleci diziye uygulandığında
"listedeki değerlerden herhangi biri" demektir ve 14.033 belge döndürür; "hem tarih hem
bilim etiketli kitap" sorusu ise iki koşullu bir bağlaçtır ve 2.777 belge döndürür. Fark
11.256'dır. Burada belge düzeyi bağlaç **doğru** araçtır, çünkü etiketler ayrı ögelerdedir
ve koşulların aynı ögede karşılanması istenmez. İki ölçüm birlikte kuralı verir: bağlacın
belge düzeyinde mi öge düzeyinde mi kurulacağını sorunun kendisi belirler, dizinin varlığı
değil.

`tumu` işleci 1.919 belge döndürür: bütün kopyaları rafta olan kitaplar. Bu işlecin
değerlendiricideki tanımı bir ayrıntı taşır — `say === oge.length && oge.length > 0`.
İkinci koşul olmasaydı, hiç kopyası olmayan bir kitap "bütün kopyaları rafta" sorusuna
doğru yanıt verirdi, çünkü boş bir küme üzerinde her önerme doğrudur. Boş dizinin bu
davranışı, belge modelinde alanın hiç bulunmamasıyla birlikte iki ayrı boşluk yaratır ve
ikisi de sorgunun yanıtını sessizce değiştirir.

Son üç satır esnek şemanın sorgu tarafındaki izidir. 500 belgede `yayin_yili` alanı yoktur.
`yayin_yili >= 2010` bu belgeleri döndürmez (3.891), ama `degil` ile sarılmış aynı koşul
döndürür (4.391): eksik alan üzerinde hiçbir karşılaştırma doğmaz, dolayısıyla
olumsuzlaması doğar. Fark tam olarak 500'dür — alanı olmayan belge sayısı. Bir raporun
"2010 öncesi olmayan kitaplar" satırı, alanı hiç bulunmayan 500 kitabı da içine alır.

Karşılaştırma sayıları maliyeti verir. Öge düzeyi eşleştirme 70.018 karşılaştırma ve 59.494
öge incelemesi yapar, belge düzeyi bağlaç 88.749 karşılaştırma yapar. İkisi de derlemin
tamamını okur: 20.000 belge. Bu, işleç seçiminin okunan belge sayısını değiştirmediği
anlamına gelir — o sayıyı değiştiren şey dizindir ve bu konunun beşinci dersinin konusudur.

#### Özet

- Bir alan yolu dizinin içine iner ve birden çok değere ulaşır; koşul, ulaşılan
  değerlerden herhangi biri sağladığında doğar.
- "Kadıköy'de onarımdaki kopya" sorusu belge düzeyinde 6.033, öge düzeyinde 3.340 belge
  döndürür; 2.693 belgede iki koşul ayrı kopyalarda karşılanır.
- Dizide olumsuzlama kapsayıcıdır: `kopya.durum <> "oduncte"` 17.787 belge döndürür, "hiçbir
  kopyası ödünçte değil" 7.111 belge; fark 10.676'dır.
- `icinde` işleci diziye uygulandığında "herhangi biri" demektir (14.033 belge); "hepsi"
  sorusu iki koşullu belge düzeyi bağlaçla kurulur (2.777 belge).
- Eksik alan hiçbir karşılaştırmayı doğurmaz, bu yüzden olumsuzlaması doğar: `yayin_yili`
  alanı olmayan 500 belge, koşulun olumsuzlanmış biçimine düşer.

#### Sonraki Adım

İşleçler bir soruyu süzmeye yetiyor, ama kütüphanenin sorularının çoğu süzmeyle bitmez:
"hangi şubede kaç kopya onarımda", "en çok ödünç alınan beş kitap", "yazar başına ortalama
kopya sayısı". Bunların hepsi süzmenin ardından gruplama, sayma ve sıralama ister ve
bunları tek bir koşul ifadesiyle söylemek mümkün değildir. Belge modeli bu işi aşamalardan
oluşan bir boru hattıyla yapar: her aşama bir kayıt akışı alır, dönüştürür ve bir sonrakine
verir. Aşamaların sırası akışın uzunluğunu belirler — süzme başta mı sonda mı olduğuna göre
sonraki aşamaların işlediği kayıt sayısı değişir. Sonraki ders o boru hattını kurar ve sıra
kararının işlenen kayıt sayısına etkisini sayar.

### Toplama Boru Hattı

Source: https://academia.sh/tr/kurslar/nosql/toplama-boru-hatti

Aşamalı dönüşümün kendi gerçekleştiriminde kurulması ve aynı sorunun üç farklı aşama sırasıyla yanıtlanması: işlenen kayıt sayısının 43.610'dan 124.606'ya, engelleyici aşamanın bellekte tuttuğu kaydın 2.418'den 40.276'ya çıkması ve sonucun üç sırada da aynı kalması.

Önceki ders bir belgenin koşulu sağlayıp sağlamadığını söyleyen değerlendiriciyi kurdu.
Kütüphanenin sorularının çoğu bununla bitmez: "hangi yazarın kaç kopyası onarımda", "en
çok ödünç alınan beş kitap", "şube başına rafta duran kopya sayısı". Bu soruların ortak
yanı, yanıtın tek tek belgelerde değil belgelerin **toplamında** olmasıdır ve bir koşul
ifadesi toplama yapamaz.

Belge modeli bu işi **toplama boru hattıyla** (aggregation pipeline) yapar: sıralı
**aşamalardan** (stage) oluşan bir zincir. Her aşama bir kayıt akışı alır, dönüştürür ve
bir sonrakine verir. Sözcük kabuk komutlarını birbirine bağlayan boru hattıyla aynıdır ve
benzerlik gerçektir; ayrıldıkları yer akışın birimidir — kabukta akan şey bayttır, burada
akan şey kayıttır ve aşamalar kayıt üzerinde çalışır. Bu dersin sorusu aşamaların ne
yaptığı değil, **sıralarının** ne kadara mal olduğudur.

#### Akış Aşaması ve Engelleyici Aşama

Aşamalar iki sınıfa ayrılır. **Akış aşaması** kaydı alır, işler, bırakır: süzme, dizi açma
ve yansıtma böyledir; bellekte bir anda tek bir kayıt tutulur. **Engelleyici aşama** ise
sonucunu üretebilmek için akışın tamamını görmek zorundadır: sıralama akışı bütünüyle
bellekte tutar, gruplama ise grup tablosunu tutar. Bir boru hattının bellek maliyeti,
engelleyici aşamalarının o anda tuttuğu kayıt sayısıdır.

```js
// boruhatti.mjs — asamali donusum. Her asama bir kayit akisi alir, donusturur, sonrakine
// verir. Asama basina giren/cikan kayit ve o asamanin bellekte tuttugu kayit sayilir.
export function calistir(kaynak, asamalar) {
  let akis = kaynak, doruk = 0, islenen = 0;
  const rapor = [];
  for (const asama of asamalar) {
    const ad = Object.keys(asama)[0], giren = akis.length;
    let bellek = 1;                                    // akis asamasi: tek kayit tutar
    if (ad === "suz") akis = akis.filter(asama.suz);
    else if (ad === "ac") {                            // diziyi ogelerine acar
      const alan = asama.ac;
      akis = akis.flatMap((b) => (b[alan] ?? []).map((o) => ({ ...b, [alan]: o })));
    } else if (ad === "grupla") {
      const m = new Map();
      for (const b of akis) {
        const k = asama.grupla.anahtar(b);
        m.set(k, asama.grupla.topla(m.get(k) ?? asama.grupla.bos(k), b));
      }
      akis = [...m.values()];
      bellek = m.size;                                 // engelleyici: grup tablosu bellekte
    } else if (ad === "sirala") {
      bellek = giren;                                  // engelleyici: akisin tamami bellekte
      akis = [...akis].sort(asama.sirala);
    } else if (ad === "sinirla") akis = akis.slice(0, asama.sinirla);
    else if (ad === "yansit") akis = akis.map(asama.yansit);
    islenen += giren;
    doruk = Math.max(doruk, bellek);
    rapor.push({ ad, giren, cikan: akis.length, bellek });
  }
  return { sonuc: akis, rapor, islenen, doruk };
}

export function yazdir(baslik, c) {
  console.log(`${baslik}: islenen kayit ${c.islenen}, doruk bellek ${c.doruk} kayit`);
  for (const r of c.rapor)
    console.log(`  ${r.ad.padEnd(8)} giren ${String(r.giren).padStart(6)}` +
      ` -> cikan ${String(r.cikan).padStart(6)}   bellek ${r.bellek}`);
}
```

Dizi açma aşaması belge modeline özgüdür ve boru hattının en pahalı aşamasıdır: bir kopya
dizisi taşıyan kitap belgesi, dizinin öge sayısı kadar kayda dönüşür. 20.000 kitap
belgesi, açıldığında 59.494 kayıt olur. Bu aşamadan sonra gelen her aşama üç kat fazla
kayıt işler.

#### Aynı Soru, Üç Sıra

**NS7 (varsayım):** katalog 20.000 kitap belgesidir, kitap başına 1–5 kopya ve 2–4 etiket
bulunur, tohum 424242'dir. Soru sabittir: Kadıköy şubesinde onarımdaki kopya sayısına göre
en yüksek beş yazar. Üç boru hattı bu soruyu yanıtlar. A süzmeyi en başa koyar; B önce
diziyi açıp sonra süzer; C hiç süzmeden gruplar ve süzmeyi grupların üstünde yapar.
Üçünün de doğru yanıtı vermesi gerekir — ölçülen şey doğruluk değil, bedel.

```js
// sira-olcum.mjs — ayni soru uc farkli asama sirasiyla yanitlanir. Sonuc ayni, islenen
// kayit ve doruk bellek farkli. Ayni dizinde boruhatti.mjs bulunur.
import { calistir, yazdir } from "./boruhatti.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"];

const KATALOG = [];
for (let i = 1; i <= 20000; i += 1) {
  const kopya = [], etiket = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] });
  for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1)
    etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
  KATALOG.push({ _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), etiket, kopya });
}
console.log(`katalog ${KATALOG.length} kitap, ` +
  `${KATALOG.reduce((t, b) => t + b.kopya.length, 0)} kopya`);

// Soru: Kadikoy subesinde onarimdaki kopya sayisina gore en yuksek 5 yazar.
const ONARIM = (k) => k.sube === "Kadikoy" && k.durum === "onarimda";
const GRUP = { anahtar: (b) => b.yazar, bos: (k) => ({ yazar: k, sayi: 0 }),
  topla: (g, b) => ({ ...g, sayi: g.sayi + 1 }) };
const SIRALA = (a, b) => b.sayi - a.sayi || (a.yazar < b.yazar ? -1 : 1);

const A = calistir(KATALOG, [
  { suz: (b) => b.kopya.some(ONARIM) },                 // once suz: kitap duzeyinde
  { ac: "kopya" },
  { suz: (b) => ONARIM(b.kopya) },                      // acilan ogeyi suz
  { grupla: GRUP }, { sirala: SIRALA }, { sinirla: 5 },
]);
const B = calistir(KATALOG, [
  { ac: "kopya" },
  { suz: (b) => ONARIM(b.kopya) },
  { grupla: GRUP }, { sirala: SIRALA }, { sinirla: 5 },
]);
const C = calistir(KATALOG, [
  { ac: "kopya" },
  { grupla: { anahtar: (b) => `${b.yazar}|${b.kopya.sube}|${b.kopya.durum}`,
    bos: (k) => ({ yazar: k.split("|")[0], sube: k.split("|")[1],
      durum: k.split("|")[2], sayi: 0 }),
    topla: (g, b) => ({ ...g, sayi: g.sayi + 1 }) } },
  { suz: (g) => g.sube === "Kadikoy" && g.durum === "onarimda" },
  { sirala: SIRALA }, { sinirla: 5 },
]);

yazdir("A once suz", A);
yazdir("B ac sonra suz", B);
yazdir("C once grupla sonra suz", C);
const ozet = (c) => c.sonuc.map((g) => `${g.yazar}=${g.sayi}`).join(" ");
console.log(`A sonuc: ${ozet(A)}`);
console.log(`ucu ayni mi: ${ozet(A) === ozet(B) && ozet(B) === ozet(C)}`);
console.log(`islenen kayit  A ${A.islenen}  B ${B.islenen}  C ${C.islenen}` +
  `   B/A ${(B.islenen / A.islenen).toFixed(2)}  C/A ${(C.islenen / A.islenen).toFixed(2)}`);
console.log(`doruk bellek   A ${A.doruk}  B ${B.doruk}  C ${C.doruk}` +
  `   C/A ${(C.doruk / A.doruk).toFixed(1)} kat`);
```

```
katalog 20000 kitap, 59494 kopya
A once suz: islenen kayit 43610, doruk bellek 2418 kayit
  suz      giren  20000 -> cikan   3340   bellek 1
  ac       giren   3340 -> cikan  11926   bellek 1
  suz      giren  11926 -> cikan   3508   bellek 1
  grupla   giren   3508 -> cikan   2418   bellek 2418
  sirala   giren   2418 -> cikan   2418   bellek 2418
  sinirla  giren   2418 -> cikan      5   bellek 1
B ac sonra suz: islenen kayit 87838, doruk bellek 2418 kayit
  ac       giren  20000 -> cikan  59494   bellek 1
  suz      giren  59494 -> cikan   3508   bellek 1
  grupla   giren   3508 -> cikan   2418   bellek 2418
  sirala   giren   2418 -> cikan   2418   bellek 2418
  sinirla  giren   2418 -> cikan      5   bellek 1
C once grupla sonra suz: islenen kayit 124606, doruk bellek 40276 kayit
  ac       giren  20000 -> cikan  59494   bellek 1
  grupla   giren  59494 -> cikan  40276   bellek 40276
  suz      giren  40276 -> cikan   2418   bellek 1
  sirala   giren   2418 -> cikan   2418   bellek 2418
  sinirla  giren   2418 -> cikan      5   bellek 1
A sonuc: Yazar 1068=4 Yazar 111=4 Yazar 1530=4 Yazar 1580=4 Yazar 1618=4
ucu ayni mi: true
islenen kayit  A 43610  B 87838  C 124606   B/A 2.01  C/A 2.86
doruk bellek   A 2418  B 2418  C 40276   C/A 16.7 kat
```

#### Sıranın Bedeli

Üç boru hattı aynı beş yazarı aynı sayılarla döndürür; `ucu ayni mi` satırı bunu
doğrular. Ayrıldıkları yer bedeldir ve bedel iki ayrı sayıyla ölçülür.

İşlenen kayıt sayısı A'da 43.610, B'de 87.838, C'de 124.606'dır. A ile B arasındaki fark
yalnız ilk süzmeden gelir: A, dizi açma aşamasına 20.000 kitabın 3.340'ını sokar, B ise
tamamını sokar. Açma aşaması A'da 11.926, B'de 59.494 kayıt üretir — beş kat fazla. Buna
karşılık A fazladan bir aşama koşturur ve ilk süzme 20.000 belgeye bakar. Toplamda oran
2,01'dir. Kural şudur: süzme, akışı en çok daraltacağı yere değil, **kaydı çoğaltan
aşamadan önceye** konur; dizi açma bir çoğaltıcıdır.

A'nın ilk süzmesi öge düzeyinde değil belge düzeyindedir ve önceki dersin ölçtüğü gevşek
biçimdedir: 3.340 belge döndürür. Ardından açılan 11.926 kaydın yalnız 3.508'i ikinci
süzmeden geçer. Bu gevşeklik bir hata değil, kasıtlı bir seçimdir — belge düzeyi süzme
doğru sonucu **kaçırmaz**, yalnız fazladan kayıt geçirir. Boru hattında öne konan süzmenin
tek koşulu budur: kapsayıcı olmalı, daraltıcı olması gerekmez.

İkinci sayı belleği verir. A ve B'de doruk bellek 2.418 kayıttır — grup tablosunun boyu,
yani Kadıköy'de onarımda kopyası olan farklı yazar sayısı. C'de aynı sayı 40.276'dır, 16,7
kat fazla. C'nin yaptığı hata gruplamayı süzmeden önce koymaktır: gruplama artık yalnız
ilgilenilen yazarları değil, yazar–şube–durum üçlüsünün bütün birleşimlerini tutar. Bu
sayı derlem büyüdükçe grup anahtarının çarpımıyla büyür ve engelleyici aşamanın bellek
sınırını aşması, boru hattının diske taşmasına ya da tümden reddedilmesine yol açar.

Son aşama olan `sinirla` üçünde de aynı yerdedir ve hiçbirinde işi azaltmaz: sıralama zaten
2.418 kaydın tamamını görmüştür. Sınırlama, kendisinden önceki engelleyici aşamanın işini
küçültmez — bunu ancak sıralamanın kendisinin ilk beşi tutan bir biçimi yapabilir ve o da
sıralama aşamasının içinde bir karardır, boru hattının sırasında değil.

#### Özet

- Toplama boru hattı sıralı aşamalardan oluşur; akış aşamaları tek kayıt tutar, engelleyici
  aşamalar (gruplama, sıralama) akışın tamamını ya da grup tablosunu bellekte tutar.
- Dizi açma aşaması kaydı çoğaltır: 20.000 kitap belgesi 59.494 kayda dönüşür.
- Aynı soru üç sırayla aynı yanıtı verir, ama 43.610, 87.838 ve 124.606 kayıt işler; süzmeyi
  çoğaltıcı aşamadan önceye almak işlenen kaydı yarıya indirir.
- Öne konan süzmenin kapsayıcı olması yeter; A'nın belge düzeyi süzmesi 3.340 belge geçirir,
  bunların açılmasından çıkan 11.926 kaydın 3.508'i sonuca kalır.
- Gruplamayı süzmeden önce koymak doruk belleği 2.418 kayıttan 40.276 kayda çıkarır (16,7 kat).

#### Sonraki Adım

Üç boru hattının ortak yanı gözden kaçtı: üçü de ilk aşamada derlemin tamamını, 20.000
belgeyi okudu. Aşama sırası işlenen kaydı ve tutulan belleği değiştirdi, okunan belge
sayısını değiştirmedi. Onu değiştiren tek şey dizindir. Belge modelinde dizin, ilişkisel
kursta ölçülen dizinden yapı olarak ayrılmaz — ama üstüne kurulduğu veri ayrılır: dizi
alanı bir belge için birden çok giriş üretir, gömülü alan noktalı bir yolla adreslenir ve
dizinin yalnız belgelerin bir bölümünü kapsaması istenebilir. Sonraki ders bu üç durumu
kurar ve dizin boyutu ile yazma bedelini sayar.

### Dizinleme

Source: https://academia.sh/tr/kurslar/nosql/dizinleme

Dizinin belge yapısı üzerinde kurulması: bir dizi alanının tek belgeden kaç giriş ürettiği, iki dizi alanını tek bileşik dizinde birleştirmenin giriş sayısını 20.000'den 120.042'ye çıkarması, aynı ögede karşılanması gereken koşulun iki tek alan dizininde 6.033 belge okutup öge düzeyi dizinde 3.340 okutması ve kısmi dizinin aynı yanıtı derlemin %27,8'i yerine %6'sı yer kaplayarak vermesi.

Önceki dersin üç boru hattı aynı yanıtı üç ayrı bedelle üretti, ama ortak bir yanları
vardı: üçü de ilk aşamada derlemin tamamını, 20.000 belgeyi okudu. Aşama sırası işlenen
kaydı ve tutulan belleği değiştirdi, **okunan belge sayısını** değiştirmedi. Onu
değiştiren tek yapı dizindir.

Dizinin kuramı burada kurulmaz. İlişkisel Veritabanı Yönetimi kursunun Dizinler ve
Bölümleme konusu ağaç, karma ve ters dizini, sütun sırasını ve kısmi dizinin kazancını
ölçtü; sıralı bir yapının hangi soruya yanıt verdiği orada sayılara bağlandı ve o sayılar
burada geçerlidir. Bu dersin sorusu dizinin **üstüne kurulduğu veridir**. İlişkisel bir
satırda dizinlenen sütun tek bir değer taşır; belge modelinde aynı alan bir dizi olabilir,
iç içe bir belgenin içinde durabilir ya da belgelerin yalnız bir bölümünde bulunabilir. Üç
durumun üçü de dizin girişinin sayısını değiştirir ve giriş sayısı hem yeri hem yazma
bedelini belirler.

#### Bir Belge, Birden Çok Giriş

Dizin girişi bir anahtar değeri ile o değeri taşıyan belgeye bir başvurudan oluşur.
İlişkisel dizinde bu ilişki birebirdir: bir satır bir giriş üretir. Bir yol dizinin içine
indiğinde ilişki bire çok olur — beş kopyası olan bir kitap, `kopya.sube` dizininde beş
giriş üretir. Buna **çoklu giriş** (multikey) denir ve önceki dersteki varoluşsal kuralın
depolama tarafındaki karşılığıdır: yol birden çok değere ulaşıyorsa, dizin o değerlerin
hepsini ayrı ayrı adreslemek zorundadır.

Aynı belgede aynı anahtarın iki kez üretilmesi durumunda dizin tek giriş tutar; üç kopyası
da Kadıköy'de olan bir kitap `kopya.sube` dizininde bir kez görünür. Aşağıdaki
gerçekleştirim üç kararı da taşır: çoklu giriş, bileşik anahtar ve kısmi koşul.

```js
// dizin.mjs — belge dizini. Bir yol dizinin icine indiginde tek belge birden cok giris
// uretir: coklu giris. Bilesik anahtar, oge duzeyi anahtar ve kismi kosul desteklenir.
// Giris bayti 01. dersin kodlama kuralindan gelir; her giris ayrica belgeye 8 baytlik
// bir basvuru tasir.
const BASVURU = 8;

export function bayt(d) {
  if (d === null || d === undefined) return 0;
  if (typeof d === "boolean") return 1;
  if (typeof d === "number") return Number.isInteger(d) ? 4 : 8;
  if (typeof d === "string") return 4 + Buffer.byteLength(d) + 1;
  const oge = Array.isArray(d) ? d.map((v, i) => [String(i), v]) : Object.entries(d);
  return 5 + oge.reduce((t, [a, v]) => t + 2 + Buffer.byteLength(a) + bayt(v), 0);
}

// Yoldaki degerler: yol bir diziye rastlarsa ogelerine iner.
export function degerler(kok, yol) {
  let kume = [kok];
  for (const ad of yol.split(".")) {
    const sonraki = [];
    for (const d of kume)
      for (const o of Array.isArray(d) ? d : [d])
        if (o && typeof o === "object" && !Array.isArray(o) && ad in o) sonraki.push(o[ad]);
    kume = sonraki;
  }
  return kume.flatMap((v) => (Array.isArray(v) ? v : [v]));
}

export class Dizin {
  // alanlar: anahtari kuran yollar. oge: verilirse anahtar o dizinin OGESI uzerinde
  // kurulur ve yollar ogeye goredir. kosul: verilirse yalniz kosulu saglayan belge ya da
  // oge dizine girer -- kismi dizin.
  constructor(ad, { alanlar, oge = null, kosul = null }) {
    Object.assign(this, { ad, alanlar, oge, kosul });
    this.giris = new Map();                         // anahtar -> belge anahtari dizisi
    this.ham = 0;                                   // tekillestirmeden onceki giris
    this.taranan = 0;
  }
  anahtarlar(belge) {                               // bir belgenin urettigi anahtarlar
    const kaynak = this.oge
      ? degerler(belge, this.oge).filter((o) => !this.kosul || this.kosul(o))
      : !this.kosul || this.kosul(belge) ? [belge] : [];
    const cikan = [];
    for (const k of kaynak) {
      let carpim = [[]];
      for (const alan of this.alanlar) {
        const d = degerler(k, alan);
        carpim = carpim.flatMap((on) => (d.length ? d : [null]).map((v) => [...on, v]));
      }
      cikan.push(...carpim);
    }
    return cikan;
  }
  kur(belgeler) {
    for (const b of belgeler) {
      const uretilen = this.anahtarlar(b).map((a) => JSON.stringify(a));
      this.ham += uretilen.length;
      for (const a of new Set(uretilen)) {           // ayni belgede ayni anahtar bir kez
        if (!this.giris.has(a)) this.giris.set(a, []);
        this.giris.get(a).push(b._k);
      }
    }
    return this;
  }
  get sayi() {
    return [...this.giris.values()].reduce((t, v) => t + v.length, 0);
  }
  get bayt() {
    let n = 0;
    for (const [a, k] of this.giris)
      n += k.length * (JSON.parse(a).reduce((t, v) => t + bayt(v), 0) + BASVURU);
    return n;
  }
  ara(...anahtar) {                                  // tam anahtarla arama
    const k = this.giris.get(JSON.stringify(anahtar)) ?? [];
    this.taranan += k.length;
    return k;
  }
}
```

`oge` seçeneği belge modeline özgü olan ayrımı taşır. `alanlar: ["kopya.sube",
"kopya.durum"]` ile kurulan bileşik dizin iki yolu ayrı ayrı çözer ve değerlerinin
çarpımını alır; `oge: "kopya"` ile kurulan dizin ise anahtarı **her kopyanın kendi
içinde** kurar. İkisi aynı iki alanı taşır ve farklı soruya yanıt verir.

#### Yedi Dizin, Aynı Katalog

**NS7 (varsayım):** katalog 20.000 kitap belgesidir, her kitabın 1–5 kopyası ve 2–4
etiketi vardır, tohum 424242'dir. **NS13 (varsayım):** bir dizin girişi anahtar değerinin
kodlanmış hâline ek olarak 8 baytlık bir belge başvurusu taşır ve düğüm doluluğu tam
sayılır. Gerekçe: başvuru genişliği ile doluluk motora göre değişir; ikisi de dizinler
arasındaki **oranı** değiştirmez, yalnız mutlak baytı ölçekler.

Ölçülen iş, önceki iki dersin sorusudur: Kadıköy şubesinde onarımdaki kopyası olan
kitaplar. Yanıt dört yoldan alınır ve her yolda taranan dizin girişi ile okunan belge
sayılır. Ardından, ikinci dersin üçüncü işi olan 5.000 kopya durumu değişikliği dizin
bakımı açısından iki kararla koşturulur.

```js
// dizin-olcum.mjs — ayni katalog uzerinde yedi dizin kurulur, ayni soru dort yoldan
// yanitlanir ve 5.000 durum degisikliginin dizin bakimi iki kararla sayilir.
// Ayni dizinde dizin.mjs bulunur.
import { Dizin, bayt } from "./dizin.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"];

const KATALOG = [];
for (let i = 1; i <= 20000; i += 1) {
  const kopya = [], etiket = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] });
  for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1)
    etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
  KATALOG.push({ _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), etiket, kopya });
}
const HARITA = new Map(KATALOG.map((b) => [b._k, b]));
const DERLEM = KATALOG.reduce((t, b) => t + bayt(b), 0);
const KOPYA = KATALOG.reduce((t, b) => t + b.kopya.length, 0);
console.log(`katalog ${KATALOG.length} belge, ${KOPYA} kopya, derlem ${DERLEM} bayt`);

const [yazarD, etiketD, subeD, durumD, carpimD, ogeD, kismiD] = [
  new Dizin("yazar", { alanlar: ["yazar"] }),
  new Dizin("etiket", { alanlar: ["etiket"] }),
  new Dizin("kopya.sube", { alanlar: ["kopya.sube"] }),
  new Dizin("kopya.durum", { alanlar: ["kopya.durum"] }),
  new Dizin("etiket + kopya.sube", { alanlar: ["etiket", "kopya.sube"] }),
  new Dizin("oge(sube,durum)", { alanlar: ["sube", "durum"], oge: "kopya" }),
  new Dizin("kismi oge(sube)", { alanlar: ["sube"], oge: "kopya",
    kosul: (o) => o.durum === "onarimda" }),
].map((d) => d.kur(KATALOG));
const DIZINLER = [yazarD, etiketD, subeD, durumD, carpimD, ogeD, kismiD];

for (const d of DIZINLER)
  console.log(`${d.ad.padEnd(19)} ham ${String(d.ham).padStart(6)}` +
    `  giris ${String(d.sayi).padStart(6)}  belge basina ${(d.sayi / 20000).toFixed(2)}` +
    `  ${String(d.bayt).padStart(7)} bayt  derlemin %${(100 * d.bayt / DERLEM).toFixed(1)}`);

// Ayni soru: Kadikoy subesinde onarimdaki kopyasi olan kitaplar.
const OGE = (k) => k.sube === "Kadikoy" && k.durum === "onarimda";
const dogru = KATALOG.filter((b) => b.kopya.some(OGE)).length;
const suz = (anahtarlar) => anahtarlar.filter((k) => HARITA.get(k).kopya.some(OGE)).length;
console.log(`kosulu saglayan kopya ${KATALOG.reduce((t, b) => t + b.kopya.filter(OGE).length, 0)}` +
  `, bu kopyalari tasiyan belge ${dogru}`);

const kesisim = new Set(subeD.ara("Kadikoy"));
const B = durumD.ara("onarimda").filter((k) => kesisim.has(k));
const C = ogeD.ara("Kadikoy", "onarimda");
const D = kismiD.ara("Kadikoy");
const yollar = [
  ["A tarama", 0, KATALOG.map((b) => b._k)],
  ["B iki tek alan dizini", subeD.taranan + durumD.taranan, B],
  ["C oge duzeyi bilesik", ogeD.taranan, C],
  ["D kismi oge dizini", kismiD.taranan, D],
];
for (const [ad, taranan, kume] of yollar)
  console.log(`${ad.padEnd(22)} taranan giris ${String(taranan).padStart(6)}` +
    `  okunan belge ${String(kume.length).padStart(5)}  sonuc ${suz(kume)}`);

// 5.000 kopyanin durumu degisir: dizin bakimi iki kararla sayilir.
const anahtarKumesi = (d, b) => [...new Set(d.anahtarlar(b).map((a) => JSON.stringify(a)))];
const fark = (a, b) => a.filter((v) => !b.includes(v)).length
  + b.filter((v) => !a.includes(v)).length;
let farkli = 0, tam = 0, gercek = 0;
for (let i = 0; i < 5000; i += 1) {
  const kitap = KATALOG[(i * 7) % KATALOG.length];
  const hedef = kitap.kopya[i % kitap.kopya.length];
  const eski = DIZINLER.map((d) => anahtarKumesi(d, kitap));
  if (hedef.durum !== "rafta") gercek += 1;
  hedef.durum = "rafta";
  const yeni = DIZINLER.map((d) => anahtarKumesi(d, kitap));
  for (let j = 0; j < DIZINLER.length; j += 1) {
    farkli += fark(eski[j], yeni[j]);
    tam += eski[j].length + yeni[j].length;
  }
}
console.log(`5.000 durum degisikliginin ${gercek} tanesi degeri gercekten degistiriyor`);
console.log(`  fark bakimi             ${farkli} giris yazmasi`);
console.log(`  belge yeniden dizinleme ${tam} giris yazmasi, ${(tam / farkli).toFixed(1)} kat`);
```

```
katalog 20000 belge, 59494 kopya, derlem 6388114 bayt
yazar               ham  20000  giris  20000  belge basina 1.00   454450 bayt  derlemin %7.1
etiket              ham  59730  giris  50179  belge basina 2.51   911722 bayt  derlemin %14.3
kopya.sube          ham  59494  giris  47982  belge basina 2.40   976310 bayt  derlemin %15.3
kopya.durum         ham  59494  giris  38997  belge basina 1.95   766544 bayt  derlemin %12.0
etiket + kopya.sube ham 177480  giris 120042  belge basina 6.00  3664167 bayt  derlemin %57.4
oge(sube,durum)     ham  59494  giris  55516  belge basina 2.78  1777092 bayt  derlemin %27.8
kismi oge(sube)     ham  20021  giris  18752  belge basina 0.94   381997 bayt  derlemin %6.0
kosulu saglayan kopya 3508, bu kopyalari tasiyan belge 3340
A tarama               taranan giris      0  okunan belge 20000  sonuc 3340
B iki tek alan dizini  taranan giris  21248  okunan belge  6033  sonuc 3340
C oge duzeyi bilesik   taranan giris   3340  okunan belge  3340  sonuc 3340
D kismi oge dizini     taranan giris   3340  okunan belge  3340  sonuc 3340
5.000 durum degisikliginin 3311 tanesi degeri gercekten degistiriyor
  fark bakimi             10840 giris yazmasi
  belge yeniden dizinleme 173994 giris yazmasi, 16.1 kat
```

#### Giriş Sayısının Bedeli

İlk satırlar dizin boyutunun kaynağını verir. `yazar` dizini belge başına tam bir giriş
üretir ve derlemin %7,1'i kadar yer tutar; bu, ilişkisel bir dizinin oranıdır. `etiket`
dizini 59.730 ham giriş üretir, tekilleştirmeden sonra 50.179 kalır — belge başına 2,51.
Aradaki 9.551 giriş aynı etiketin bir kitapta iki kez yazılmasından gelir ve dizin bunu
tek girişe indirir. `kopya.durum` dizininde tekilleştirme daha da güçlüdür: 59.494 ham
giriş 38.997'ye iner, çünkü üç değerli bir alanda bir kitabın birden çok kopyasının aynı
durumda olması sıradandır.

Beşinci satır dersin en sert sayısıdır. `etiket` ile `kopya.sube` tek bir bileşik dizinde
birleştirildiğinde giriş sayısı toplam değil **çarpım** olur: 177.480 ham giriş, 120.042
tekil giriş, belge başına altı. Dizin 3.664.167 bayt tutar, yani derlemin %57,4'ü. İki
dizi alanının aynı bileşik dizinde bulunmasının motorlar tarafından engellenmesinin
nedeni budur; engel bir kolaylık değil, bu çarpımın önlenmesidir. Aynı iki alan ayrı iki
dizinde tutulduğunda toplam 98.161 giriş ve 1.888.032 bayt eder — yarısından az.

#### Aynı Ögede Olma Koşulu

Dört yol aynı 3.340 belgeyi döndürür; ayrıldıkları yer bu yanıta ulaşmak için okunan
belgedir. Tarama 20.000 belge okur. İki tek alan dizininin kesişimi 21.248 giriş tarar ve
6.033 belge okur — üçüncü dersin belge düzeyi bağlaç sayısının tam olarak kendisi. Dizin
kesişimi "Kadıköy'de kopyası olan" ile "onarımda kopyası olan" kümelerini keser ve
koşulların **aynı kopyada** karşılandığını doğrulayamaz; o doğrulamayı belgeyi okuyarak
yapmak zorundadır. Fazladan okunan 2.693 belge bu yüzden okunur.

Öge düzeyi bileşik dizin bu işi dizinin içinde yapar. Anahtar her kopyanın kendi şubesi ve
durumundan kurulduğu için `("Kadikoy", "onarimda")` araması doğrudan 3.340 belge
başvurusu verir; taranan giriş sayısı da 3.340'tır. Koşulu sağlayan kopya sayısı 3.508'dir
— aradaki 168 kopya, aynı kitabın aynı şubede onarımdaki ikinci kopyasıdır ve dizin bunları
tek girişe indirmiştir. Okunan belge sayısı taramaya göre altıda bire, dizin kesişimine
göre 1,8 kat düşer ve okunan her belge sonuca kalır.

Bedeli boyuttur: bu dizin 1.777.092 bayt, derlemin %27,8'i. Kısmi dizin aynı yanıtı çok
daha ucuza verir. `durum` alanı `onarimda` olan kopyalar 59.494'ün 20.021'idir; yalnız
onları kapsayan dizin 18.752 giriş ve 381.997 bayt tutar, derlemin %6'sı. Aynı arama aynı
3.340 belgeyi aynı sayıda giriş tarayarak döndürür. Kısıtı, kapsadığı koşulun sorguda
bulunmasıdır: bu dizin "Kadıköy'de raftaki kopyalar" sorusuna hiçbir şey söyleyemez, çünkü
o kopyalar dizinde yoktur.

#### Yazmanın Bedeli

Son üç satır dizinin öteki yüzüdür. 5.000 kopya durum değişikliğinin 3.311'i değeri
gerçekten değiştirir. Değişen anahtarları hesaplayıp yalnız onları yazan bakım yedi dizin
için 10.840 giriş yazması yapar — denenen değişiklik başına 2,17. Aynı işi belgenin bütün
girişlerini silip yeniden üreterek yapan bakım 173.994 giriş yazması yapar, 16,1 kat.
Fark ikinci dersin bulgusuyla aynı kökten gelir: gömülü şemada bir kopyanın durumu
değiştiğinde kitabın tamamı yeniden yazılır ve yeniden yazılan bir belgenin bütün dizin
girişlerini yeniden üretmek en kolay uygulamadır. Depo hangi alanın değiştiğini biliyorsa
bu 16 katın tamamından kaçınır; bilmiyorsa dizin sayısı arttıkça yazma bedeli doğrusal
büyür.

Dizin başına düşen paya bakıldığında karar netleşir: yedi dizinin toplamı 8.932.282 bayt,
derlemin 1,4 katı. Belge modelinde dizin, alan başına değil **giriş başına** ücretlendirilir
ve giriş sayısını belirleyen şey şemanın kendisidir.

#### Özet

- Bir yol dizinin içine indiğinde tek belge birden çok dizin girişi üretir; `kopya.sube`
  dizini 20.000 belgeden 59.494 ham, tekilleştirmeden sonra 47.982 giriş üretir.
- İki dizi alanını tek bileşik dizinde birleştirmek girişleri toplamaz, çarpar: 120.042
  giriş ve derlemin %57,4'ü kadar yer.
- İki tek alan dizininin kesişimi koşulların aynı ögede karşılandığını doğrulayamaz; 6.033
  belge okur, bunların 2.693'ü sonuca kalmaz.
- Öge düzeyi bileşik dizin aynı yanıtı 3.340 giriş tarayıp 3.340 belge okuyarak verir;
  kısmi dizin aynı yanıtı derlemin %27,8'i yerine %6'sı kadar yer kaplayarak verir.
- Değişen anahtarı hesaplayan bakım 5.000 güncellemeye 10.840 giriş yazması harcar, belgeyi
  bütünüyle yeniden dizinleyen bakım 173.994 (16,1 kat).

#### Sonraki Adım

Bu dersteki bütün dizinler var olan alanların üstüne kuruldu ve alanların ne taşıdığına
hiç bakmadı. `kopya.durum` dizini `onarimda` yerine `onarım` yazılmış bir kopyayı da
sessizce dizinler; birinci dersin ölçtüğü metin yazılmış yayın yılları da `yayin_yili`
dizinine kendi tipleriyle girer. Esnek şema, alanın bulunup bulunmamasını ve tipini yazma
anında bağlamaz — birinci ders bunun aralık sorgusunda 3.990 yerine 5.991 ya da 3.491
belge döndürdüğünü saymıştı ve kuralın nasıl konacağını bu derse bırakmıştı. Sonraki ders
o kuralı yazar ve iki sayıyı ölçer: kuralın yakaladığı bozuk belge sınıfı ile kaçırdığı
sınıf ve kural derlem doluyken konduğunda ortaya çıkan ihlal sayısıyla geriye dönük
düzeltmenin dokunduğu belge sayısı.

### Şema Doğrulama

Source: https://academia.sh/tr/kurslar/nosql/sema-dogrulama

Esnek şemaya kural eklemenin iki ölçüsü: yüzeysel bir kuralın dört bozuk belge sınıfının ikisini yakalayıp ikisini kaçırması, aynı kuralın dolu bir derleme sonradan konduğunda 3.200 belgede 3.900 ihlal üretmesi, geriye dönük düzeltmenin 3.000 ihlali makineyle kapatıp 900'ünü karara bırakması ve düzeltilmemiş derlemde sıradan 5.000 güncellemenin 785'inin reddedilmesi.

Önceki dersin bütün dizinleri var olan alanların üstüne kuruldu ve alanların ne taşıdığına
hiç bakmadı: `kopya.durum` dizini sözlükte olmayan bir değeri de sessizce dizinler, birinci
dersin ölçtüğü metin yazılmış yayın yılları da kendi tipleriyle girer. Esnek şema alanın
bulunup bulunmadığını ve tipini yazma anında bağlamaz; birinci ders bunun bir aralık
sorgusunda 3.990 yerine 5.991 ya da 3.491 belge döndürdüğünü saymış ve kuralın nasıl
konacağını bu derse bırakmıştı.

Kural konabilir. **Şema doğrulama** (schema validation), bir derleme bağlanan ve yazma
anında değerlendirilen bir kural kümesidir. Bu ders kuralın iki ölçüsünü üretir: kuralın
yakaladığı bozuk belge sınıfı ile kaçırdığı sınıf ve kural dolu bir derleme sonradan
konduğunda ortaya çıkan ihlal sayısı ile geriye dönük düzeltmenin bedeli.

#### Kuralın Kapsamı

Bir doğrulama kuralı üç ayrı kararı taşır. Birincisi **kapsamdır**: kural yalnız üst düzey
alanlara mı bakar, yoksa iç içe belgelere ve dizi ögelerine iner mi. İkincisi **kapalılık**:
kuralda sayılmayan bir alanın belgede bulunması ihlal sayılır mı. Üçüncüsü **kip**: ihlalli
yazma reddedilir mi, yoksa yazılıp sayılır mı. Üç kararın üçü de ölçülebilir sonuç üretir.

```js
// dogrulayici.mjs — derleme baglanan kural kumesi. Kural yazma aninda degerlendirilir ve
// ihlalleri sinifiyla birlikte dondurur: eksik alan, yanlis tip, sozluk disi deger,
// kuralda bulunmayan alan. Kural ic ice belgelere ve dizi ogelerine inebilir.

export function tipAdi(d) {
  if (d === null) return "bos";
  if (Array.isArray(d)) return "dizi";
  if (typeof d === "number") return Number.isInteger(d) ? "tam" : "ondalik";
  return typeof d === "string" ? "metin" : "belge";
}

export function bayt(d) {                        // 01. dersin kodlama kurali
  if (d === null || d === undefined) return 0;
  if (typeof d === "number") return Number.isInteger(d) ? 4 : 8;
  if (typeof d === "string") return 4 + Buffer.byteLength(d) + 1;
  const oge = Array.isArray(d) ? d.map((v, i) => [String(i), v]) : Object.entries(d);
  return 5 + oge.reduce((t, [a, v]) => t + 2 + Buffer.byteLength(a) + bayt(v), 0);
}

// kural: { alanlar: { ad: { zorunlu, tip, sozluk, oge } }, kapali }
// kapali=true ise alanlar listesinde bulunmayan her alan ihlaldir.
export function dogrula(belge, kural, yol = "") {
  const ihlal = [];
  for (const [ad, k] of Object.entries(kural.alanlar)) {
    const d = belge[ad];
    if (d === undefined) {
      if (k.zorunlu) ihlal.push({ sinif: "eksik", alan: yol + ad });
      continue;
    }
    if (tipAdi(d) !== k.tip) {
      ihlal.push({ sinif: "tip", alan: yol + ad });
      continue;
    }
    if (k.sozluk && !k.sozluk.includes(d)) ihlal.push({ sinif: "sozluk", alan: yol + ad });
    if (k.oge) for (const o of d) ihlal.push(...dogrula(o, k.oge, `${yol}${ad}.`));
  }
  if (kural.kapali)
    for (const ad of Object.keys(belge))
      if (ad !== "_k" && !(ad in kural.alanlar)) ihlal.push({ sinif: "fazla", alan: yol + ad });
  return ihlal;
}

export class Derlem {
  // kip: "reddet" ihlalli yazmayi geri cevirir, "uyar" yazar ve sayar, null kural yoksa.
  constructor(kural = null, kip = "reddet") {
    Object.assign(this, { kural, kip, belge: new Map() });
    this.o = { yazilan: 0, reddedilen: 0, uyarilan: 0, yazilanBayt: 0 };
  }
  yaz(b) {
    const ihlal = this.kural ? dogrula(b, this.kural) : [];
    if (ihlal.length) {
      if (this.kip === "reddet") {
        this.o.reddedilen += 1;
        return false;
      }
      this.o.uyarilan += 1;
    }
    this.belge.set(b._k, b);
    this.o.yazilan += 1;
    this.o.yazilanBayt += bayt(b);
    return true;
  }
  denetle() {                                    // dolu derlemin kurala gore taranmasi
    const sayim = new Map();
    let bozuk = 0;
    for (const b of this.belge.values()) {
      const ihlal = dogrula(b, this.kural);
      if (ihlal.length) bozuk += 1;
      for (const i of ihlal) sayim.set(i.sinif + " " + i.alan,
        (sayim.get(i.sinif + " " + i.alan) ?? 0) + 1);
    }
    return { bozuk, sayim };
  }
}
```

#### Dört Bozuk Sınıf, İki Kural

Ölçüm önceki derslerin bıraktığı bozukluklarla yapılır. **NS7 (varsayım):** katalog 20.000
kitap belgesidir, tohum 424242'dir. **NS8 (varsayım):** kayıtların sekizde birinde yayın
yılı metin olarak yazılmıştır. **NS12 (varsayım):** kayıtların kırkta birinde yayın yılı
alanı hiç yoktur. **NS28 (varsayım):** dış kaynaktan alınan kayıtların ellide birinde bir
kopyanın durumu sözlükte bulunmayan bir değerle yazılmıştır ve yirmide birinde yayın yılı
ikinci bir alan adıyla tekrarlanmıştır. Gerekçe: dört sınıfın dördü de aynı kaynaktan
gelir — alanın adını, tipini ve değer kümesini bağlayan bir tanımın bulunmaması.

Kural iki biçimde kurulur. A yüzeyseldir: yalnız üst düzey alanlara bakar, kapalı değildir.
B öge düzeyine iner ve kapalıdır. İkisi de aynı derlem üzerinde koşturulur.

```js
// kural-olcum.mjs — ayni katalog iki kuralla denetlenir: yuzeysel kural yalniz ust duzey
// alanlara bakar, tam kural dizi ogelerine iner ve kuralda bulunmayan alani yasaklar.
// Yakalanan ve kacirilan bozuk belge siniflari, kuralin dolu derleme sonradan konmasi ve
// geriye donuk duzeltme sayilir. Ayni dizinde dogrulayici.mjs bulunur.
import { Derlem, dogrula, bayt } from "./dogrulayici.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const DURUM = ["rafta", "oduncte", "onarimda"];
const ETIKETLER = ["roman", "tarih", "cocuk", "siir", "bilim", "basvuru"];

const KATALOG = [];
for (let i = 1; i <= 20000; i += 1) {
  const kopya = [], etiket = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * 6)], durum: DURUM[Math.floor(rast() * 3)] });
  for (let j = 0, n = 2 + Math.floor(rast() * 3); j < n; j += 1)
    etiket.push(ETIKETLER[Math.floor(rast() * 6)]);
  const b = { _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), etiket, kopya };
  if (i % 8 === 0) b.yayin_yili = String(b.yayin_yili);  // NS8: yil metin yazilmis
  if (i % 40 === 0) delete b.yayin_yili;                 // NS12: alan hic yok
  if (i % 50 === 0) b.kopya[0].durum = "kayip";          // NS28: sozluk disi durum
  if (i % 20 === 0) b.yayinYili = 1950 + (i % 75);       // NS28: alan ikinci adla tekrar
  KATALOG.push(b);
}

const ALAN = {
  yazar: { zorunlu: true, tip: "metin" },
  yayin_yili: { zorunlu: true, tip: "tam" },
  etiket: { zorunlu: true, tip: "dizi" },
};
const KURAL_A = { alanlar: ALAN };                       // yuzeysel: ust duzey alanlar
const KURAL_B = {                                        // tam: ogelere iner, kapalidir
  alanlar: { ...ALAN, kopya: { zorunlu: true, tip: "dizi", oge: {
    alanlar: { barkod: { zorunlu: true, tip: "metin" },
      sube: { zorunlu: true, tip: "metin", sozluk: SUBE },
      durum: { zorunlu: true, tip: "metin", sozluk: DURUM } }, kapali: true } } },
  kapali: true,
};

const SINIF = {
  "eksik yil": (b) => b.yayin_yili === undefined,
  "metin yil": (b) => typeof b.yayin_yili === "string",
  "sozluk disi durum": (b) => b.kopya.some((k) => !DURUM.includes(k.durum)),
  "yinelenen alan": (b) => "yayinYili" in b,
};
console.log(`katalog ${KATALOG.length} belge, ${KATALOG.reduce((t, b) => t + bayt(b), 0)} bayt`);
for (const [ad, f] of Object.entries(SINIF))
  console.log(`  ${ad.padEnd(18)} ${KATALOG.filter(f).length} belge`);
console.log(`  ${"tekil bozuk belge".padEnd(18)} ` +
  `${KATALOG.filter((b) => Object.values(SINIF).some((f) => f(b))).length}`);

// Her kural hangi sinifi yakaliyor.
for (const [ad, kural] of [["A yuzeysel", KURAL_A], ["B tam", KURAL_B]]) {
  const satir = Object.entries(SINIF).map(([sad, f]) => {
    const kume = KATALOG.filter(f);
    return `${sad} ${kume.filter((b) => dogrula(b, kural).length > 0).length}/${kume.length}`;
  });
  console.log(`${ad.padEnd(11)} yakalanan ` +
    `${KATALOG.filter((b) => dogrula(b, kural).length > 0).length} belge   ${satir.join("  ")}`);
}

// Kural dolu derleme sonradan konuyor: once var olan belgelerdeki ihlal sayilir.
const derlem = new Derlem(KURAL_B, "uyar");
for (const b of KATALOG) derlem.yaz(b);
const { bozuk, sayim } = derlem.denetle();
console.log(`kural sonradan konunca ${bozuk} belge ihlalli, ` +
  `${[...sayim.values()].reduce((t, v) => t + v, 0)} ihlal:`);
for (const [ad, n] of [...sayim].sort((a, b) => b[1] - a[1])) console.log(`  ${ad.padEnd(22)} ${n}`);

// Kural "reddet" kipinde, derlem duzeltilmeden: siradan bir guncelleme ne yapar.
function guncelle(kip) {
  const d = new Derlem(KURAL_B, kip);
  for (let i = 0; i < 5000; i += 1) {
    const kitap = KATALOG[(i * 7) % KATALOG.length];
    const yeni = { ...kitap, kopya: kitap.kopya.map((k) => ({ ...k })) };
    yeni.kopya[i % yeni.kopya.length].durum = "rafta";
    d.yaz(yeni);
  }
  return d.o;
}
const once = guncelle("reddet");
console.log(`duzeltilmemis derlemde 5.000 durum guncellemesi: ` +
  `${once.yazilan} kabul, ${once.reddedilen} red`);

// Geriye donuk duzeltme: hangi ihlal makineyle kapanir, hangisi karar ister.
let makine = 0, karar = 0, dokunulan = 0, yazilanBayt = 0;
for (const b of KATALOG) {
  if (dogrula(b, KURAL_B).length === 0) continue;
  if (typeof b.yayin_yili === "string") { b.yayin_yili = Number(b.yayin_yili); makine += 1; }
  if ("yayinYili" in b) { delete b.yayinYili; makine += 1; }
  if (b.yayin_yili === undefined) karar += 1;
  karar += b.kopya.filter((k) => !DURUM.includes(k.durum)).length;
  dokunulan += 1;
  yazilanBayt += bayt(b);
}
console.log(`geriye donuk duzeltme ${dokunulan} belgeye dokundu, ${yazilanBayt} bayt yazdi`);
console.log(`  makineyle kapanan ihlal ${makine}, karar isteyen ihlal ${karar}`);
console.log(`  duzeltmeden sonra ihlalli belge ${derlem.denetle().bozuk}`);
const sonra = guncelle("reddet");
console.log(`duzeltilmis derlemde ayni 5.000 guncelleme: ` +
  `${sonra.yazilan} kabul, ${sonra.reddedilen} red`);
```

```
katalog 20000 belge, 6404466 bayt
  eksik yil          500 belge
  metin yil          2000 belge
  sozluk disi durum  400 belge
  yinelenen alan     1000 belge
  tekil bozuk belge  3200
A yuzeysel  yakalanan 2500 belge   eksik yil 500/500  metin yil 2000/2000  sozluk disi durum 100/400  yinelenen alan 500/1000
B tam       yakalanan 3200 belge   eksik yil 500/500  metin yil 2000/2000  sozluk disi durum 400/400  yinelenen alan 1000/1000
kural sonradan konunca 3200 belge ihlalli, 3900 ihlal:
  tip yayin_yili         2000
  fazla yayinYili        1000
  eksik yayin_yili       500
  sozluk kopya.durum     400
duzeltilmemis derlemde 5.000 durum guncellemesi: 4215 kabul, 785 red
geriye donuk duzeltme 3200 belgeye dokundu, 1012270 bayt yazdi
  makineyle kapanan ihlal 3000, karar isteyen ihlal 900
  duzeltmeden sonra ihlalli belge 800
duzeltilmis derlemde ayni 5.000 guncelleme: 4821 kabul, 179 red
```

#### Yakalanan ve Kaçırılan

İlk beş satır bozukluğun dağılımını verir ve bir yan bulgu taşır: metin yazılmış yıl 2.500
değil 2.000 belgededir. Sekizde bir metin yazılmış kayıtların beşte biri aynı zamanda yıl
alanını hiç taşımayan kayıtlardır ve alan yoksa tipi de yoktur. İki bozukluk sınıfı
birbirini örter; sınıfların toplamı 3.900, ayrı belge sayısı 3.200'dür. Bir doğrulama
raporunda ihlal sayısıyla belge sayısının farklı olmasının nedeni budur.

İki kural aynı derlemde farklı sayı üretir. A yüzeysel kuralı 2.500 belge yakalar, B tam
kuralı 3.200. Fark, kuralın kapsamındadır ve satır satır okunabilir. Yıl sınıflarında iki
kural aynıdır: 500/500 ve 2.000/2.000. Sözlük dışı durum sınıfında A yalnız 400'ün 100'ünü
yakalar — ve bu 100 belge, kopya durumu yüzünden değil, aynı belgede bulunan bir yıl
ihlali yüzünden yakalanmıştır. Kuralın gerçekten kaçırdığı sınıf tamdır: A kuralı bir dizi
ögesinin içine hiç bakmaz. Yinelenen alan sınıfında da aynı yanılgı vardır: A 1.000'in
500'ünü yakalar, hepsi başka bir ihlal sayesinde.

Buradan kuralın okunma biçimi çıkar. "Kural 2.500 belge yakaladı" cümlesi bir kapsama
ölçüsü değildir; kapsama ancak sınıf sınıf sorulduğunda görünür. Yüzeysel kural, belge
modelinin en çok kullanılan yapısına — dizi içindeki iç içe belgeye — hiç dokunmaz ve
kapalı olmadığı için yanlış adla yazılmış bir alanı ihlal saymaz. Yanlış adla yazılan
alan sessiz bir sınıftır: sorgu onu okumaz, dizin onu dizinlemez, kural onu görmez.

#### Kuralın Geç Konması

Kural boş bir derleme konduğunda bedeli yalnız yazma yolundaki denetimdir. Dolu bir
derleme konduğunda üç ayrı sayı doğar. Birincisi ihlal sayısıdır: 3.200 belge, 3.900
ihlal. İkincisi, kural `reddet` kipinde ve derlem düzeltilmemişken sıradan güncellemelerin
başına gelendir. İkinci dersin üçüncü işi olan 5.000 kopya durumu değişikliği bu derlemde
koşturulduğunda 785'i reddedilir. Reddedilen yazmaların hiçbiri yeni bir bozukluk
getirmiyordu — durumu `rafta` yapmak istiyorlardı. Reddedilme nedenleri, gömülü şemada
güncellemenin belgenin tamamını yeniden yazması ve yeniden yazılan belgenin kendi eski
ihlalini de taşımasıdır. Kural, geçmişi düzeltmeden konduğunda bugünün işini durdurur.

Üçüncü sayı düzeltmenin kendisidir. 3.200 belge okunur ve yeniden yazılır: 1.012.270 bayt,
derlemin %15,8'i. Düzeltmenin 3.000 ihlali makineyle kapanır — metin yıl sayıya
çevrilebilir, yinelenen alan silinebilir. Kalan 900 ihlal karar ister: yılı hiç bulunmayan
500 belgenin yılı türetilemez, sözlük dışı 400 durumun ne anlama geldiği ise ancak
kütüphanenin kendisine sorularak bilinir — `kayip` bir yazım hatası mı, yoksa sözlüğe
eklenmesi gereken gerçek bir durum mu. Düzeltmeden sonra 800 belge hâlâ ihlallidir ve aynı
5.000 güncellemenin reddedilen sayısı 785'ten 179'a iner.

Kararın kuralı bu üç sayıdan çıkar. Doğrulama kuralı bir şema tanımı değil, bir yazma yolu
denetimidir; kapsamı sınıf sınıf ölçülür ve dolu bir derleme konurken sırası şudur: önce
`uyar` kipinde ölç, sonra geriye dönük düzelt, sonra `reddet` kipine geç. Ters sıra,
düzeltilmemiş belgelere dokunan her işi durdurur.

#### Özet

- Şema doğrulama derleme bağlanan ve yazma anında değerlendirilen bir kuraldır; kapsamı,
  kapalılığı ve kipi ayrı ayrı seçilir.
- Yüzeysel kural dört bozuk sınıfın ikisini tam yakalar, dizi ögesindeki sözlük ihlalinin
  400'ünün 100'ünü ve yinelenen alanın 1.000'inin 500'ünü yakalar; bu belgeler de kendi
  sınıfları yüzünden değil, aynı belgedeki başka bir ihlal yüzünden yakalanır.
- İhlal sayısı ile belge sayısı farklıdır: 3.900 ihlal 3.200 belgeye dağılır, çünkü
  bozukluk sınıfları birbirini örter.
- Kural dolu derleme `reddet` kipinde konduğunda 5.000 sıradan durum güncellemesinin 785'i
  reddedilir; reddedilen yazmalar yeni bir bozukluk getirmiyordu.
- Geriye dönük düzeltme 3.200 belgeye dokunur ve 1.012.270 bayt yazar; 3.000 ihlal
  makineyle kapanır, 900'ü karar ister ve 800 belge ihlalli kalır, red sayısı 179'a iner.

#### Sonraki Adım

Geriye dönük düzeltme 3.200 belgeye tek tek dokundu ve bu, konunun şimdiye kadar
söylenmemiş bir varsayımını görünür kılıyor: her yazma tek bir belgeyi kapsıyordu. Belge
modelinin atomiklik güvencesi de tam olarak buradadır — bir belgeye yapılan yazma ya
bütünüyle olur ya hiç olmaz, ama iki belgeye yapılan iki yazma arasında böyle bir bağ
yoktur. Düzeltme yarıda kesilseydi derlemin bir bölümü düzelmiş, bir bölümü düzelmemiş
olurdu ve hangi bölümün hangisi olduğunu söyleyen bir kayıt bulunmazdı. Aynı soru
kütüphanenin günlük işinde de vardır: bir ödünç verme işlemi kopyanın durumunu, üyenin
ödünç sayısını ve ödünç kaydını birlikte değiştirir. Sonraki ders tek belge atomikliğinin
bu işe yettiği ve yetmediği durumları aynı işi iki şemada koşturarak sayar ve çok belgeli
işlemin bedelini ölçer.

### İşlemler

Source: https://academia.sh/tr/kurslar/nosql/islemler

Tek belge atomikliğinin yettiği ve yetmediği durumların sayılması: aynı kopya için iki eş zamanlı ödünç, yirmi adım sıralamasının hepsinde koşturulduğunda gömülü düzende hiç ihlal üretmiyor, referanslı düzende yirmi sıralamanın on ikisinde iki açık ödünç doğuruyor, çok belgeli işlem bu on ikiyi kapatırken on sekiz iptal ve 604.903 baytlık geri alma imgesi ödüyor.

Önceki dersin geriye dönük düzeltmesi 3.200 belgeye tek tek dokundu ve konunun şimdiye
kadar söylenmemiş bir varsayımını görünür kıldı: her yazma tek bir belgeyi kapsıyordu.
Belge modelinin atomiklik güvencesi de tam olarak buradadır. **Tek belge atomikliği**
(single-document atomicity), bir belgeye yapılan yazmanın ya bütünüyle ya hiç
gerçekleşmesidir — belge kaç alan, kaç iç içe belge ve kaç dizi ögesi taşırsa taşısın. İki
belgeye yapılan iki yazma arasında böyle bir bağ yoktur.

Bu dersin sorusu, kütüphanenin günlük işinin bu güvenceye sığıp sığmadığıdır. Ödünç verme
işi bir kopyanın durumunu değiştirir ve bir ödünç kaydı yazar. İki değişikliğin arasında
korunması gereken bir değişmez vardır: bir kopya aynı anda birden çok açık ödünçte olamaz.
Bu değişmezin tek bir belgenin içine sığıp sığmaması, şemanın kararına bağlıdır.

#### Bölünmez Yazma ve Çok Belgeli İşlem

Tek belge atomikliğinin sorgu tarafındaki karşılığı **koşullu yazmadır**: okuma, denetim
ve yazma bölünmez tek bir adımdır. İki iş aynı belgeye aynı anda koşullu yazma denerse
biri kazanır, öteki koşulun artık sağlanmadığını görür ve hiçbir şey yazmaz.

**Çok belgeli işlem** (multi-document transaction) aynı güvenceyi birden çok belgeye
yayar. Bedeli üç kalemdir: dokunulan her belge için bir **geri alma imgesi** tutulur,
belgeler işlem boyunca kilitli kalır ve kilidi başkasının elinde bulan işlem iptal edilip
baştan denenir. İşlemlerin, yalıtım düzeylerinin ve kilitlemenin kuramı Veri Modelleme ve
İlişkisel Kuram ile İlişkisel Veritabanı Yönetimi kurslarında kuruldu; burada ölçülen şey
**kapsamdır** — güvencenin kaç belgeyi sardığı ve bunun neye mal olduğu.

```js
// islem.mjs — tek belge atomikligi ve cok belgeli islem. Tek belge yazmasi kosulludur ve
// bolunmez: okuma, denetim ve yazma tek adimdir. Cok belgeli islem dokundugu her belgeyi
// okurken kilitler, yazmayi hemen uygular ama geri alma imgesini saklar; kilit baskasinin
// elindeyse islem iptal edilir ve imgeler geri yazilir.

export function bayt(d) {                          // 01. dersin kodlama kurali
  if (d === null || d === undefined) return 0;
  if (typeof d === "boolean") return 1;
  if (typeof d === "number") return Number.isInteger(d) ? 4 : 8;
  if (typeof d === "string") return 4 + Buffer.byteLength(d) + 1;
  const oge = Array.isArray(d) ? d.map((v, i) => [String(i), v]) : Object.entries(d);
  return 5 + oge.reduce((t, [a, v]) => t + 2 + Buffer.byteLength(a) + bayt(v), 0);
}

export class Depo {
  constructor() {
    this.derlem = new Map();
    this.kilit = new Map();
    this.o = { yazilanBelge: 0, yazilanBayt: 0, geriAlmaBayt: 0, iptal: 0 };
  }
  d(ad) {
    if (!this.derlem.has(ad)) this.derlem.set(ad, new Map());
    return this.derlem.get(ad);
  }
  bul(ad, k) {                                     // okuma belgenin bir kopyasini verir
    const b = this.d(ad).get(k);
    return b === undefined ? undefined : structuredClone(b);
  }
  yaz(ad, b) {
    this.o.yazilanBelge += 1;
    this.o.yazilanBayt += bayt(b);
    this.d(ad).set(b._k, b);
  }
  // Tek belge atomikligi: oku-denetle-yaz bolunmez. Kosul saglanmazsa hicbir sey olmaz.
  kosulluYaz(ad, k, kosul, uygula) {
    const b = this.bul(ad, k);
    if (b === undefined || !kosul(b)) return false;
    this.yaz(ad, uygula(b));
    return true;
  }
}

export class Islem {
  constructor(depo) {
    Object.assign(this, { depo, kilitli: [], imge: [] });
  }
  kilitle(ad, k) {
    const a = `${ad}/${k}`, sahip = this.depo.kilit.get(a);
    if (sahip === this) return true;
    if (sahip) return false;                       // baskasinin elinde: catisma
    this.depo.kilit.set(a, this);
    this.kilitli.push(a);
    return true;
  }
  oku(ad, k) {                                     // kilitli okuma
    return this.kilitle(ad, k) ? this.depo.bul(ad, k) : this.geriAl();
  }
  yaz(ad, b) {
    if (!this.kilitle(ad, b._k)) return this.geriAl();
    const eski = this.depo.d(ad).get(b._k);
    this.imge.push([ad, b._k, eski]);
    this.depo.o.geriAlmaBayt += bayt(eski);
    this.depo.yaz(ad, b);
    return true;
  }
  isle() {
    this.birak();
    return true;
  }
  geriAl() {                                       // imgeler ters sirada geri yazilir
    for (const [ad, k, eski] of [...this.imge].reverse())
      if (eski === undefined) this.depo.d(ad).delete(k);
      else this.depo.d(ad).set(k, eski);
    this.depo.o.iptal += 1;
    this.birak();
    return undefined;
  }
  birak() {
    for (const a of this.kilitli) this.depo.kilit.delete(a);
    this.kilitli = [];
  }
}
```

#### Aynı İş, Üç Düzen

**NS7 (varsayım):** katalog 20.000 kitap belgesidir, her kitabın 1–5 kopyası vardır, tohum
424242'dir. **NS11 (varsayım):** ödünç isteklerinin kitaplara dağılımı Zipf biçimindedir.
**NS29 (varsayım):** iş yükü 20.000 ödünç isteğidir, üye sayısı 2.000'dir ve eşzamanlılık
modeli her turda 8 isteğin birlikte işlenmesidir; aynı belgeye dokunan ikinci istek çatışma
sayılır. Gerekçe: tur genişliği ve üye sayısı çatışmanın mutlak sayısını ölçekler, düzenler
arasındaki oranı değiştirmez.

İade modellenmez; ölçülen şey verilen ödüncün bedelidir ve üç düzen de aynı isteklere aynı
sırayla yanıt verir. Eş zamanlılık, adım adım ilerleyen iki işin bütün sıralamalarının
sayılmasıyla gösterilir — bu bir **modeldir**, gerçek iş parçacığı kurulmaz.

```js
// islem-olcum.mjs — ayni odunc verme isi uc duzende kosturulur: gomulu semada tek belge
// kosullu yazmasi, referansli semada iki ayri tek belge yazmasi, referansli semada cok
// belgeli islem. Once iki es zamanli odunc butun adim siralamalarinda denenir, sonra
// 20.000 istegin yazma hacmi ile catisma sayisi olculur. Ayni dizinde islem.mjs bulunur.
import { Depo, Islem } from "./islem.mjs";

let cekirdek = 424242;                                   // gorunur tohum
const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648;
const SUBE = ["Merkez", "Bahcelievler", "Kadikoy", "Beyoglu", "Konak", "Nilufer"];
const N = 20000, UYE = 2000, ISTEK = 20000, TUR = 8;

const HAM = [];
for (let i = 1; i <= N; i += 1) {
  const kopya = [];
  for (let j = 0, n = 1 + Math.floor(rast() * 5); j < n; j += 1)
    kopya.push({ barkod: `B${String(i * 10 + j).padStart(7, "0")}`,
      sube: SUBE[Math.floor(rast() * 6)], durum: "rafta" });
  HAM.push({ _k: `K-${String(i).padStart(5, "0")}`, yazar: `Yazar ${i % 4000}`,
    yayin_yili: 1950 + (i % 75), kopya });
}
const kur = (kume = HAM) => {
  const depo = new Depo();
  for (const b of kume) {
    depo.d("kitap").set(b._k, structuredClone(b));
    for (const k of b.kopya) depo.d("kopya").set(k.barkod, { _k: k.barkod, kitap: b._k, ...k });
  }
  return depo;
};

// Uc duzen. Her biri uc adimdan olusur; adimlar arasindaki yield es zamanlilik noktasidir.
function* gomulu(depo, kitapK, barkod, uye, no) {
  const kitap = depo.bul("kitap", kitapK);
  const k = kitap.kopya.find((x) => x.barkod === barkod);
  yield;
  if (k.durum !== "rafta") return false;
  yield;
  return depo.kosulluYaz("kitap", kitapK,               // atomik: oku-denetle-yaz bolunmez
    (b) => b.kopya.find((x) => x.barkod === barkod).durum === "rafta",
    (b) => {
      Object.assign(b.kopya.find((x) => x.barkod === barkod),
        { durum: "oduncte", uye, odunc: no });
      return b;
    });
}
function* referansli(depo, kitapK, barkod, uye, no) {
  const kopya = depo.bul("kopya", barkod);
  yield;
  if (kopya.durum !== "rafta") return false;
  depo.yaz("kopya", { ...kopya, durum: "oduncte" });
  yield;
  depo.yaz("odunc", { _k: `O-${no}`, kopya: barkod, uye, acik: true });
  return true;
}
function* islemli(depo, kitapK, barkod, uye, no) {
  const t = new Islem(depo);
  const kopya = t.oku("kopya", barkod);                 // kilitli okuma
  yield;
  if (kopya === undefined) return false;                // catisma: islem iptal edildi
  if (kopya.durum !== "rafta") { t.isle(); return false; }
  if (!t.yaz("kopya", { ...kopya, durum: "oduncte" })) return false;
  yield;
  if (!t.yaz("odunc", { _k: `O-${no}`, kopya: barkod, uye, acik: true })) return false;
  t.isle();
  return true;
}
const DUZEN = [["A gomulu tek belge", gomulu], ["B referansli islemsiz", referansli],
  ["C referansli islem", islemli]];

// 1. olcum: ayni kopya icin iki es zamanli odunc, butun adim siralamalarinda.
const siralamalar = (n, m) => n === 0 ? [Array(m).fill(1)] : m === 0 ? [Array(n).fill(0)]
  : [...siralamalar(n - 1, m).map((s) => [0, ...s]),
    ...siralamalar(n, m - 1).map((s) => [1, ...s])];
const SIRA = siralamalar(3, 3);
const acikOdunc = (depo, barkod) =>
  [...depo.d("kitap").values()].filter((b) =>
    b.kopya.some((k) => k.barkod === barkod && k.durum === "oduncte")).length
  + [...depo.d("odunc").values()].filter((o) => o.kopya === barkod && o.acik).length;

console.log(`ayni kopya icin iki es zamanli odunc, ${SIRA.length} adim siralamasi`);
for (const [ad, duzen] of DUZEN) {
  let ihlal = 0, verilen = 0, iptal = 0;
  for (const s of SIRA) {
    const depo = kur(HAM.slice(0, 1));                  // tek kitap yeter
    const isler = [duzen(depo, "K-00001", "B0000010", "U-0001", 1),
      duzen(depo, "K-00001", "B0000010", "U-0002", 2)];
    const cevap = [undefined, undefined];
    const ilerle = (i) => {
      if (cevap[i] !== undefined) return;
      const r = isler[i].next();
      if (r.done) cevap[i] = r.value === true;
    };
    for (const secim of s) ilerle(secim);
    for (const i of [0, 1]) while (cevap[i] === undefined) ilerle(i);
    const kabul = cevap.filter(Boolean).length, acik = acikOdunc(depo, "B0000010");
    verilen += kabul;
    iptal += depo.o.iptal;
    if (kabul !== acik || acik > 1) ihlal += 1;
  }
  console.log(`  ${ad.padEnd(22)} ihlalli siralama ${String(ihlal).padStart(2)}/${SIRA.length}` +
    `  verilen odunc ${verilen}  iptal ${iptal}`);
}

// 2. olcum: 20.000 odunc istegi. Kitap secimi Zipf, uye 2.000, tur basina 8 istek.
const H = Array.from({ length: N }, (_, i) => 1 / (i + 1)).reduce((a, b) => a + b);
const kumulatif = [];
for (let i = 0, s = 0; i < N; i += 1) kumulatif.push((s += 1 / ((i + 1) * H)));
const cek = (u) => {
  let alt = 0, ust = N - 1;
  while (alt < ust) {
    const orta = (alt + ust) >> 1;
    if (kumulatif[orta] < u) alt = orta + 1; else ust = orta;
  }
  return alt;
};
const ISTEKLER = [];
for (let n = 0; n < ISTEK; n += 1) {
  const kitap = HAM[cek(rast())];
  ISTEKLER.push({ kitapK: kitap._k, barkod: kitap.kopya[n % kitap.kopya.length].barkod,
    uye: `U-${String(n % UYE).padStart(4, "0")}`, no: n });
}
console.log(`${ISTEK} odunc istegi, en cok istenen kitap ` +
  `${ISTEKLER.filter((s) => s.kitapK === "K-00001").length} kez isteniyor`);
for (const [ad, duzen] of DUZEN) {
  const depo = kur();
  let verilen = 0;
  for (const s of ISTEKLER) {
    const g = duzen(depo, s.kitapK, s.barkod, s.uye, s.no);
    let r = g.next();
    while (!r.done) r = g.next();
    if (r.value === true) verilen += 1;
  }
  const o = depo.o;
  console.log(`  ${ad.padEnd(22)} verilen ${verilen}  yazilan belge ${String(o.yazilanBelge).padStart(5)}` +
    `  yazilan bayt ${String(o.yazilanBayt).padStart(7)}  geri alma ${String(o.geriAlmaBayt).padStart(6)} bayt`);
}
// Kilit hedefi duzene gore degisir: gomulu semada kitap belgesi, referansli semada kopya.
const catisma = (hedef) => {
  let n = 0;
  for (let i = 0; i < ISTEK; i += TUR) {
    const gorulen = new Set();
    for (const s of ISTEKLER.slice(i, i + TUR))
      if (gorulen.has(hedef(s))) n += 1; else gorulen.add(hedef(s));
  }
  return n;
};
console.log(`  ${TUR}'li turda catisan istek: kitap belgesi hedefli ${catisma((s) => s.kitapK)}` +
  `, kopya belgesi hedefli ${catisma((s) => s.barkod)}`);
```

```
ayni kopya icin iki es zamanli odunc, 20 adim siralamasi
  A gomulu tek belge     ihlalli siralama  0/20  verilen odunc 20  iptal 0
  B referansli islemsiz  ihlalli siralama 12/20  verilen odunc 32  iptal 0
  C referansli islem     ihlalli siralama  0/20  verilen odunc 20  iptal 18
20000 odunc istegi, en cok istenen kitap 1980 kez isteniyor
  A gomulu tek belge     verilen 6212  yazilan belge  6212  yazilan bayt 2102524  geri alma      0 bayt
  B referansli islemsiz  verilen 6212  yazilan belge 12424  yazilan bayt 1009960  geri alma      0 bayt
  C referansli islem     verilen 6212  yazilan belge 12424  yazilan bayt 1009960  geri alma 604903 bayt
  8'li turda catisan istek: kitap belgesi hedefli 1021, kopya belgesi hedefli 296
```

#### Değişmezin Sığdığı Yer

İlk üç satır dersin asıl bulgusudur. Gömülü düzende ödünç bilgisi kopyanın içinde, kopya
kitap belgesinin içindedir; değişmez tek bir belgenin sınırları içine sığar ve koşullu
yazma onu yirmi sıralamanın yirmisinde de korur. Yirmi sıralamada toplam 20 ödünç verilir —
her sıralamada tam olarak bir tane. Kaybeden iş bir hata almaz, yalnız koşulun artık
sağlanmadığını görür.

Referanslı düzende aynı değişmez iki belgeye yayılır ve yirmi sıralamanın on ikisinde
bozulur: iki üye de kopyayı "rafta" okur, ikisi de yazar, ortaya aynı kopya için iki açık
ödünç kaydı çıkar. Toplam 32 ödünç verilmiştir, oysa doğru sayı 20'dir. Bu 12 sıralamanın
hiçbirinde hata görünmez — iki iş de başarıyla döner, veri sessizce tutarsızlaşır.

Çok belgeli işlem bu on ikiyi kapatır: ihlal 0'a, verilen ödünç 20'ye iner. Bedeli son
sütundadır. Yirmi sıralamanın on sekizinde bir iptal olur; yalnız iki sıralamada — iki
işin baştan sona ayrı koştuğu sıralamalarda — iptal gerekmez. İşlem, on iki sessiz
tutarsızlığı on sekiz görünür iptale çevirir. İptal edilen iş kaybolmaz, yeniden denenir;
ama her yeniden deneme bir turluk gecikme ve boşa harcanmış bir yazma demektir.

#### Kapsamın Bedeli

İkinci ölçüm bedeli iş yükü ölçeğinde verir. Üç düzen de aynı 6.212 ödüncü verir; 20.000
isteğin geri kalanı, istenen kopyanın zaten ödünçte olması yüzünden reddedilir — Zipf
dağılımı isteklerin büyük bölümünü aynı birkaç kitaba yığar ve en çok istenen kitap tek
başına 1.980 istek alır.

Yazma hacmi ikinci dersin bulgusunu tekrar üretir. Gömülü düzen ödünç başına tek belge
yazar ama o belge kitabın tamamıdır: 6.212 yazma, 2.102.524 bayt. Referanslı düzen iki
belge yazar, ikisi de küçüktür: 12.424 yazma, 1.009.960 bayt. Belge sayısı iki katına
çıkarken bayt yarıya iner.

Çok belgeli işlemin kendi kalemi geri alma imgesidir: 604.903 bayt, yazılan baytın
%59,9'u. Bu bayt hiçbir sorguya yanıt vermez, yalnız iptal olasılığı için tutulur ve
işlem sona erdiğinde atılır. İşlemin ikinci kalemi çatışmadır ve şemaya bağlıdır. Kilit
hedefi gömülü düzende kitap belgesidir, referanslı düzende kopya belgesidir. Sekizli
turlarda kitap belgesi hedefli çatışma 1.021, kopya belgesi hedefli çatışma 296'dır — 3,4
kat fark. Gömme okuma yolunu kısaltırken kilit tanesini de büyütür: aynı kitabın farklı
kopyalarına yapılan iki ödünç, ayrı belgelerde çatışmazken tek belgede çatışır.

Kararın kuralı buradan çıkar: değişmez hangi belgelere yayılıyorsa kapsam odur ve şema
bu yayılmayı belirler. Tek belgeye sığdırılabilen bir değişmez için çok belgeli işlem
kurmak, geri alma imgesini ve iptali karşılıksız ödemektir; sığmayan bir değişmezi işlemsiz
bırakmak ise hatasız görünen bir tutarsızlıktır.

#### Özet

- Tek belge atomikliği, belgeye yapılan yazmanın bütünüyle ya da hiç gerçekleşmesidir; kaç
  alan ve kaç dizi ögesi değiştiği bunu değiştirmez.
- Değişmez tek belgeye sığdığında koşullu yazma yeter: yirmi adım sıralamasının yirmisinde
  de tam bir ödünç verilir, iptal olmaz.
- Değişmez iki belgeye yayıldığında yirmi sıralamanın on ikisi bozulur ve 20 yerine 32
  ödünç verilir; iki iş de hatasız döner, tutarsızlık sessizdir.
- Çok belgeli işlem ihlali sıfıra indirir ve karşılığında on sekiz iptal ile yazılan baytın
  %59,9'u kadar (604.903 bayt) geri alma imgesi ödetir.
- Kilit tanesi şemadan gelir: aynı iş yükünde kitap belgesi hedefli çatışma 1.021, kopya
  belgesi hedefli çatışma 296'dır.

#### Sonraki Adım

Bu konu belge modelini tip sisteminden çok belgeli işleme kadar götürdü ve her adımda bir
kararı sayıya bağladı: gömme kararının okuma yolunu kaç isteğe indirdiği, işleç
semantiğinin kaç belge döndürdüğü, boru hattı sırasının kaç kayıt işlettiği, dizinin
taranan giriş sayısını nereye çektiği, doğrulama kuralının kaç ihlal ürettiği ve çok
belgeli bir işlemin neye mal olduğu. Bütün bu ölçümlerin ortak bir sınırı var: hepsi **tek
bir düğümde** yapıldı. Belge modelinin vaatlerinin çoğu ise tek düğümde sınanamaz — yatay
ölçekleme de, bir makine kaybedildiğinde hizmetin sürmesi de birden çok düğüm ister ve bu
derste ölçülen kilit, iptal ve atomiklik kavramlarının hepsi orada yeniden tanımlanır.
Sonraki konu depoyu dağıtık kuruluma taşır ve ilk soruyu sorar: aynı veri birden çok üyede
duruyorsa, bunlardan hangisinin yazma kabul ettiğine kim karar verir.

## Dağıtık Davranış

İlişkisel olmayan depoların dağıtık kurulumda verdiği kararlar: birincil–ikincil topoloji ile seçim süreci, parça anahtarı seçiminin dengesizlik riski, okuma ve yazma ilgilerinin onay düzeyi ayarları, güçlü, sonunda ve nedensel tutarlılığın uygulamaya yansıması, CAP ile PACELC ödünleşimlerinin doğru ifade edilmesi ve kimlik doğrulama, rol modeli ile şifreleme yapılandırması.

### Çoğaltma Kümeleri

Source: https://academia.sh/tr/kurslar/nosql/cogaltma-kumeleri

Belge deposunun tek düğümden çıkması: birincil–ikincil topolojinin çoğaltma mekaniği değil kümenin kendi seçimi olarak ele alınması, çoğunluk kuralının küme boyuna göre katlanılan üye kaybına çevrilmesi, aynı arızanın beş küme ayarında koşturulup seçimin gerçekleşip gerçekleşmediğinin, yazmasız tur sayısının ve yeni birincilin veri farkından doğan geri alınan yazma sayısının ölçülmesi, aday seçme kuralının ikinci bir seçim turu doğurmasının gösterilmesi.

Önceki konu belge modelini tek belge atomikliğine kadar götürdü ve orada durdu. Buraya kadarki
her ölçüm **tek bir düğümde** yapıldı: bir gömme kararının okuma yolunu kaç isteğe indirdiği, bir
dizinin taranan giriş sayısını nereye çektiği, çok belgeli bir işlemin neye mal olduğu. Belge
modelinin vaatlerinin çoğu ise tek düğümde sınanamaz — yatay ölçekleme de, bir makine
kaybedildiğinde hizmetin sürmesi de birden çok düğüm ister.

Bu konu deponun dağıtık kurulumdaki davranışını ele alır ve ilk soru şudur: aynı veri birden çok
üyede duruyorsa, bunlardan hangisinin yazma kabul ettiğine kim karar verir. Kütüphane katalog ve
ödünç veritabanı bu konuda da örnek alandır; ölçülen iş, ödünç kayıtlarının yazılmasıdır.

#### Kümenin Kendi Seçimi

Aynı veriyi birden çok üyede tutan bu düzene **çoğaltma kümesi** (replica set) denir. Üyelerden
biri **birincil**dir (primary) ve yazmaları kabul eder; ötekiler **ikincil**dir (secondary), aynı
değişiklikleri uygular ve ayarlara göre okuma karşılar. Bu, İleri Veri Ölçekleme kursundaki
**ana–kopya** düzeninin kendisidir; iki ad aynı kalıbı adlandırır ve ikisi arasında bir mekanik
farkı yoktur.

Çoğaltmanın mekaniği o kursta ve İlişkisel Veritabanı Yönetimi kursunda kuruldu: günlük akışı,
çoğaltma gecikmesi, kopya eklemenin azalan kazancı ve bayat okuma orada ölçüldü. Burada
tekrarlanmaz. Bu dersin sorusu kümenin kendi başına verdiği karardır: birincil durduğunda yerine
kimin geçeceğine üyeler kendi aralarında karar verir ve bu kararın iki ölçülebilir sonucu vardır —
kararın verilemediği süre boyunca hiçbir yazma kabul edilmez ve yeni birincil eskisinden geride
olduğunda aradaki fark kadar yazma **geri alınır**.

Kararı bağlayan kural **çoğunluk kuralıdır** (quorum): bir üye ancak üye sayısının yarısından
fazlasının oyunu alarak birincil olabilir. Bu kural ikiye bölünmüş bir kümede iki tarafın birden
kendini birincil sanmasını engeller. Bedeli de aynı kuralın içindedir: çoğunluk toplanamıyorsa
seçim hiç başlamaz.

#### Düzenek

Aşağıdaki düzenek bir **modeldir**: gerçek küme, kapsayıcı ya da bulut kurulmaz, üyeler birer
sayıdır. Tur soyut bir adımdır; yayılım gecikmesi, algılama eşiği ve seçim süresi tur cinsinden
parametrelerdir, ölçülmüş süre değildir. Modelin tek gerçekçi varsayımı yayılımın **çekme**
yönlü olmasıdır: ikincil, birincilin günlüğünü okur. Birincil durduğunda henüz okunmamış yazma
hiçbir üyeye ulaşamaz; geri alınan yazmanın kaynağı budur.

**NS14 — üye başına yayılım gecikmesi 2, 3, 3 ve 5 turdur.** Gerekçe: ikincillerin birinciliye
uzaklığı eşit değildir. **NS15 — algılama eşiği 2 tur, seçim 2 turdur.** Gerekçe: birincilin
durduğuna karar vermek ve oy toplamak ayrı adımlardır. İki varsayım da doğrusal etkilidir:
gecikme iki katına çıkarsa geri alınan yazma da yaklaşık iki katına çıkar.

```js
// kume/model.mjs — cogaltma kumesi MODELIDIR. Tur soyut bir adimdir; yayilim gecikmesi,
// algilama esigi ve secim suresi tur cinsinden parametrelerdir, olculmus sure degildir.
// Gercek kume kurulmaz. Yayilim CEKME yonludur: ikincil birincilin gunlugunu okur, birincil
// durdugunda henuz okunmamis yazma hicbir uyeye ulasmaz.
export function kosum({ uye, gecikme, dusen = [], kural = "en-guncel", oncelikli = 3,
                        ariza = 13, algilama = 2, secim = 2, hiz = 3, tur: N = 24 }) {
  const cogunluk = Math.floor(uye / 2) + 1;
  const kapali = new Set();
  const kayit = [0];                       // tur -> birincilin o turda kabul ettigi son sira
  const uygulanan = Array(uye).fill(0);
  let birincil = 0, sira = 0, bekleme = 0, esik = algilama + secim, red = 0;
  let yazmasiz = 0, kabulEski = null, yeni = null, geriAlinan = null;

  for (let t = 1; t <= N; t += 1) {
    if (t === ariza) {                     // birincil ve varsa ikinci uye ayni turda duser
      kabulEski = sira;
      for (const i of [birincil, ...dusen]) kapali.add(i);
      birincil = null;
    }
    if (birincil === null) {
      yazmasiz += 1; bekleme += 1;
      const oy = [...Array(uye).keys()].filter((i) => !kapali.has(i));
      if (oy.length < cogunluk || bekleme !== esik) continue;   // cogunluk yoksa secim hic baslamaz
      const enGuncel = oy.reduce((a, b) => (uygulanan[b] > uygulanan[a] ? b : a));
      const aday = kural === "oncelikli" && red === 0 && !kapali.has(oncelikli) ? oncelikli : enGuncel;
      if (oy.some((i) => uygulanan[i] > uygulanan[aday])) {     // cogunluk kurali adayi reddeder
        red += 1; esik += secim; continue;                      // ikinci secim turu
      }
      yeni = aday;
      geriAlinan = kabulEski - uygulanan[yeni];
      sira = uygulanan[yeni];
      for (let x = 0; x < kayit.length; x += 1) kayit[x] = Math.min(kayit[x], sira); // gunluk kirpilir
      birincil = yeni;
      continue;
    }
    sira += hiz;                            // her turda hiz kadar odunc kaydi birincile yazilir
    kayit[t] = sira;
    for (let i = 0; i < uye; i += 1) {
      if (kapali.has(i)) continue;
      uygulanan[i] = i === birincil ? sira : Math.max(uygulanan[i], kayit[t - gecikme[i]] ?? 0);
    }
  }
  return { cogunluk, oy: uye - kapali.size, yazmasiz, geriAlinan, yeni, kabulEski, red, uygulanan };
}
```

Modelin bir satırı bütün ödünleşimi taşıyor: aday, **oy veren hiçbir üyeden geride olamaz**.
Çoğunluk kuralının gerçek işlevi oy saymak değil, seçilen üyenin elindeki günlüğün oy veren
çoğunluğunkinden eski olmamasını güvence altına almaktır.

```js
// kume/olc.mjs — ayni ariza, dort kume ayari; sonra ayni kumede iki secim kurali
import { kosum } from "./model.mjs";

const G = [0, 2, 3, 3, 5];                 // uye basina yayilim gecikmesi (tur)
const s = (x, n) => String(x).padStart(n);
console.log("24 tur, her turda 3 odunc kaydi. Birincil 13. turda durur; algilama 2, secim 2 tur.");
console.log("durmadan once 36 kayit kabul edilmisti.\n");
console.log("kume | dusen uye | cogunluk | oy verebilen | secim | yazmasiz tur | geri alinan | yeni birincil");
console.log("-----|-----------|----------|--------------|-------|--------------|-------------|--------------");
const AYAR = [
  [3, [], "u0"], [3, [1], "u0+u1"], [4, [1], "u0+u1"], [5, [1], "u0+u1"], [5, [4], "u0+u4"],
];
for (const [uye, dusen, ad] of AYAR) {
  const r = kosum({ uye, gecikme: G.slice(0, uye), dusen });
  console.log(`${s(uye, 4)} | ${ad.padEnd(9)} | ${s(r.cogunluk, 8)} | ${s(r.oy, 12)} | ` +
    `${(r.yeni === null ? "yok" : "var").padStart(5)} | ${s(r.yazmasiz, 12)} | ` +
    `${s(r.geriAlinan === null ? "-" : r.geriAlinan, 11)} | ${r.yeni === null ? "yok" : "u" + r.yeni}`);
}

console.log("\n5 uyeli kume, u0+u4 duser; aday secme kurali degisiyor:");
console.log("kural      | reddedilen aday | secim turu | yazmasiz tur | geri alinan | yeni birincil");
console.log("-----------|-----------------|------------|--------------|-------------|--------------");
for (const kural of ["en-guncel", "oncelikli"]) {
  const r = kosum({ uye: 5, gecikme: G, dusen: [4], kural, oncelikli: 3 });
  console.log(`${kural.padEnd(10)} | ${s(r.red, 15)} | ${s(r.red + 1, 10)} | ${s(r.yazmasiz, 12)} | ` +
    `${s(r.geriAlinan, 11)} | u${r.yeni}`);
}

console.log("\nkosumdan bagimsiz: cogunluk = floor(n/2)+1, katlanilan uye kaybi = n - cogunluk");
console.log("n        : " + [3, 4, 5, 6, 7].map((n) => s(n, 3)).join(""));
console.log("cogunluk : " + [3, 4, 5, 6, 7].map((n) => s(Math.floor(n / 2) + 1, 3)).join(""));
console.log("kayip    : " + [3, 4, 5, 6, 7].map((n) => s(n - (Math.floor(n / 2) + 1), 3)).join(""));
```

```
24 tur, her turda 3 odunc kaydi. Birincil 13. turda durur; algilama 2, secim 2 tur.
durmadan once 36 kayit kabul edilmisti.

kume | dusen uye | cogunluk | oy verebilen | secim | yazmasiz tur | geri alinan | yeni birincil
-----|-----------|----------|--------------|-------|--------------|-------------|--------------
   3 | u0        |        2 |            2 |   var |            4 |           6 | u1
   3 | u0+u1     |        2 |            1 |   yok |           12 |           - | yok
   4 | u0+u1     |        3 |            2 |   yok |           12 |           - | yok
   5 | u0+u1     |        3 |            3 |   var |            4 |           9 | u2
   5 | u0+u4     |        3 |            3 |   var |            4 |           6 | u1

5 uyeli kume, u0+u4 duser; aday secme kurali degisiyor:
kural      | reddedilen aday | secim turu | yazmasiz tur | geri alinan | yeni birincil
-----------|-----------------|------------|--------------|-------------|--------------
en-guncel  |               0 |          1 |            4 |           6 | u1
oncelikli  |               1 |          2 |            6 |           6 | u1

kosumdan bagimsiz: cogunluk = floor(n/2)+1, katlanilan uye kaybi = n - cogunluk
n        :   3  4  5  6  7
cogunluk :   2  3  3  4  4
kayip    :   1  1  2  2  3
```

#### Sayıların Okunması

**Çift sayılı küme fazladan hiçbir şey katmıyor.** Son bloktaki üç satır koşumdan bağımsızdır ve
yalnız aritmetiktir: dört üyeli kümede çoğunluk 3, üç üyeli kümede 2; ikisi de tek bir üye
kaybına dayanır. Dördüncü üye saklanan veriyi bir kat artırır ve katlanılan kaybı hiç
büyütmez. Tabloda bu doğrudan görünüyor: üç üyeli ve dört üyeli kümeler iki üye düştüğünde
aynı sonucu veriyor — seçim yok, yazmasız tur 12.

**Seçimin gerçekleşmediği satırlarda ölçülen şey bir kesintidir.** İki üyesi düşen üç üyeli
kümede ayakta kalan tek üye vardır ve elinde en yeni veri de olabilir; yine de birincil olamaz,
çünkü çoğunluk 2'dir. Bu, kaybedilmiş bir fırsat değil, kuralın kendisidir: ayakta kalan üye
komşusunun kendisinden habersiz bir yazma almadığını doğrulayamaz. Kalan 12 turda tek bir ödünç
kaydı kabul edilmez.

**Geri alınan yazma sayısını hangi üyenin düştüğü belirliyor.** Beş üyeli kümenin iki satırı bunu
tek başına gösteriyor. `u0+u1` düştüğünde en güncel ayakta üye `u2`'dir ve 27. kayda kadar
uygulamıştır: 36 − 27 = 9 ödünç kaydı geri alınır. `u0+u4` düştüğünde ise `u1` ayaktadır ve 30.
kayda kadar uygulamıştır: geri alınan 6. Aynı küme, aynı arıza süresi, aynı çoğunluk — tek fark
düşen ikinci üyenin kimliği ve sonuç yüzde elli daha fazla geri alınan yazma. Bu sayı, birincilin
kabul ettiği ama hiçbir hayatta kalan üyeye ulaşmamış yazmaların sayısıdır.

Geri alınan yazma bir veri kaybı değildir, ondan daha sinsi bir şeydir: **kabul edilmiş ve
başarılı diye bildirilmiş** bir ödünç kaydının sonradan yok sayılmasıdır. Üye tarafından yazılan
istek yanıt almıştır; ödünç işlemi kayıtta yoktur. Bu farkın nasıl kapatılacağı bu dersin değil,
onay düzeyi ayarlarının konusudur.

#### Adayın Seçilmesi

İkinci tablo aynı kümede aynı arızayı iki aday seçme kuralıyla koşturuyor. `en-guncel` kuralında
aday, oy verenler arasındaki en güncel üyedir ve çoğunluk kuralı onu ilk turda kabul eder.
`oncelikli` kuralında belirli bir üye — örneğin ana binadaki üye — aday olarak öne çıkarılır;
model bu üyeyi `u3` olarak alıyor ve `u3` 27. kayda kadar uygulamıştır.

Sonuç, çoğunluk kuralının ne yaptığını gösteriyor: `u1` üyesi 30. kayda kadar uygulamış olduğu
için `u3`'ün adaylığı **reddediliyor**. Reddedilen aday sütunu 1, seçim turu 2 oluyor ve yazmasız
tur 4'ten 6'ya çıkıyor. Geri alınan yazma iki kuralda da 6'dır ve bu tesadüf değildir: kural,
adayın geride olmasına izin vermediği için sonuçta yine en güncel üye birincil olur. Öncelik
ayarının ödediği bedel veri değil, **iki tur fazladan kesintidir**.

Buradan bir işletme kuralı çıkar: öncelik ayarı ancak öne çıkarılan üye gerçekten güncel
kalabiliyorsa ücretsizdir. Yayılım gecikmesi yüksek bir üyeyi öne çıkarmak, her arızada bir
seçim turu daha ödemek demektir.

#### Özet

- Çoğaltma kümesi birincil–ikincil topolojisidir ve İleri Veri Ölçekleme kursundaki ana–kopya
  düzeniyle aynı kalıptır; iki ad tek bir mekaniği adlandırır.
- Çoğunluk kuralı `floor(n/2)+1` oydur ve katlanılan üye kaybı `n − çoğunluk`'tur: üç ve dört
  üyeli kümeler tek bir kayba, beş ve altı üyeli kümeler iki kayba dayanır — çift sayılı üye
  eklemek dayanıklılığı büyütmez.
- İki üyesi düşen üç ve dört üyeli kümelerde seçim hiç başlamadı ve kalan 12 tur boyunca tek bir
  ödünç kaydı kabul edilmedi; ayakta kalan üyenin elindeki verinin güncelliği bunu değiştirmiyor.
- Aynı arızada geri alınan yazmayı düşen üyenin kimliği belirledi: beş üyeli kümede `u0+u1`
  düştüğünde 9, `u0+u4` düştüğünde 6 ödünç kaydı geri alındı.
- Geri alınan yazma, kabul edilmiş ve başarılı diye bildirilmiş bir yazmanın sonradan yok
  sayılmasıdır; kaynağı, hiçbir hayatta kalan üyeye ulaşmamış olmasıdır.
- Öncelikli aday kuralı çoğunluk kuralınca reddedildiğinde geri alınan yazmayı değiştirmedi ama
  yazmasız turu 4'ten 6'ya çıkardı.

#### Sonraki Adım

Bu ders kümenin bir üyeyi kaybettiğinde ne yaptığını ölçtü, ama ölçtüğü her sayı aynı örtük
varsayıma dayandı: bütün veri her üyede duruyor. Bu varsayım, çoğaltmanın hiçbir zaman
çözemeyeceği bir sınırı beraberinde getirir — kümeye üye eklemek okumayı böler, yazmayı ve
saklanan veriyi bölmez. Katalog belge sayısıyla büyüdüğünde ve ödünç kayıtları tek bir üyenin
diskine sığmaz olduğunda veriyi üyeler arasında **bölmek** gerekir. Bölme kararının kendisi tek
bir alan seçimine iner ve bu seçim belgenin içinden yapılır. Sonraki ders o alanı seçmenin
ölçütlerini sayar: seçilen alanın tekilliği parça başına düşen belge sayısını nasıl dengesizleştirir,
artan bir alan neden bütün yazmayı tek parçaya yığar ve aynı sorgu kümesi iki farklı seçimde kaç
parçaya dokunur.

### Parçalama

Source: https://academia.sh/tr/kurslar/nosql/parcalama

Parça anahtarının belge alanından seçilmesi: anahtarın her belgede bulunma ve tekillik koşullarının yönlendirilemeyen belge sayısı ile parça sayısı üst sınırına çevrilmesi, beş aday anahtarın aynı yirmi bin belgelik ödünç derlemi üzerinde yerleşim dengesi ve sıcak parça bakımından ölçülmesi, aynı sorgu karışımının her adayda kaç parçaya dokunduğunun sayılması ve bileşik anahtarda önek olmadan sorgunun dağıtılmasının gösterilmesi.

Önceki ders kümeye üye eklemenin neyi çözdüğünü ve neyi çözmediğini gösterdi: bir üye
kaybedildiğinde hizmet sürer, ama her üye aynı verinin tamamını taşımayı sürdürür. Katalog
büyüdüğünde ve ödünç kayıtları tek bir üyenin diskine sığmaz olduğunda çoğaltmanın yapabileceği
bir şey kalmaz; veriyi üyeler arasında **bölmek** gerekir.

Bölmenin adı **parçalamadır** (sharding) ve tek bir karara iner: her belgenin hangi parçaya
gideceğini belirleyen alanın seçimi. Bu alana **parça anahtarı** (shard key) denir. Veriyi bölme
stratejilerinin aritmetiği — aralığa göre mi karmaya göre mi bölündüğü, yeniden dengelemenin
taşıdığı veri — İleri Veri Ölçekleme kursunda ölçüldü ve burada tekrarlanmaz. Buradaki soru,
belge modeline özgü olanıdır: anahtar bir **belge alanıdır** ve o alanın seçimi geri alınması
pahalı bir karardır.

#### Anahtar Belgenin İçinden Gelir

İlişkisel bir tabloda bölümleme sütunu şemanın parçasıdır ve her satırda vardır. Belge modelinde
böyle bir güvence yoktur: derlemdeki belgeler farklı alan kümeleri taşıyabilir. Bu, parça
anahtarına iki koşul yükler.

**Anahtar alanı her belgede bulunmalıdır.** Yönlendirici, alanı olmayan bir belgeyi hiçbir
parçaya gönderemez. Kütüphane veritabanında bu soyut bir risk değildir: eski dizgeden aktarılan
ödünç kayıtlarının bir kısmında şube alanı hiç yoktur.

**Anahtarın tekilliği parça sayısını sınırlar.** Parçalamanın en küçük tanesi bir anahtar
değeridir; aynı değeri taşıyan belgeler bölünemez. Altı farklı değeri olan bir alan altıdan çok
parçaya dağıtılamaz ve değerlerden biri belgelerin yarısını taşıyorsa o yığın hiçbir zaman
bölünmez. Bu, koşumdan bağımsız bir üst sınırdır.

Üçüncü koşul ölçülecek olandır: anahtar, hem yazmayı parçalara yaymalı hem de sık gelen
sorguların tek bir parçaya yönlendirilmesine izin vermelidir. Bir sorgu anahtarın **önekini**
sabitliyorsa yönlendirici onu tek parçaya gönderir; buna **hedeflenen sorgu** denir. Sabitlemiyorsa
yönlendirici bütün parçalara sormak zorundadır; buna **dağıtılan sorgu** denir. İki tür arasındaki
fark, dokunulan parça sayısıdır ve sayılabilir.

#### Düzenek

**NS16 — ödünç kayıtlarının şubelere dağılımı 45, 18, 14, 11, 8 ve 4 birimdir.** Gerekçe: merkez
şube en büyük derlemi ve en çok üyeyi taşır. **NS17 — aktarılan eski kayıtların yüzde 2'sinde
şube alanı yoktur.** **NS18 — sorgu karışımı: üye sorgusu yüzde 45, şube gün sonu listesi yüzde
20, son yedi günün ödünçleri yüzde 15, kayıt kimliğiyle erişim yüzde 15, gecikmiş ödünç taraması
yüzde 5.** Üç varsayım da doğrusal etkilidir; karışımdaki ağırlıklar değişirse aşağıdaki
ağırlıklı ortalama aynı yönde değişir.

```js
// parca/model.mjs — odunc belgeleri ureteci ve parca yerlesimi. Uretec kendi yazilmistir,
// tohum gorunurdur: her kosumda ayni belge kumesi cikar. Parca sayisi P bir parametredir;
// dagitimin araliga mi karmaya mi dayandigi Ileri Veri Olcekleme kursunda olculdu, burada
// degismeyen tek sey her belgenin ANAHTAR ALANININ DEGERINE gore bir parcaya yonlenmesidir.
export const SUBE = [["merkez", 45], ["kadikoy", 18], ["besiktas", 14], ["uskudar", 11],
  ["sisli", 8], ["adalar", 4]];
export const GUN = 180, UYE = 6000;

export function uret({ adet = 20000, tohum = 20260731, eksikSube = 0.02 } = {}) {
  let s = tohum % 2147483647;
  const rast = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const toplam = SUBE.reduce((t, [, w]) => t + w, 0);
  const belge = [];
  for (let i = 1; i <= adet; i += 1) {
    let x = rast() * toplam, sube = SUBE[0][0];
    for (const [ad, w] of SUBE) { if (x < w) { sube = ad; break; } x -= w; }
    const d = { odunc_id: i, uye_id: 1 + Math.floor(rast() * UYE),
      alinma_gunu: 1 + Math.floor(rast() * GUN), iade_edildi: rast() < 0.82 };
    if (rast() >= eksikSube) d.sube = sube;   // eski kayitlarin bir kisminda sube alani yok
    belge.push(d);
  }
  return belge;
}

const kiyas = (a, b) => {                     // anahtar demetlerinin toplam siralamasi
  for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
  return 0;
};
const karmaDeger = (m) => { let h = 2166136261; for (const c of m) h = Math.imul(h ^ c.charCodeAt(0), 16777619); return h >>> 0; };

// Parca yerlesimi: anahtar degerleri siraya dizilir ve esit belge sayisi hedefiyle P pencereye
// bolunur. Tek bir anahtar degeri bolunemez — parcalamanin en kucuk tanesi bir anahtar degeridir.
export function yerlestir(belge, alanlar, P, karma = false) {
  const anahtar = (d) => alanlar.map((a) => d[a]);
  const tam = belge.filter((d) => alanlar.every((a) => d[a] !== undefined));
  const sayac = new Map();
  for (const d of tam) {
    const k = JSON.stringify(karma ? [karmaDeger(JSON.stringify(anahtar(d)))] : anahtar(d));
    sayac.set(k, (sayac.get(k) ?? 0) + 1);
  }
  const sirali = [...sayac.entries()].sort((a, b) => kiyas(JSON.parse(a[0]), JSON.parse(b[0])));
  const hedef = tam.length / P;
  const yer = new Map();
  let p = 0, birikim = 0;
  for (const [k, n] of sirali) {
    yer.set(k, p);
    birikim += n;
    if (birikim >= hedef * (p + 1) && p < P - 1) p += 1;
  }
  const parca = (d) => yer.get(JSON.stringify(karma ? [karmaDeger(JSON.stringify(anahtar(d)))] : anahtar(d)));
  const dagilim = Array(P).fill(0);
  for (const d of tam) dagilim[parca(d)] += 1;
  return { parca, dagilim, tam, eksik: belge.length - tam.length, farkli: sayac.size };
}

// Sorgu, anahtarin bir ONEKINI sabitliyorsa yonlendirilebilir (hedeflenen sorgu); sabitlemiyorsa
// yonlendirici butun parcalara gitmek zorundadir (dagitilan sorgu). Karma yerlesimde aralik
// kisiti kullanilamaz, cunku bitisik degerler bitisik parcalara dusmez.
export function dokunulan(sorgu, alanlar, karma, y, P) {
  const kullanilir = [];
  for (const a of alanlar) {
    if (sorgu.esit && a in sorgu.esit) { kullanilir.push({ alan: a, tip: "esit" }); continue; }
    if (sorgu.aralik && sorgu.aralik.alan === a && karma === false) kullanilir.push({ alan: a, tip: "aralik" });
    break;
  }
  if (kullanilir.length === 0) return P;
  const uyar = (d) => kullanilir.every((k) => k.tip === "esit"
    ? d[k.alan] === sorgu.esit[k.alan]
    : d[k.alan] >= sorgu.aralik.min && d[k.alan] <= sorgu.aralik.max);
  return new Set(y.tam.filter(uyar).map(y.parca)).size;
}
```

```js
// parca/olc.mjs — bes parca anahtari adayi: yerlesim dengesi, sicak parca ve dokunulan parca
import { uret, yerlestir, dokunulan, SUBE, GUN, UYE } from "./model.mjs";

const P = 4, ADET = 20000;
const belge = uret({ adet: ADET });
const ADAY = [["sube", ["sube"], false], ["alinma_gunu", ["alinma_gunu"], false],
  ["karma(alinma_gunu)", ["alinma_gunu"], true], ["uye_id", ["uye_id"], false],
  ["{sube,uye_id}", ["sube", "uye_id"], false]];
const SORGU = [
  ["S1 uyenin acik oduncleri", 45, (i) => ({ esit: { uye_id: 1 + (i * 137) % UYE } })],
  ["S2 sube gun sonu listesi", 20, (i) => ({ esit: { sube: SUBE[i % SUBE.length][0], alinma_gunu: 1 + (i * 31) % GUN } })],
  ["S3 son 7 gunun oduncleri", 15, () => ({ aralik: { alan: "alinma_gunu", min: GUN - 6, max: GUN } })],
  ["S4 odunc kaydi id ile", 15, (i) => ({ esit: { odunc_id: 1 + (i * 911) % ADET } })],
  ["S5 gecikmis odunc taramasi", 5, () => ({ esit: {} })],
];
const s = (x, n) => String(x).padStart(n);
const yeni = belge.filter((d) => d.alinma_gunu > GUN - 7);
console.log(`${ADET} odunc belgesi, ${P} parca, ${SUBE.length} sube, ${UYE} uye, ${GUN} gun. Tohum 20260731.`);
console.log(`son 7 gunde alinan: ${yeni.length} belge\n`);
console.log("parca anahtari      | farkli deger | yonlendirilemeyen | parca dagilimi          | en dolu/ort | son 7 gun en dolu parca");
console.log("--------------------|--------------|-------------------|-------------------------|-------------|------------------------");
const yerlesim = new Map();
for (const [ad, alanlar, karma] of ADAY) {
  const y = yerlestir(belge, alanlar, P, karma);
  yerlesim.set(ad, y);
  const enDolu = Math.max(...y.dagilim) / (y.tam.length / P);
  const yd = Array(P).fill(0);
  for (const d of yeni) if (alanlar.every((a) => d[a] !== undefined)) yd[y.parca(d)] += 1;
  const pay = Math.max(...yd) / yd.reduce((t, x) => t + x, 0);
  console.log(`${ad.padEnd(19)} | ${s(y.farkli, 12)} | ${s(y.eksik, 17)} | ` +
    `${y.dagilim.map((x) => s(x, 5)).join(" ").padEnd(23)} | ${s(enDolu.toFixed(2), 11)} | ` +
    `${s("%" + (100 * pay).toFixed(1), 23)}`);
}

console.log("\ndokunulan parca (sorgu basina, 1 = hedeflenen, 4 = dagitilan):");
console.log("parca anahtari      |  S1  |  S2  |  S3  |  S4  |  S5  | agirlikli ortalama");
console.log("--------------------|------|------|------|------|------|-------------------");
for (const [ad, alanlar, karma] of ADAY) {
  const y = yerlesim.get(ad);
  const hucre = [], agirlikli = [];
  for (const [, agirlik, ornek] of SORGU) {
    let t = 0;
    for (let i = 0; i < 12; i += 1) t += dokunulan(ornek(i), alanlar, karma, y, P);
    hucre.push(t / 12);
    agirlikli.push((t / 12) * agirlik);
  }
  const ort = agirlikli.reduce((a, b) => a + b, 0) / SORGU.reduce((a, b) => a + b[1], 0);
  console.log(`${ad.padEnd(19)} | ${hucre.map((x) => s(x.toFixed(2), 4)).join(" | ")} | ${s(ort.toFixed(2), 18)}`);
}
console.log("\nsorgu karisimi: " + SORGU.map(([a, w]) => `${a.slice(0, 2)} %${w}`).join(", "));
console.log(`kosumdan bagimsiz: bir anahtarin farkli deger sayisi parca sayisinin ust siniridir` +
  ` (sube icin ${SUBE.length}).`);
```

```
20000 odunc belgesi, 4 parca, 6 sube, 6000 uye, 180 gun. Tohum 20260731.
son 7 gunde alinan: 782 belge

parca anahtari      | farkli deger | yonlendirilemeyen | parca dagilimi          | en dolu/ort | son 7 gun en dolu parca
--------------------|--------------|-------------------|-------------------------|-------------|------------------------
sube                |            6 |               406 |  6950  8867  1597  2180 |        1.81 |                   %45.6
alinma_gunu         |          180 |                 0 |  5022  5031  5031  4916 |        1.01 |                  %100.0
karma(alinma_gunu)  |          180 |                 0 |  5034  4973  5002  4991 |        1.01 |                   %59.6
uye_id              |         5788 |                 0 |  5000  5000  5002  4998 |        1.00 |                   %27.0
{sube,uye_id}       |        13443 |               406 |  4899  4901  4897  4897 |        1.00 |                   %25.8

dokunulan parca (sorgu basina, 1 = hedeflenen, 4 = dagitilan):
parca anahtari      |  S1  |  S2  |  S3  |  S4  |  S5  | agirlikli ortalama
--------------------|------|------|------|------|------|-------------------
sube                | 4.00 | 1.00 | 4.00 | 4.00 | 4.00 |               3.40
alinma_gunu         | 4.00 | 1.00 | 1.00 | 4.00 | 4.00 |               2.95
karma(alinma_gunu)  | 4.00 | 1.00 | 4.00 | 4.00 | 4.00 |               3.40
uye_id              | 1.00 | 4.00 | 4.00 | 4.00 | 4.00 |               2.65
{sube,uye_id}       | 4.00 | 1.50 | 4.00 | 4.00 | 4.00 |               3.50

sorgu karisimi: S1 %45, S2 %20, S3 %15, S4 %15, S5 %5
kosumdan bagimsiz: bir anahtarin farkli deger sayisi parca sayisinin ust siniridir (sube icin 6).
```

#### Yerleşim Dengesi ve Sıcak Parça

**En sezgisel aday en kötü dengeyi veriyor.** Ödünç kayıtlarını şubeye göre bölmek işletme
düzenine uygun görünür; ölçüm başka bir şey söylüyor. Altı şube dört parçaya sıkıştığında
parçalara 6950, 8867, 1597 ve 2180 belge düşüyor: en dolu parça ortalamanın 1,81 katı. Sebep
tekilliktir — bir parçanın taşıdığı en küçük tane bir şubedir ve merkez şube tek başına ödünç
kayıtlarının yüzde 45'ini üretir. Dengesizlik daha çok parça eklenerek de düzelmez, çünkü altı
farklı değer altı parçanın üst sınırıdır. Bu satırın ikinci sayısı da uyarıdır: 406 belge şube
alanını taşımadığı için hiçbir parçaya yönlendirilemez.

**Dengeli yerleşim tek başına yeterli değildir.** `alinma_gunu` anahtarı belgeleri neredeyse
kusursuz bölüyor — en dolu parça ortalamanın 1,01 katı. Son sütun bu satırı çürütüyor: son yedi
günde alınan 782 ödüncün yüzde 100'ü tek bir parçaya düşüyor. Artan bir alan anahtar olduğunda
geçmiş veri düzgün dağılır ama **bütün yeni yazma** en son pencereyi taşıyan parçaya yığılır.
Buna **sıcak parça** denir ve depoda görünen tek belirtisi, kümenin üç üyesi boştayken birinin
yazma sırasına girmesidir.

**Karma yerleşim sıcak parçayı bir başka şeyle takas ediyor.** Aynı alanın karma yerleşimi
sıcak parçanın payını yüzde 100'den 59,6'ya indiriyor. Yüzde 25'e inmemesinin sebebi de
tekilliktir: yedi günün yedi farklı değeri dört parçaya dağıldığında bazı parçalara iki gün
düşer. Bedel ikinci tablodadır ve büyüktür.

**En yüksek tekillikli aday en iyi dengeyi veriyor.** `uye_id` anahtarında dağılım 5000, 5000,
5002, 4998 ve son yedi günün payı yüzde 27 — dört parça için ideal olan yüzde 25'e yakın. Bileşik
anahtar da aynı dengeyi veriyor ama yönlendirilemeyen 406 belgeyi geri getiriyor, çünkü önekinde
şube alanı var.

#### Hedeflenen ve Dağıtılan Sorgu

İkinci tablo aynı sorgu karışımını beş adayda koşturuyor ve dengeye bakarak verilen kararı
tersine çeviriyor.

`uye_id` anahtarı ağırlıklı ortalamada 2,65 parçayla en iyi sonucu veriyor, çünkü karışımın en
ağır sorgusu olan üye sorgusu tam olarak anahtarı sabitliyor: 1,00 parça. Aynı anahtarda şube gün
sonu listesi 4,00'e çıkıyor — anahtarı hiç sabitlemediği için dört parçaya birden gidiyor.

`{sube,uye_id}` bileşik anahtarı bu dersin en öğretici satırıdır. Yerleşim dengesi kusursuz
(1,00) ve şube sorgusu 1,50 parçaya iniyor; buna karşın **üye sorgusu 4,00**. Sebep, bileşik
anahtarın soldan sağa çalışmasıdır: üye sorgusu `uye_id` alanını sabitliyor ama önekteki `sube`
alanını sabitlemiyor, bu yüzden yönlendirici hangi parçaya gideceğini bilemiyor. Ağırlıklı
ortalama 3,50 ile beş adayın en kötüsü. Bileşik anahtar eklemek erişimi daraltmaz, önek koşulunu
sıkılaştırır.

`alinma_gunu` ile karma yerleşiminin karşılaştırması ödünleşimi tek bir sütunda gösteriyor. Karma
yerleşim sıcak parçayı yüzde 100'den 59,6'ya indirdi; karşılığında son yedi günün ödünçleri
sorgusu 1,00'dan 4,00 parçaya çıktı ve ağırlıklı ortalama 2,95'ten 3,40'a yükseldi. Karma
yerleşim bitişik değerleri bitişik parçalara koymadığı için **aralık sorgusu yönlendirilemez**.
Yazmayı yayan şeyle okumayı toplayan şey burada doğrudan karşı karşıya gelir.

Son satır koşumdan bağımsız kalan sınırı yineliyor: bir anahtarın farklı değer sayısı, parça
sayısının üst sınırıdır. Bu sınır makineye, veriye ya da ayara bağlı değildir.

#### Özet

- Parça anahtarı bir belge alanıdır ve alanı taşımayan belge yönlendirilemez: şube tabanlı
  anahtarda 20.000 belgenin 406'sı hiçbir parçaya gidemedi.
- Anahtarın farklı değer sayısı parça sayısının üst sınırıdır; altı şubeye bölünen derlemde en
  dolu parça ortalamanın 1,81 katı oldu ve daha çok parça eklemek bunu düzeltmez.
- Dengeli yerleşim sıcak parçayı önlemez: `alinma_gunu` anahtarında yerleşim 1,01 kat dengeliyken
  son yedi günün 782 ödüncünün yüzde 100'ü tek parçaya düştü.
- Karma yerleşim sıcak parçanın payını yüzde 59,6'ya indirdi ama aralık sorgusunu 1,00 parçadan
  4,00 parçaya çıkardı; ağırlıklı ortalama 2,95'ten 3,40'a yükseldi.
- Bileşik anahtar önek koşuluna bağlıdır: `{sube,uye_id}` yerleşimi kusursuz dengeledi ama önekini
  sabitlemeyen üye sorgusu 4,00 parçaya dağıldı ve ağırlıklı ortalama 3,50 ile en kötü çıktı.
- Bu karışımda en iyi anahtar `uye_id` oldu: 1,00 kat denge, yüzde 27 sıcak pay ve 2,65 ağırlıklı
  parça. Seçim dengeye değil, **karışımın en ağır sorgusuna** bakılarak yapılır.

#### Sonraki Adım

Bu ders bir belgenin hangi parçaya gideceğini çözdü, ama parçanın kendisi önceki dersin çoğaltma
kümesidir: her parça birden çok üyeden oluşur ve o üyelerden hangilerinin yazmayı görmüş olması
gerektiği hâlâ açık. İki ders de aynı boşluğu bıraktı — birinci derste kabul edilmiş 6 ödünç
kaydı geri alındı ve bunun nasıl önleneceği söylenmedi. Bir yazmanın "başarılı" sayılması için
kaç üyenin onay vermesi gerektiği bir ayardır; aynı biçimde bir okumanın hangi üyeden ve hangi
doğrulamayla yanıtlanacağı da bir ayardır. Sonraki ders bu iki ayarı adlandırır ve aynı ödünç
senaryosunu üç ayarda koşturur: onay için beklenen tur sayısı, arıza anında kaybolan yazma sayısı
ve okumada görülen sürüm nasıl değişiyor.

### Okuma ve Yazma İlgileri

Source: https://academia.sh/tr/kurslar/nosql/okuma-ve-yazma-ilgileri

Onay düzeyinin bir ayar olarak ölçülmesi: aynı ödünç senaryosunun üç yazma ilgisinde koşturulup onay turu, onaylanan yazma ve arıza anında kaybolan yazma sayılarının sayılması, geri alınan yazma ile kayıp yazmanın ayrılması, tüm üyelerden onay istemenin tek bir üye kaybını yazma kesintisine çevirmesinin gösterilmesi ve okuma ilgisi ile okuma yönünün dört bileşiminde yanıtsız istek, bayat okuma ve geri alınacak veriyi gören okuma sayılarının karşılaştırılması.

Önceki iki ders aynı boşluğu bıraktı. Çoğaltma kümesinde kabul edilmiş ödünç kayıtları arıza
sonrasında geri alındı ve bunun nasıl önleneceği söylenmedi; parçalama dersinde her parçanın
kendi çoğaltma kümesi olduğu belirtildi ama bir yazmanın kaç üyeye ulaşmış sayılacağı açıkta
kaldı. Bu iki soru tek bir ayarın iki yüzüdür.

Ayarın adı **yazma ilgisidir** (write concern): bir yazmanın çağırana başarılı diye bildirilmesi
için kaç üyenin onu uygulamış olması gerektiği. Karşılığı okuma tarafında **okuma ilgisidir**
(read concern): bir okumanın hangi doğrulama düzeyiyle yanıtlanacağı. İkisi de derlem ya da
istek başına verilebilen ayarlardır; ne deponun ne de kümenin sabit bir özelliğidir.

#### İki Ayrı Ayar

Yazma ilgisinin taşıdığı sayıya **onay düzeyi** denir. `w=1` yalnız birincilin uygulamasını
ister; `w=çoğunluk` üyelerin yarısından fazlasının uygulamasını ister; `w=hepsi` bütün üyeleri
ister. Onay düzeyi yükseldikçe çağıran daha uzun bekler, çünkü onay en yakın üyeden değil, gereken
sayıdaki **en uzak** üyeden döner.

Okuma ilgisinin iki ayarı burada ölçülür. `yerel` okuma, seçilen üyenin elindeki değeri
doğrulamadan döndürür. `çoğunluk` okuması yalnız çoğunluğa ulaşmış — yani geri alınamayacak —
veriyi döndürür. Okuma ilgisinden bağımsız üçüncü bir ayar da vardır ve karıştırılmaması gerekir:
**okuma yönü**, isteğin birinciliye mi bir ikincile mi gideceğini belirler. İkisi ayrı ayrı
seçilir ve ölçümde ayrı sütunlar üretir.

#### Düzenek

Aşağıdaki düzenek yine bir **modeldir**: gerçek küme kurulmaz, tur soyut bir adımdır ve üye
uzaklıkları tur cinsinden parametredir. Onay, gereken sayıda üyenin yazmayı uyguladığını
birincilin **öğrenmesidir**; bu yüzden bedeli tek yön değil, gidiş dönüştür.

**NS19 — üyelerin birinciliye uzaklığı 3, 5, 6 ve 8 turdur.** Gerekçe: ikincillerin bir kısmı
aynı binada, bir kısmı uzak bir yerleşkededir. **NS20 — her turda bir ödünç kaydı yazılır ve bir
okuma gelir.** Gerekçe: yazma ile okuma hızının eşit olduğu varsayılır; oran değişirse bayat
okuma sayısı doğrusal olarak değişir. Arıza takvimi birinci dersle aynıdır: birincil durur,
algılama iki tur, seçim iki tur.

```js
// ilgi/model.mjs — onay duzeyi MODELIDIR. Tur soyut bir adimdir; uye basina gecikme, arizanin
// turu ve secim suresi tur cinsinden parametrelerdir, olculmus sure degildir. Gercek kume
// kurulmaz. Yazma her turda bir odunc kaydidir; onay, w uyenin yazmayi uyguladigini birincilin
// ogrenmesidir (gidis-donus, yani gecikmenin iki kati).
export const GECIKME = [0, 3, 5, 6, 8];   // uye basina birincile uzaklik (tur)
export const UYE = 5, ARIZA = 21, ALGILAMA = 2, SECIM = 2, TUR = 48;
const COGUNLUK = Math.floor(UYE / 2) + 1;

export function kosum({ w, okuma = "yerel", yon = "birincil" }) {
  const kapali = new Set();
  const kayit = [0], uygulanan = Array(UYE).fill(0), yazma = [], okunan = [];
  let birincil = 0, sira = 0, bekleme = 0, kesme = null, devir = null;

  // ayaktaki ikincillerin birincile uzakligi, kucukten buyuge
  const uzaklik = () => [...Array(UYE).keys()]
    .filter((i) => i !== birincil && !kapali.has(i))
    .map((i) => Math.abs(GECIKME[i] - GECIKME[birincil])).sort((a, b) => a - b);
  const onaySuresi = (k) => {
    if (k <= 1) return 0;
    const u = uzaklik();
    return u.length >= k - 1 ? 2 * u[k - 2] : null;   // yeterli uye yoksa onay hic gelmez
  };
  // o ana kadar onaylanmis ve geri alinmamis en buyuk sira: bayatligin olculdugu esik
  const sonOnay = (t) => yazma.reduce((m, y) =>
    (y.onay !== null && y.onay <= t && y.geriAlindi === false && y.sira > m ? y.sira : m), 0);

  for (let t = 1; t <= TUR; t += 1) {
    if (t === ARIZA) { kapali.add(birincil); birincil = null; }

    if (birincil === null) {
      bekleme += 1;
      const oy = [...Array(UYE).keys()].filter((i) => !kapali.has(i));
      if (oy.length >= COGUNLUK && bekleme === ALGILAMA + SECIM) {
        birincil = oy.reduce((a, b) => (uygulanan[b] > uygulanan[a] ? b : a));
        kesme = uygulanan[birincil];
        sira = kesme; devir = t;
        for (let x = 0; x < kayit.length; x += 1) kayit[x] = Math.min(kayit[x], kesme);
        for (const y of yazma) if (y.sira > kesme) y.geriAlindi = true;   // gunluk kirpilir
      }
    } else {
      sira += 1;
      kayit[t] = sira;
      const s = onaySuresi(w);
      yazma.push({ sira, gelis: t, onay: s === null ? null : t + s, geriAlindi: false });
      for (let i = 0; i < UYE; i += 1) {
        if (kapali.has(i)) continue;
        uygulanan[i] = i === birincil ? sira
          : Math.max(uygulanan[i], kayit[t - Math.abs(GECIKME[i] - GECIKME[birincil])] ?? 0);
      }
    }
    // birincil olmeden once tamamlanmayan onay hicbir zaman gelmez
    if (t === ARIZA) for (const y of yazma) if (y.onay !== null && y.onay >= t) y.onay = null;

    const ikincil = [...Array(UYE).keys()].filter((i) => i !== birincil && !kapali.has(i));
    const m = yon === "birincil" ? birincil : (ikincil.length ? ikincil[t % ikincil.length] : null);
    if (m === null) { okunan.push({ t, yanitsiz: true }); continue; }  // birincil yokken yanit yok
    const sc = onaySuresi(COGUNLUK);
    const taahhut = sc === null ? sonOnay(t) : Math.min(sonOnay(t), kayit[t - sc] ?? 0);
    const deger = okuma === "yerel" ? uygulanan[m] : Math.min(uygulanan[m], taahhut);
    okunan.push({ t, uye: m, deger, gercek: sonOnay(t), yanitsiz: false });
  }

  const onayli = yazma.filter((y) => y.onay !== null && y.onay <= TUR);
  const yanitli = okunan.filter((o) => o.yanitsiz === false);
  return {
    onayTuru: yazma[0].onay === null ? null : yazma[0].onay - yazma[0].gelis,
    onaylanan: onayli.length, onaysiz: yazma.length - onayli.length,
    geriAlinan: yazma.filter((y) => y.geriAlindi).length,
    kayip: onayli.filter((y) => y.geriAlindi).length,
    sonrasi: onayli.filter((y) => y.gelis > devir).length,
    yanitsiz: okunan.filter((o) => o.yanitsiz).length,
    bayat: yanitli.filter((o) => o.deger < o.gercek).length,
    gerilik: yanitli.reduce((a, o) => a + (o.gercek - o.deger), 0) / yanitli.length,
    // geri alinacak veriyi goren okuma: yalniz ariza oncesi okumalar sayilir, cunku kirpma
    // sonrasi ayni sira numaralari yeniden ve gecerli olarak kullanilir
    hayalet: yanitli.filter((o) => o.t < ARIZA && kesme !== null && o.deger > kesme).length,
    okuma: yanitli.length, devir, kesme,
  };
}
```

```js
// ilgi/olc.mjs — ayni odunc senaryosu uc yazma ilgisinde, sonra dort okuma ayarinda
import { kosum, UYE, ARIZA, TUR, GECIKME } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const AD = { 1: "w=1", 3: "w=cogunluk", 5: "w=hepsi" };
console.log(`${UYE} uyeli kume, uye uzakligi ${GECIKME.join("/")} tur. ${TUR} tur, her turda bir`);
console.log(`odunc kaydi. Birincil ${ARIZA}. turda durur; algilama 2, secim 2 tur.\n`);
console.log("yazma ilgisi | onay turu | onaylanan | onaysiz | geri alinan | KAYIP YAZMA | ariza sonrasi onay");
console.log("-------------|-----------|-----------|---------|-------------|-------------|-------------------");
for (const w of [1, 3, 5]) {
  const r = kosum({ w });
  console.log(`${AD[w].padEnd(12)} | ${s(r.onayTuru === null ? "yok" : r.onayTuru, 9)} | ${s(r.onaylanan, 9)} | ` +
    `${s(r.onaysiz, 7)} | ${s(r.geriAlinan, 11)} | ${s(r.kayip, 11)} | ${s(r.sonrasi, 18)}`);
}

console.log("\nokuma tarafi (yazma ilgisi w=1 sabit; kirpma siniri: kayit " + kosum({ w: 1 }).kesme + "):");
console.log("okuma yonu | okuma ilgisi | yanitsiz | bayat okuma | ortalama gerilik | geri alinacak veriyi goren");
console.log("-----------|--------------|----------|-------------|------------------|---------------------------");
for (const yon of ["birincil", "ikincil"]) {
  for (const okuma of ["yerel", "cogunluk"]) {
    const r = kosum({ w: 1, okuma, yon });
    console.log(`${yon.padEnd(10)} | ${okuma.padEnd(12)} | ${s(r.yanitsiz, 8)} | ` +
      `${s(`${r.bayat}/${r.okuma}`, 11)} | ${s(r.gerilik.toFixed(2), 16)} | ${s(r.hayalet, 26)}`);
  }
}

const u = GECIKME.slice(1).sort((a, b) => a - b);
console.log("\nkosumdan bagimsiz: onay turu, ikincillerin uzaklik siralamasindaki (w-1). degerin");
console.log(`iki katidir. Bu kumede siralama ${u.join(" < ")}, yani w buyudukce onay turu`);
console.log(`${u.map((x) => 2 * x).join(", ")} degerlerini alir; hicbir w'de azalmaz.`);
```

```
5 uyeli kume, uye uzakligi 0/3/5/6/8 tur. 48 tur, her turda bir
odunc kaydi. Birincil 21. turda durur; algilama 2, secim 2 tur.

yazma ilgisi | onay turu | onaylanan | onaysiz | geri alinan | KAYIP YAZMA | ariza sonrasi onay
-------------|-----------|-----------|---------|-------------|-------------|-------------------
w=1          |         0 |        44 |       0 |           3 |           3 |                 24
w=cogunluk   |        10 |        28 |      16 |           3 |           0 |                 18
w=hepsi      |        16 |         4 |      40 |           3 |           0 |                  0

okuma tarafi (yazma ilgisi w=1 sabit; kirpma siniri: kayit 17):
okuma yonu | okuma ilgisi | yanitsiz | bayat okuma | ortalama gerilik | geri alinacak veriyi goren
-----------|--------------|----------|-------------|------------------|---------------------------
birincil   | yerel        |        3 |        0/45 |             0.00 |                          3
birincil   | cogunluk     |        3 |       44/45 |             7.82 |                          0
ikincil    | yerel        |        0 |       48/48 |             4.06 |                          0
ikincil    | cogunluk     |        0 |       48/48 |             8.69 |                          0

kosumdan bagimsiz: onay turu, ikincillerin uzaklik siralamasindaki (w-1). degerin
iki katidir. Bu kumede siralama 3 < 5 < 6 < 8, yani w buyudukce onay turu
6, 10, 12, 16 degerlerini alir; hicbir w'de azalmaz.
```

#### Kayıp Yazma, Geri Alınan Yazmadan Başkadır

Birinci tablonun iki sütunu birbirine benziyor ve aynı şey değil. **Geri alınan yazma** üç
ayarda da 3'tür; bu, kümenin bir olgusudur ve ayarla değişmez, çünkü eski birincilin yazdığı ama
hiçbir hayatta kalan üyeye ulaşmamış üç kayıt vardır. **Kayıp yazma** ise bunların içinden
çağırana **başarılı diye bildirilmiş** olanlarıdır ve ayarla doğrudan değişir: `w=1` ayarında 3,
`w=çoğunluk` ve `w=hepsi` ayarlarında 0.

`w=çoğunluk` ayarının kayıp yazmayı sıfırlaması bir tesadüf değil, önceki dersteki seçim
kuralının doğrudan sonucudur. Bir yazma çoğunluğa ulaştıysa, yeni birincil olabilecek her üye o
çoğunluğun içinden gelir; aday oy veren çoğunluktan geride olamayacağı için o yazma yeni
birincilde de vardır. Yazma ilgisi ile seçim kuralı aynı sayının iki ucudur.

Bedel onay turu sütunundadır: 0 tura karşılık 10 tur. Bu sayı koşuma bağlı değildir; son bloktaki
satır onu türetiyor. Onay turu, ikincillerin uzaklık sıralamasındaki `(w-1)`. değerin iki
katıdır. Bu kümede sıralama 3, 5, 6, 8 olduğu için w büyüdükçe onay turu 6, 10, 12, 16 değerlerini
alır ve hiçbir adımda azalmaz. Onay düzeyini yükseltmek yazmayı yavaşlatmakla kalmaz — **hangi
üyenin yavaş olduğunu doğrudan çağıranın beklediği süreye çevirir**.

#### Tümünden Onay İstemenin Bedeli

`w=hepsi` satırı en sert sonucu veriyor. Arıza öncesinde 20 yazmanın yalnız 4'ü onaylandı;
arızadan sonra ise **hiçbiri** onaylanmadı. Sebep aritmetiktir: bir üye kapalıyken "bütün üyeler"
koşulu hiçbir zaman sağlanamaz. Beş üyeli bir kümede tek bir üyenin kaybı, `w=hepsi` ayarında
yazmayı tamamen durdurur.

Bu satır, onay düzeyinin "yüksek olması iyidir" biçiminde okunamayacağını gösteriyor. `w=hepsi`
kayıp yazmayı sıfırlar ama bunu 40 onaysız yazmayla ve arıza sonrası sıfır verimle satın alır.
`w=çoğunluk` aynı sıfır kaybı 16 onaysız yazmayla ve arıza sonrası 18 onaylı yazmayla sağlar.
Çoğunluk kuralı burada da tepe noktasıdır: onay düzeyini çoğunluğun üstüne çıkarmak dayanıklılık
eklemez, yalnız kırılganlık ekler.

Onaysız kalan yazmalar veri kaybı değildir; çağıranın **ne olduğunu bilmediği** yazmalardır. Bir
ödünç kaydı üyenin hesabına geçmiş de olabilir, geçmemiş de. Yeniden denenebilir olması, ödünç
verme işleminin kendisinin yinelenebilir tasarlanmasını gerektirir.

#### Okuma Yönü ve Okuma İlgisi

İkinci tablo dört bileşimi aynı senaryoda karşılaştırıyor ve dördü de farklı bir bedel ödüyor.

`birincil` + `yerel`: 45 yanıtın hiçbiri bayat değil, ortalama gerilik 0,00. Buna karşılık 3 okuma
**sonradan geri alınacak veriyi gördü** ve 3 istek arıza penceresinde yanıtsız kaldı. Bu ayar en
güncel yanıtı verir ve en çok yanılma riskini taşır: ödünç ekranında görünen kayıt, birkaç tur
sonra hiç var olmamış hâle gelebilir.

`birincil` + `çoğunluk`: geri alınacak veriyi gören okuma 0'a iniyor. Bedel bayat okuma
sütunundadır — 45 yanıtın 44'ü bayat ve ortalama gerilik 7,82 ödünç kaydı. Çoğunluk okuması
"doğru" değeri vermez; **geri alınmayacak** değeri verir ve bunun için çoğunluğun yetişmesini
bekler.

`ikincil` yönlü iki satırın ortak kazancı yanıtsız sütunundadır: 0. İkincilden okuyan bir istek,
birincil düştüğünde ve seçim sürerken de yanıt alır. Kütüphane katalog aramaları için doğru ayar
budur. Bedeli iki satırda da bayatlıktır: `yerel` ile 4,06, `çoğunluk` ile 8,69 kayıt gerilik.
İkincil yönü çoğunluk okumasıyla birleştirmek iki bekleme kaynağını üst üste koyar — üyenin kendi
gerikliği artı çoğunluğun taahhüt gecikmesi.

Buradan çıkan kural işlem başına ayar yapmaktır. Ödünç verme yazması `w=çoğunluk` ile yazılır ve
üyeye "kitap sizde" denmeden önce çoğunluk beklenir; aynı kaydın kataloğa yansıması `ikincil` +
`yerel` ile okunur ve dört kayıtlık gerilik kabul edilir.

#### Özet

- Yazma ilgisi ile okuma ilgisi ayarlardır ve istek başına verilir; onay düzeyi, bir yazmanın
  başarılı sayılması için gereken üye sayısıdır.
- Geri alınan yazma üç ayarda da 3 çıktı — bu kümenin olgusudur; kayıp yazma ayarla değişti:
  `w=1` ile 3, `w=çoğunluk` ve `w=hepsi` ile 0.
- Onay turu koşumdan bağımsız türetilir: ikincillerin uzaklık sıralamasındaki `(w-1)`. değerin iki
  katı — bu kümede 6, 10, 12, 16 ve hiçbir adımda azalmaz.
- `w=hepsi` ayarında tek bir üyenin kaybı yazmayı tamamen durdurdu: arıza sonrası onaylanan yazma
  0, toplam onaysız 40. Çoğunluğun üstüne çıkmak dayanıklılık eklemedi.
- Okuma ayarlarının dört bileşimi dört ayrı bedel ödedi: `birincil`+`yerel` 3 yanıtsız istek ve
  geri alınacak veriyi gören 3 okuma; `birincil`+`çoğunluk` 44/45 bayat ve 7,82 gerilik;
  `ikincil` yönü 0 yanıtsız ama 48/48 bayat.
- Okuma yönü ile okuma ilgisi ayrı ayarlardır; ikisini birleştirmek iki bekleme kaynağını üst üste
  koyar (4,06'ya karşı 8,69 gerilik).

#### Sonraki Adım

Bu dersin tablolarında adı konmamış bir şey var. `w=çoğunluk` ile `çoğunluk` okumasının birlikte
verdiği güvenceyle, `w=1` ile `yerel` okumanın verdiği güvence aynı adı taşımaz; ikisinin arasında
duran bileşimler de vardır ve her biri ayrı bir söz verir. Bu sözlerin adları Sistem Tasarımına
Giriş kursunda tanımlandı: güçlü tutarlılık, sonunda tutarlılık, monotonik okuma ve kendi
yazdığını okuma. Tanımlar orada kaldı; burada eksik olan, hangi ayar bileşiminin hangi sözü
verdiğidir. Sonraki ders bu eşlemeyi kurar, aralarına listede bulunmayan bir modeli — nedensel
tutarlılığı — ekler ve aynı üye oturumunu altı ayar bileşiminde koşturarak kendi yazdığını
okuyamama sayısını, monotonik ihlali, nedensel bağın koptuğu okuma sayısını ve her güvencenin
bedelini oluşturan bekleme turlarını sayar.

### Tutarlılık Modelleri

Source: https://academia.sh/tr/kurslar/nosql/tutarlilik-modelleri

Ayar bileşimlerinin tutarlılık modeline çevrilmesi: hangi okuma ve yazma ilgisinin hangi modeli verdiğinin aynı üye oturumunun altı ayarda koşturulmasıyla ölçülmesi, oturum güvencelerinin korunduğu ayarların kendi yazdığını okuyamama ve monotonik ihlal sayılarıyla ayrılması, ayrı parçalarda duran iki derlem üzerinde nedensel tutarlılığın tanımlanması ve nedensel bağı kopan okumaların sayılması, her güvencenin bedelinin beklenen tur sayısına çevrilmesi.

Önceki dersin iki tablosu ayarları saydı ama adlarını koymadı. `w=1` ile `yerel` okumanın verdiği
söz ile `w=çoğunluk` ile `çoğunluk` okumasının verdiği söz aynı değildir; ikisinin arasında duran
bileşimler de ayrı şeyler vaat eder. Bu sözlerin adları — güçlü tutarlılık, sonunda tutarlılık,
monotonik okuma, kendi yazdığını okuma — Sistem Tasarımına Giriş kursunda tanımlandı ve orada
ölçüldü. Tanımlar burada tekrarlanmaz.

Bu dersin işi çeviridir. Depoda bir tutarlılık modeli seçilmez; bir **ayar** seçilir. Yazma
ilgisi, okuma ilgisi ve okuma yönünden oluşan üçlü bir model adına karşılık gelir ve o adı hak
edip etmediği sayılabilir. Ders o eşlemeyi kurar, listede bulunmayan bir modeli — nedensel
tutarlılığı — ekler ve aynı üye oturumunu altı ayarda koşturur.

#### Nedensel Tutarlılık

Sonunda tutarlılık ile güçlü tutarlılık arasında geniş bir aralık vardır ve o aralığın en
kullanışlı noktası **nedensel tutarlılıktır** (causal consistency). Sözü şudur: bir yazma başka
bir yazmanın sonucu görülerek yapılmışsa, ikincisini gören her okuma birincisini de görür.
Aralarında bağ olmayan yazmalar farklı sıralarla görülebilir. Güçlü tutarlılıktan ayrıldığı yer
budur: bütün yazmaları tek sıraya dizmez, yalnız **bağlı olanları** dizer.

Kütüphane veritabanında bağ somuttur. Bir ödünç kaydı yazılır; kayıt onaylandıktan sonra o ödünce
ait gecikme cezası yazılır. Ceza kaydı ödünç kaydına atıf yapar ve ancak onun varlığı görüldüğü
için üretilmiştir. Bağın kopması tek bir okumadır: **ceza görünüyor, atıf yaptığı ödünç kaydı
görünmüyor.** Ekranda kaynağı bulunamayan bir ceza kalır.

Tek bir çoğaltma kümesinde bu olamaz, çünkü ikincil birincilin günlüğünü sırayla uygular: ceza
kaydını uygulamış bir üye ödünç kaydını da uygulamıştır. Kopma, iki derlemin **ayrı parçalarda**
durmasıyla gelir. Önceki iki ders bu düzeni kurdu — parça anahtarı derlem başına seçilir ve her
parça kendi çoğaltma kümesidir. İki kümenin üye uzaklıkları birbirinden bağımsızdır ve okuyan
oturum iki derlemi iki ayrı üyeden okur. Sıra güvencesi kümenin içindedir, kümeler arasında
değil.

#### Düzenek

Düzenek yine bir modeldir: gerçek küme kurulmaz, tur soyut bir adımdır, üye uzaklığı tur
cinsinden bir parametredir.

**NS21 — ceza parçasının çoğaltma kümesinde üye uzaklıkları 1, 2, 3 ve 4 turdur; ödünç parçasının
kümesi NS19'un kümesidir (3, 5, 6, 8 tur).** Gerekçe: ceza derlemi tek yerleşkede tutulur, ödünç
derlemi şubelere yayılmıştır. **NS22 — sekiz turda bir ödünç işlemi yapılır ve ceza kaydı, ödünç
kaydının onayından bir tur sonra gönderilir.** Gerekçe: ceza, kaydın işlendiği görüldükten sonra
yazılır. İki varsayım da doğrusal etkilidir: uzaklıklar büyürse kopma penceresi aynı oranda
büyür.

```js
// oturum/model.mjs — oturum guvenceleri ve nedensellik MODELIDIR: tur soyut bir adimdir, uye
// uzakligi tur cinsinden bir parametredir, gercek kume kurulmaz. Odunc derlemi ile ceza derlemi
// AYRI PARCALARDA durur; her parca kendi cogaltma kumesidir ve uzakliklari ayridir. Ceza kaydi,
// odunc kaydi ONAYLANDIKTAN sonra gonderilir: iki yazma arasindaki bag nedenseldir.
export const GECIKME = { odunc: [0, 3, 5, 6, 8], ceza: [0, 1, 2, 3, 4] };
export const ISLEM = 12, ARALIK = 8, TUR = 108, COGUNLUK = 3, IKINCIL = [1, 2, 3, 4];

// 03. dersin turetimi: onay turu, ikincillerin uzaklik siralamasindaki (w-1). degerin iki kati
export const onayTuru = (parca, w) =>
  w <= 1 ? 0 : 2 * GECIKME[parca].slice(1).sort((a, b) => a - b)[w - 2];

// Bir uyenin t turunda gordugu en buyuk sira. Cogunluk okumasi ayrica cogunluga ulasmis ve
// birincilin bunu ogrendigi noktayla sinirlidir (03. dersteki taahhut noktasi).
const gorunen = (yazma, parca, uye, t, okuma) => yazma.reduce((s, y) =>
  y.gonderim + GECIKME[parca][uye] <= t
  && (okuma !== "cogunluk" || y.gonderim + onayTuru(parca, COGUNLUK) <= t) ? y.sira : s, 0);

// "birincil" tek uye; "yapiskan" oturumu iki parcada da en yakin ikincile baglar; "donusumlu"
// her turda baska bir ikincile gider ve iki parcayi ters yonde dolasir. Dort turda bir, odunc
// okumasi en yakin ikincilden en uzagina atlar: monotonik ihlalin gorunecegi yer orasidir.
const uyeSec = (yon, t, parca) => yon === "birincil" ? 0 : yon === "yapiskan" ? IKINCIL[0]
  : parca === "ceza" ? IKINCIL[t % 4] : IKINCIL[(3 * t) % 4];

export function kosum({ w, okuma = "yerel", yon = "birincil", belirtec = false }) {
  const onay = onayTuru("odunc", w), odunc = [], ceza = [];
  for (let j = 1; j <= ISLEM; j += 1) {            // odunc takvimi ayardan bagimsizdir
    const g = 1 + (j - 1) * ARALIK;
    odunc.push({ sira: j, gonderim: g, onay: g + onay });
    ceza.push({ sira: j, gonderim: g + onay + 1 });
  }
  const oku = (y, p, u, t) => gorunen(y, p, u, t, okuma);
  // Oturum belirteci acikken okuma, hedefe ulasmamis uyeden yanitlanmaz: uye yetisene kadar
  // beklenir. Beklenen tur sayisi guvencenin bedelidir.
  const yetis = (y, p, u, t, hedef) => {
    let x = t;
    while (x <= TUR && oku(y, p, u, x) < hedef) x += 1;
    return x - t;
  };

  let kyo = 0, yazmaBekleme = 0;                   // yazan oturum: onay turunda kaydi geri okur
  for (const y of odunc) {
    const t = y.onay, u = uyeSec(yon, t, "odunc");
    if (oku(odunc, "odunc", u, t) >= y.sira) continue;
    if (belirtec) yazmaBekleme += yetis(odunc, "odunc", u, t, y.sira); else kyo += 1;
  }

  // okuyan oturum: her turda once ceza derlemi, sonra cezanin atif yaptigi odunc kaydi okunur
  let mono = 0, kopma = 0, bayat = 0, okumaBekleme = 0, beklemeli = 0, gorulenSira = 0;
  for (let t = 1; t <= TUR; t += 1) {
    const gc = oku(ceza, "ceza", uyeSec(yon, t, "ceza"), t);
    const u = uyeSec(yon, t, "odunc");
    let go = oku(odunc, "odunc", u, t), tr = t;    // tr: yanitin verildigi tur
    if (belirtec) {
      const b = yetis(odunc, "odunc", u, t, Math.max(gc, gorulenSira));
      if (b > 0) { beklemeli += 1; okumaBekleme += b; tr = t + b; go = oku(odunc, "odunc", u, tr); }
    }
    if (go < gorulenSira) mono += 1;
    if (gc > go) kopma += 1;                       // ceza gorunuyor, atif yaptigi odunc gorunmuyor
    if (go < odunc.filter((y) => y.onay <= tr).length) bayat += 1;  // yanit aninda onaylanmisa gore
    gorulenSira = Math.max(gorulenSira, go);
  }
  return { kyo, mono, kopma, bayat, bekleme: yazmaBekleme + okumaBekleme,
    yazmaBekleme, okumaBekleme, beklemeli, onay };
}
```

```js
// oturum/olc.mjs — ayni uye oturumu alti ayarda: hangi ayar hangi guvenceyi veriyor
import { kosum, onayTuru, GECIKME, IKINCIL, ISLEM, ARALIK, TUR, COGUNLUK } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const AYAR = [
  ["w=1 / yerel / donusumlu", { w: 1, yon: "donusumlu" }],
  ["w=1 / yerel / yapiskan", { w: 1, yon: "yapiskan" }],
  ["w=1 / yerel / donusumlu + belirtec", { w: 1, yon: "donusumlu", belirtec: true }],
  ["w=1 / yerel / birincil", { w: 1, yon: "birincil" }],
  ["w=cogunluk / cogunluk / birincil", { w: COGUNLUK, okuma: "cogunluk", yon: "birincil" }],
  ["w=cogunluk / cogunluk / donusumlu", { w: COGUNLUK, okuma: "cogunluk", yon: "donusumlu" }],
];
console.log(`odunc parcasinin uye uzakliklari ${GECIKME.odunc.join("/")}, ceza parcasininki ` +
  `${GECIKME.ceza.join("/")} tur.\n${TUR} tur, ${ARALIK} turda bir odunc kaydi (${ISLEM} islem); ` +
  `ceza kaydi kendi odunc kaydinin\nonayindan bir tur sonra gonderilir.\n`);
console.log("ayar (yazma ilgisi / okuma ilgisi / yon) | kendi yazdigini | monotonik | nedensel | bayat  | onay | bekleme");
console.log("                                         |    okuyamama    |   ihlal   |  kopma   | okuma  | turu |  (tur)");
console.log("-----------------------------------------|-----------------|-----------|----------|--------|------|--------");
for (const [ad, secim] of AYAR) {
  const r = kosum(secim);
  console.log(`${ad.padEnd(40)} | ${s(`${r.kyo}/${ISLEM}`, 15)} | ${s(r.mono, 9)} | ` +
    `${s(`${r.kopma}/${TUR}`, 8)} | ${s(`${r.bayat}/${TUR}`, 6)} | ${s(r.onay, 4)} | ${s(r.bekleme, 7)}`);
}
const b = kosum({ w: 1, yon: "donusumlu", belirtec: true });
console.log(`\nbelirtecli ayarda bekleme: ${b.yazmaBekleme} tur ${ISLEM} geri okumada, ` +
  `${b.okumaBekleme} tur okuyan oturumda\n(${b.beklemeli}/${TUR} okuma bekledi).`);

// Kosumdan bagimsiz: ceza gorunup odunc gorunmemesi, ancak odunc uyesinin uzakligi ceza
// uyesinin uzakligindan iki yazma arasindaki bosluktan (onay turu + 1) fazla buyukse olur.
for (const w of [1, COGUNLUK]) {
  const bosluk = onayTuru("odunc", w) + 1;
  const n = IKINCIL.flatMap((uo) => IKINCIL.map((uc) =>
    GECIKME.odunc[uo] > GECIKME.ceza[uc] + bosluk)).filter(Boolean).length;
  console.log(`kosumdan bagimsiz: iki yazma arasindaki bosluk w=${w === 1 ? "1" : "cogunluk"} ` +
    `ayarinda ${bosluk} tur;\n${IKINCIL.length ** 2} uye ciftinin ${n} tanesinde kopma, ` +
    `yonlendirme hangisi olursa olsun kacinilmazdir.`);
}
const enUzak = Math.max(...GECIKME.odunc), oc = onayTuru("odunc", COGUNLUK);
console.log(`kosumdan bagimsiz: en buyuk uye uzakligi ${enUzak} tur, cogunluk onay turu ${oc} tur;` +
  `\n${enUzak} < ${oc} oldugu icin cogunluk okumasinin dondurdugu deger okunan uyeden bagimsizdir.`);
```

```
odunc parcasinin uye uzakliklari 0/3/5/6/8, ceza parcasininki 0/1/2/3/4 tur.
108 tur, 8 turda bir odunc kaydi (12 islem); ceza kaydi kendi odunc kaydinin
onayindan bir tur sonra gonderilir.

ayar (yazma ilgisi / okuma ilgisi / yon) | kendi yazdigini | monotonik | nedensel | bayat  | onay | bekleme
                                         |    okuyamama    |   ihlal   |  kopma   | okuma  | turu |  (tur)
-----------------------------------------|-----------------|-----------|----------|--------|------|--------
w=1 / yerel / donusumlu                  |           12/12 |        24 |   24/108 | 60/108 |    0 |       0
w=1 / yerel / yapiskan                   |           12/12 |         0 |   12/108 | 36/108 |    0 |       0
w=1 / yerel / donusumlu + belirtec       |            0/12 |         0 |    0/108 | 47/108 |    0 |     156
w=1 / yerel / birincil                   |            0/12 |         0 |    0/108 |  0/108 |    0 |       0
w=cogunluk / cogunluk / birincil         |            0/12 |         0 |    0/108 |  0/108 |   10 |       0
w=cogunluk / cogunluk / donusumlu        |            0/12 |         0 |    0/108 |  0/108 |   10 |       0

belirtecli ayarda bekleme: 96 tur 12 geri okumada, 60 tur okuyan oturumda
(24/108 okuma bekledi).
kosumdan bagimsiz: iki yazma arasindaki bosluk w=1 ayarinda 1 tur;
16 uye ciftinin 12 tanesinde kopma, yonlendirme hangisi olursa olsun kacinilmazdir.
kosumdan bagimsiz: iki yazma arasindaki bosluk w=cogunluk ayarinda 11 tur;
16 uye ciftinin 0 tanesinde kopma, yonlendirme hangisi olursa olsun kacinilmazdir.
kosumdan bagimsiz: en buyuk uye uzakligi 8 tur, cogunluk onay turu 10 tur;
8 < 10 oldugu icin cogunluk okumasinin dondurdugu deger okunan uyeden bagimsizdir.
```

#### Oturum Güvenceleri Hangi Ayarda Korunur

İlk satırda ödünç masası kendi yazdığını hiç okuyamıyor: 12 onaylı yazmanın 12'si, onay geldiği
turda yapılan geri okumada bulunamadı. Bu sayı koşuma bağlı değildir. `w=1` ayarında onay turu
0'dır, en yakın ikincil ise 3 tur geridedir; ikincile giden bir geri okuma kaydı hiçbir koşumda
bulamaz. Ödünç ekranında bunun karşılığı, kitap verildikten hemen sonra üyenin ödünç listesinin
boş görünmesidir.

Aynı satırda 24 monotonik ihlal var. Sebep yönlendirmenin dönüşümlü olmasıdır: okuma dört turda
bir en yakın ikincilden en uzağına atlıyor ve o üye aradaki yazmayı henüz uygulamamış oluyor.
Ekranda görünen ödünç sayısı geri gidiyor.

İkinci satır, Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunun **yapışkan yönlendirmesini**
okuma yoluna uyguluyor: oturum tek bir ikincile bağlanıyor. Monotonik ihlal 24'ten 0'a iniyor,
çünkü bir üyenin sıra numarası hiç geri gitmez; bayat okuma da 60'tan 36'ya iniyor. Değişmeyen
tek sütun kendi yazdığını okuyamamadır: hâlâ 12/12. Yapışkanlık istemciyi bir ikincile bağlar, o
ikincil de birincilin gerisindedir.

Dördüncü satır iki oturum güvencesini de bedelsiz veriyor: 0/12, 0 ihlal, 0 bayat okuma, 0 tur
bekleme. Oturum güvencelerinin en ucuz yolu okuma yönünü birincile çekmektir. Bu ayarın bedeli bu
tabloda görünmez; önceki ders ölçtü — arıza penceresinde 3 yanıtsız istek ve sonradan geri
alınacak veriyi gören 3 okuma.

#### Nedensel Bağın Kopması

Nedensel kopma sütunu, oturum güvencelerinin yetmediği yeri gösteriyor. Dönüşümlü ayarda 108
okumanın 24'ünde ceza göründü, atıf yaptığı ödünç kaydı görünmedi. Yapışkan ayarda bu sayı 12'ye
indi ama **sıfırlanmadı** — oysa aynı satırda monotonik ihlal 0. İki sütunun ayrışması bu dersin
ana sonucudur: yapışkan yönlendirme oturumu bir üyeye bağlar ve o üyenin içindeki sırayı korur;
iki ayrı parçanın iki ayrı kümesi arasındaki bağı korumaz. Ceza parçasının en yakın ikincili 1
tur, ödünç parçasınınki 3 tur geridedir ve ceza yazması ödüncün onayından yalnız 1 tur sonra
gönderilir; aradaki iki turluk fark her işlemde bir kopma üretir.

Üçüncü satır bağı kapatan ayarı gösteriyor. Oturum, gördüğü en ileri noktayı bir **oturum
belirtecinde** taşır; okuma, o noktanın gerisinde kalan bir üyeden yanıtlanmaz, üye yetişene kadar
beklenir. Üç ihlal sütunu da 0'a iniyor. Bedel bekleme sütunundadır: 156 tur — 96'sı 12 geri
okumada, 60'ı okuyan oturumda, 108 okumanın 24'ü bekleyerek.

Aynı satırın bayat okuma sütunu 47/108 kalıyor ve bu bir kusur değil, tanımın kendisidir.
Nedensel tutarlılık yanıtın geri gitmeyeceğini söyler, en güncel olacağını değil.

Son iki koşumdan bağımsız satır kopmanın yapısını veriyor. Kopma, ödünç üyesinin uzaklığı ceza
üyesinin uzaklığından iki yazma arasındaki boşluktan fazla büyükse olur. `w=1` ayarında bu boşluk
1 turdur ve 16 üye çiftinin 12'sinde kopma, hangi yönlendirme seçilirse seçilsin kaçınılmazdır.
`w=çoğunluk` ayarında boşluk 11 tura çıkar ve kopmalı çift sayısı 0 olur. Onay düzeyini
yükseltmek kopma olasılığını azaltmaz; kopmayı yapısal olarak ortadan kaldırır.

#### Ayarın Model Adına Çevrilmesi

| Ayar | Verdiği model | Ölçülen karşılığı |
|---|---|---|
| `w=1`, `yerel`, dönüşümlü ikincil | sonunda tutarlılık | 12/12 okuyamama, 24 ihlal, 24/108 kopma |
| `w=1`, `yerel`, yapışkan ikincil | sonunda tutarlılık ve monotonik okuma | 0 ihlal, kopma 12/108 sürüyor |
| `w=1`, `yerel`, oturum belirteci | nedensel tutarlılık | üç ihlal de 0, 156 tur bekleme, 47/108 bayat |
| `w=çoğunluk`, `çoğunluk` | güçlü tutarlılık | bütün sütunlar 0, yazma başına 10 tur onay |

Son iki satır aynı sayıları veriyor: çoğunluk okuması birincilden de dönüşümlü ikincillerden de
aynı değeri döndürüyor. Sebep koşumdan bağımsızdır — en büyük üye uzaklığı 8 tur, çoğunluk onay
turu 10 turdur ve 8 < 10 olduğu için hiçbir üye taahhüt noktasının gerisinde kalamaz. Uzak bir
yerleşkeye 12 tur ötede bir üye eklenirse bu eşitsizlik bozulur ve çoğunluk okuması da hangi
üyeden okunduğuna bağlı hâle gelir.

Dördüncü ve beşinci satırın ayırt edilememesi tablonun sınırıdır, ayarların değil. Küme sağlamken
`birincil` ile `yerel` bileşimi güçlü tutarlılığın bütün sıfırlarını bedava veriyor. Farkı önceki
ders ölçtü: birincil düştüğünde `w=1` ayarında 3 kayıp yazma çıktı, `w=çoğunluk` ayarında 0.
Yazma başına 10 turluk onay o farkın fiyatıdır.

#### Özet

- Depoda tutarlılık modeli seçilmez, ayar seçilir: yazma ilgisi, okuma ilgisi ve okuma yönü
  üçlüsü bir model adına karşılık gelir ve o adı hak edip etmediği sayılabilir.
- `w=1` ile ikincilden okuyan belirteçsiz ayarlarda kendi yazdığını okuyamama 12/12 çıktı; bu
  koşuma bağlı değildir, çünkü onay turu 0 iken en yakın ikincil 3 tur geridedir.
- Yapışkan yönlendirme monotonik ihlali 24'ten 0'a indirdi ama nedensel kopmayı 24'ten yalnız
  12'ye: oturum güvencesi bir üyenin içindeki sırayı korur, iki parçanın kümeleri arasındaki bağı
  korumaz.
- Nedensel tutarlılık bir oturum belirteciyle sağlandı; üç ihlal sütunu da 0'a indi, bedeli 156
  tur bekleme oldu ve bayat okuma 47/108 kaldı.
- Onay düzeyini çoğunluğa çekmek iki yazma arasındaki boşluğu 1 turdan 11 tura çıkardı ve kopmalı
  üye çifti sayısını 12'den 0'a indirdi; bu, olasılık değil yapı değişikliğidir.
- Sağlam kümede `birincil` ile `yerel` bileşimi güçlü tutarlılıkla aynı sıfırları bedelsiz verdi;
  fark ancak arıza penceresinde görünür ve fiyatı yazma başına 10 turdur.

#### Sonraki Adım

Buraya kadarki ölçümler kümenin bütün olduğunu varsaydı: üyeler birbirini görüyor, değişen tek
şey gecikmeydi. Bağ koptuğunda aynı ayarların anlamı değişir: `w=çoğunluk` çoğunluğu toplayamayan
tarafta yazmayı durdurur, `yerel` okuma yanıt vermeyi sürdürür. Bu ödünleşim Sistem Tasarımına
Giriş kursunda CAP ve PACELC adlarıyla kurulup ölçüldü. Depo bağlamında ise ödünleşim yanlış
ifade edilir: bir deponun "CP" ya da "AP" olduğu söylenir, bir ürün özelliğiymiş gibi. Sonraki
ders bu ifadeyi düzeltir: aynı küme aynı bölünmede birden çok ayarla koşturulur ve etiketin
deponun değil ayarın özelliği olduğu sayılır. Bu derste ölçülen 10 turluk onay küme sağlamken de
ödenir; bölünme dışındaki fatura odur.

### CAP ve PACELC

Source: https://academia.sh/tr/kurslar/nosql/cap-ve-pacelc

Ödünleşimin doğru ifade edilmesi: aynı çoğaltma kümesinin aynı bölünmede dört ayarla koşturulup reddedilen yazma, geri alınan yazma, yanıtsız okuma ve bayat okuma sayılarının karşılaştırılması, tek bir ayarın yazma yolunda tutarlılığı okuma yolunda erişilebilirliği seçebildiğinin gösterilmesi, bölünme hiç olmadığında da bir seçim yapıldığının bekleme turu ve eskilik penceresiyle ölçülmesi ve küme boyundan gelen yapısal sınırın ayardan gelen karardan ayrılması.

Önceki ders güçlü tutarlılığın bedelini yazma başına 10 turluk onay olarak ölçtü ve o sayının
küme sağlamken de ödendiğini not düşerek bitti. Bu ders o notu açar. Ele alacağı ikinci konu ise
bir ifade yanlışıdır: dağıtık depolardan söz edilirken bir deponun "CP" ya da "AP" olduğu, sanki
bu bir ürün özelliğiymiş gibi söylenir.

CAP teoreminin üç harfi ve PACELC'in iki dalı Sistem Tasarımına Giriş kursunda tanımlandı; aynı
bölünme senaryosu iki politikayla koşturuldu, bayat yanıt ve reddedilen istek sayıldı, bölünme
dışındaki gecikme ödünleşimi ölçüldü ve teoremin yaygın yanlış okunuşları ayırt edildi. Tanımlar
ve o tartışma burada tekrarlanmaz. Buradaki iş, aynı ölçüm düzenini bir **depo ayarına**
uygulayıp etiketin nereye ait olduğunu saymaktır.

#### Etiket Ürünün Değil Ayarın Özelliğidir

Önceki üç ders bir şeyi tekrar tekrar gösterdi: yazma ilgisi, okuma ilgisi ve okuma yönü istek
başına verilebilen ayarlardır. Bir depo bu ayarların hepsini destekliyorsa, o depoya tek bir sınıf
adı vermek bir kategori hatasıdır. Sınıf, deponun değil, koşan **isteğin** özelliğidir.

Bunun sınanması doğrudandır: aynı küme, aynı bölünme, aynı kod, yalnız ayar değişir. Sınıf adı
değişiyorsa etiket ayara aittir.

#### Düzenek

**NS23 — bölünme 16 tur sürer ve kümeyi ikiye ayırır: şube tarafında iki üye, merkez tarafında üç
üye kalır.** Gerekçe: şube merkeze tek bir bağla bağlıdır ve o bağ koptuğunda şubedeki iki üye
yalnız kalır. **NS24 — çoğunlukla teması kesilen birincil kendini bir zaman aşımından sonra
indirir; bu süre 3 ya da 12 turdur.** Gerekçe: bir birincilin çoğunluk temasını yitirdikten sonra
ne kadar yazma kabul etmeyi sürdüreceği bir ayardır. İkinci varsayımın etkisi doğrusaldır ve
tabloda iki değerle gösterilir.

```js
// cap/model.mjs — bolunme MODELIDIR: tur soyut bir adimdir, uye uzakligi ve bolunme penceresi
// tur cinsinden parametrelerdir, gercek ag kurulmaz. Bes uyeli tek bir cogaltma kumesi ikiye
// ayrilir: sube tarafinda u0 (o anki birincil) ve u1, merkez tarafinda u2, u3, u4. Sube
// istemcisi merkez tarafina hic ulasamaz. AYNI KUME, AYNI KOD; degisen tek sey ayardir.
export const GECIKME = [0, 3, 5, 6, 8], UYE = 5, COGUNLUK = 3;
export const TUR = 40, BOL = [11, 26], ALGILAMA = 2, SECIM = 2;
const SIRALI = GECIKME.slice(1).slice().sort((a, b) => a - b);
export const onaySuresi = (w) => (w <= 1 ? 0 : 2 * SIRALI[w - 2]);

export function kosum({ w, okuma, indirme = 3, bolunme = true }) {
  const b0 = bolunme ? BOL[0] : TUR + 1, b1 = bolunme ? BOL[1] : TUR + 1;
  const iner = b0 + indirme;                  // cogunlukla temasi kesilen birincil kendini indirir
  const yeni = b0 + ALGILAMA + SECIM;         // merkez tarafi kendi birincilini secer
  const onay = onaySuresi(w);
  const yaziTuru = [0];                       // sira -> yazildigi tur (sube tarafi)
  let sira = 0, subeKabul = 0, subeRed = 0, merkezKabul = 0, gecici = [];
  let yanit = 0, bayat = 0, yanitsiz = 0, eskilik = 0, bekleme = 0;

  for (let t = 1; t <= TUR; t += 1) {
    const bolundu = t >= b0 && t <= b1;
    // sube yazmasi: her turda bir odunc kaydi, her zaman kendi tarafindaki birincile gider
    const birincilVar = bolundu === false || t < iner;
    const wSaglanir = bolundu === false || w <= 1;   // azinlik tarafinda cogunluk toplanamaz
    if (birincilVar && wSaglanir) {
      sira += 1; yaziTuru[sira] = t; subeKabul += 1; bekleme += onay;
      if (bolundu) gecici.push(sira);          // azinlikta kabul edildi: birlesmede geri alinir
    } else subeRed += 1;
    if (bolundu && t >= yeni) merkezKabul += 1; // merkez tarafi kendi birincilini secip yazmayi surdurur

    // sube okumasi: yerel ilgi kendi tarafindaki ikincilden yanitlanir; cogunluk ilgisi
    // azinlik tarafinda dogrulanamaz, bu yuzden yanit verilmez
    if (okuma === "cogunluk" && bolundu) { yanitsiz += 1; continue; }
    const gorulen = okuma === "cogunluk"
      ? yaziTuru.findLastIndex((x) => x + onaySuresi(COGUNLUK) <= t)
      : yaziTuru.findLastIndex((x) => x + GECIKME[1] <= t);
    yanit += 1;
    if (gorulen < sira) {
      bayat += 1;
      if (gorulen >= 1) eskilik = Math.max(eskilik, t - yaziTuru[gorulen]);  // henuz kayit yoksa sayilmaz
    }
  }
  return { subeKabul, subeRed, geriAlinan: gecici.length, merkezKabul,
    yanit, bayat, yanitsiz, eskilik, bekleme, onay };
}
```

```js
// cap/olc.mjs — ayni kume, ayni bolunme, dort ayar; sonra ayni dort ayar bolunme YOKKEN
import { kosum, onaySuresi, GECIKME, BOL, TUR, COGUNLUK } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const AYAR = [
  ["w=cogunluk · cogunluk", { w: COGUNLUK, okuma: "cogunluk" }],
  ["w=1 · yerel (indirme 3)", { w: 1, okuma: "yerel", indirme: 3 }],
  ["w=1 · yerel (indirme 12)", { w: 1, okuma: "yerel", indirme: 12 }],
  ["w=cogunluk · yerel", { w: COGUNLUK, okuma: "yerel" }],
];
const pencere = BOL[1] - BOL[0] + 1;
console.log(`${TUR} tur, uye uzakligi ${GECIKME.join("/")}. Bag ${BOL[0]}-${BOL[1]}. turlarda kesik`);
console.log(`(${pencere} tur): sube tarafinda u0 ve u1, merkez tarafinda u2, u3, u4.`);
console.log("Her turda bir odunc yazmasi ve bir katalog okumasi sube tarafina gelir.\n");
console.log("ayar                      | sube yazma kabul | red | geri alinan | merkez yazma | okuma yanit | bayat | yanitsiz");
console.log("--------------------------|------------------|-----|-------------|--------------|-------------|-------|---------");
for (const [ad, p] of AYAR) {
  const r = kosum(p);
  console.log(`${ad.padEnd(25)} | ${s(r.subeKabul, 16)} | ${s(r.subeRed, 3)} | ${s(r.geriAlinan, 11)} | ` +
    `${s(r.merkezKabul, 12)} | ${s(r.yanit, 11)} | ${s(r.bayat, 5)} | ${s(r.yanitsiz, 8)}`);
}

console.log("\nayni dort ayar, bolunme HIC olmadan:");
console.log("ayar                      | onay turu | toplam bekleme | eskilik penceresi | bayat okuma | yanitsiz");
console.log("--------------------------|-----------|----------------|-------------------|-------------|---------");
for (const [ad, p] of AYAR) {
  const r = kosum({ ...p, bolunme: false });
  console.log(`${ad.padEnd(25)} | ${s(r.onay, 9)} | ${s(r.bekleme, 14)} | ${s(r.eskilik + " tur", 17)} | ` +
    `${s(`${r.bayat}/${r.yanit}`, 11)} | ${s(r.yanitsiz, 8)}`);
}

console.log("\nkosumdan bagimsiz: azinlik tarafinda kalan uye sayisi 2, cogunluk esigi " +
  `${COGUNLUK};`);
console.log("2 < 3 oldugu icin o taraf hicbir ayarda cogunluk toplayamaz — bu bir ayar sonucu degil,");
console.log(`kume boyunun sonucudur. Cogunluk onay turu ${onaySuresi(COGUNLUK)}, yerel onay turu ${onaySuresi(1)}.`);
```

```
40 tur, uye uzakligi 0/3/5/6/8. Bag 11-26. turlarda kesik
(16 tur): sube tarafinda u0 ve u1, merkez tarafinda u2, u3, u4.
Her turda bir odunc yazmasi ve bir katalog okumasi sube tarafina gelir.

ayar                      | sube yazma kabul | red | geri alinan | merkez yazma | okuma yanit | bayat | yanitsiz
--------------------------|------------------|-----|-------------|--------------|-------------|-------|---------
w=cogunluk · cogunluk     |               24 |  16 |           0 |           12 |          24 |    24 |       16
w=1 · yerel (indirme 3)   |               27 |  13 |           3 |           12 |          40 |    29 |        0
w=1 · yerel (indirme 12)  |               36 |   4 |          12 |           12 |          40 |    38 |        0
w=cogunluk · yerel        |               24 |  16 |           0 |           12 |          40 |    26 |        0

ayni dort ayar, bolunme HIC olmadan:
ayar                      | onay turu | toplam bekleme | eskilik penceresi | bayat okuma | yanitsiz
--------------------------|-----------|----------------|-------------------|-------------|---------
w=cogunluk · cogunluk     |        10 |            400 |            10 tur |       40/40 |        0
w=1 · yerel (indirme 3)   |         0 |              0 |             3 tur |       40/40 |        0
w=1 · yerel (indirme 12)  |         0 |              0 |             3 tur |       40/40 |        0
w=cogunluk · yerel        |        10 |            400 |             3 tur |       40/40 |        0

kosumdan bagimsiz: azinlik tarafinda kalan uye sayisi 2, cogunluk esigi 3;
2 < 3 oldugu icin o taraf hicbir ayarda cogunluk toplayamaz — bu bir ayar sonucu degil,
kume boyunun sonucudur. Cogunluk onay turu 10, yerel onay turu 0.
```

#### Aynı Küme, Dört Ayar, Dört Davranış

Birinci tablo bu dersin savını tek başına taşıyor. Dört satır aynı kümeyi, aynı 16 turluk
bölünmeyi ve aynı kodu kullanıyor; ayrılan tek şey ayardır ve dört farklı davranış çıkıyor.

Birinci satır tutarlılığı seçiyor: şube tarafındaki 16 yazma reddedildi, 16 okuma yanıtsız kaldı
ve geri alınan yazma 0. İkinci satır erişilebilirliği seçiyor: hiçbir okuma yanıtsız kalmadı,
şube 3 yazma daha kabul etti — ve o 3 yazma birleşmede geri alındı.

İki satır arasındaki ilişki tam bir eşitliktir ve tesadüf değildir: **azınlık tarafında kazanılan
her kabul, geri alınan bir yazmadır.** 27 − 24 = 3 ve üçüncü satırda 36 − 24 = 12. İndirme zaman
aşımını 3 turdan 12 tura çıkarmak 9 yazma daha kabul ettirdi ve tam olarak 9 yazma daha geri
aldırdı. Erişilebilirlik burada bir ödünleşim değil, bir **takas**tır; kur birebirdir ve ayarın
kendisi kuru belirlemez, yalnız işlem hacmini belirler.

Dördüncü satır etiket tartışmasını bitiriyor. `w=çoğunluk` ile `yerel` okuma bileşiminde yazma
yolu birinci satırla aynı davranıyor — 16 red, 0 geri alınan — okuma yolu ise ikinci satırla aynı
davranıyor: 40 yanıt, 0 yanıtsız. Tek bir depo, tek bir küme, tek bir ayar; yazmada tutarlılık,
okumada hizmet erişilebilirliği. Bu satıra tek harfli bir sınıf adı verilemez. Sınıf, işlemin
gittiği yola aittir.

`merkez yazma` sütunu dört satırda da 12'dir ve bir yanlış anlamayı daha kapatıyor. Bölünme
sırasında **depo durmadı**; merkez tarafı çoğunluğu topladı, kendi birincilini seçti ve yazmaya
devam etti. Reddedilen 16 yazma, deponun değil, azınlık tarafındaki istemcinin gördüğü şeydir.
Bir sınıf adı verilirken kimin gözünden bakıldığı da söylenmelidir.

#### Bölünme Yokken de Bir Seçim Yapılıyor

İkinci tablo bağın hiç kesilmediği koşumu veriyor ve dört satır orada da ayrışıyor.

`w=çoğunluk` içeren iki ayar 40 turda toplam 400 tur bekledi; `w=1` içeren iki ayar 0 tur. Bu 400
tur bölünmeyle ilgili değildir — bağ sapasağlamken, her yazmada, çoğunluğun onayının dönmesi için
harcandı. Aynı biçimde eskilik penceresi çoğunluk okumasında 10, yerel okumada 3 turdur ve bu fark
da bölünme olmadan ölçüldü.

Dördüncü satır burada da karışık kalıyor: yazma yolunda 400 tur bekleme, okuma yolunda 3 turluk
eskilik penceresi. Yani ayar, bölünme dalında olduğu gibi bölünme dışı dalda da tek bir harfe
sığmıyor.

| Ayar | Bölünmede (P dalı) | Bölünme dışında (E dalı) | Ölçülen |
|---|---|---|---|
| `w=çoğunluk` · çoğunluk | tutarlılık | tutarlılık | 16 red, 16 yanıtsız; 400 bekleme turu, 10 turluk eskilik |
| `w=1` · yerel, indirme 3 | erişilebilirlik | gecikme | 0 yanıtsız, 3 geri alınan; 0 bekleme, 3 turluk eskilik |
| `w=1` · yerel, indirme 12 | erişilebilirlik | gecikme | 0 yanıtsız, 12 geri alınan; 0 bekleme, 3 turluk eskilik |
| `w=çoğunluk` · yerel | yazmada tutarlılık, okumada erişilebilirlik | yazmada tutarlılık, okumada gecikme | 16 red ama 0 yanıtsız; 400 bekleme, 3 turluk eskilik |

Kütüphane işletiminde bu tablo doğrudan bir yapılandırmaya çevrilir. Ödünç verme yazması birinci
satırın ayarını alır: bağ koptuğunda şube ödünç veremez, ama hiçbir ödünç kaydı sonradan yok
sayılmaz. Katalog araması dördüncü satırın okuma tarafını alır: bağ kopsa da arama yanıt verir ve
üç turluk bir eskilik kabul edilir. İki karar aynı kümede, aynı derlem üzerinde, ayrı isteklerle
verilir.

#### Yapısal Olan ve Ayara Bağlı Olan

Her şey ayar değildir ve bu ayrımı görmemek ikinci bir ifade yanlışıdır. Son bloktaki satır
koşumdan bağımsızdır: azınlık tarafında iki üye kalıyor, çoğunluk eşiği üçtür ve 2 < 3 olduğu için
o taraf **hiçbir ayarda** çoğunluk toplayamaz. Bu bir politika sonucu değil, küme boyunun ve
bölünmenin nereden geçtiğinin sonucudur. Ayarla değiştirilemeyecek olan budur; ayarla
değiştirilebilecek olan, o tarafın ne kadar süre yazma kabul etmeyi sürdüreceğidir.

Aynı ayrım birinci dersin sonucuyla örtüşüyor: orada iki üyesi düşen üç üyeli kümede seçim hiç
başlamamıştı ve kalan üyenin elindeki verinin güncelliği bunu değiştirmemişti. Yapısal sınırlar
ayar tablosunun üstündedir; ayar tablosu ancak o sınırın izin verdiği alanda karar üretir.

#### Özet

- Aynı küme, aynı 16 turluk bölünme ve aynı kod dört ayarda dört farklı davranış üretti; sınıf adı
  deponun değil, ayarın ve işlemin gittiği yolun özelliğidir.
- Azınlık tarafında kazanılan her kabul, geri alınan bir yazmadır: indirme zaman aşımı 3 turdan 12
  tura çıkınca kabul 27'den 36'ya, geri alınan 3'ten 12'ye çıktı — fark her iki sütunda da 9.
- `w=çoğunluk` ile `yerel` okuma bileşimi yazma yolunda tutarlılığı (16 red, 0 geri alınan), okuma
  yolunda hizmet erişilebilirliğini (40 yanıt, 0 yanıtsız) seçti; bu satıra tek harfli bir sınıf
  verilemez.
- Bölünme sırasında merkez tarafı dört ayarda da 12 yazma kabul etti: depo durmadı, duran şey
  azınlık tarafındaki istemcinin gördüğü hizmettir.
- Bölünme hiç olmadığında da bir seçim ödendi: `w=çoğunluk` içeren ayarlar 40 turda 400 tur
  bekledi, çoğunluk okumasının eskilik penceresi 10 tur, yerel okumanınki 3 tur çıktı.
- Ayarla değiştirilemeyen bir sınır var: azınlık tarafında iki üye kalırken çoğunluk eşiği üçtür
  ve 2 < 3 olduğu için o taraf hiçbir ayarda çoğunluk toplayamaz.

#### Sonraki Adım

Bu konu boyunca kümenin verdiği her karar bir ayarla ölçüldü: kaç üyenin onay vereceği, okumanın
nereye gideceği, bir birincilin çoğunluk temasını yitirdikten sonra ne kadar yazma kabul edeceği.
Ölçülen her şeyin ortak bir varsayımı vardı ve hiç dile getirilmedi: isteği gönderenin bunu yapma
yetkisi olduğu. Modelde kümeye ulaşan her yazma bir ödünç kaydı yazdı, her okuma bir yanıt aldı;
isteğin kimden geldiği hiç sorulmadı. Gerçek bir kurulumda bu soru ilk sorulandır ve yanıtı yine
bir yapılandırmadır — üstelik varsayılan değerleri en tehlikeli olanıdır. Sonraki ders deponun
kimlik doğrulama, rol kapsamı ve şifreleme ayarlarını ele alır: yetkinin kapalı olduğu bir kurulumun
neye açık olduğu, bir rolün kapsamının derlem düzeyine indirilmesinin ne getirdiği, aktarım ve
durağan şifrelemenin hangi maliyeti eklediği.

### Güvenlik Yapılandırması

Source: https://academia.sh/tr/kurslar/nosql/guvenlik-yapilandirmasi

Yetkinin bir ayar olarak ölçülmesi: kimlik doğrulaması kapalı bir kurulumda erişilebilen derlem ve kayıt sayısının sayılması, aynı beş uygulama kimliğinin küme geneli, veritabanı düzeyi ve derlem düzeyi rollerle koşturulup okunabilir, yazılabilir ve kişisel veri taşıyan kayıt toplamlarının karşılaştırılması, en az yetkinin küçültemediği artığın adlandırılması ve aktarım ile durağan şifrelemenin bedelinin ek bayt ve ek işlem adımı olarak çıkarılması.

Önceki ders aynı kümeyi aynı bölünmede dört ayarla koşturdu ve sınıf adının deponun değil
isteğin gittiği yolun özelliği olduğunu saydı. O koşumlarda kümeye ulaşan her yazma ya kabul
edildi ya reddedildi, her okuma ya yanıtlandı ya yanıtsız kaldı. İsteğin kimden geldiği bir kez
bile sorulmadı.

Bu ders o soruyu sorar ve yanıtı yine bir yapılandırmadır. Kimlik doğrulamanın mekaniği burada
anlatılmaz; buranın konusu deponun kendi ayarlarıdır. Ölçülecek üç şey var: kapalı bir yetki
ayarının neye açık olduğu, rol kapsamını daraltmanın bunu ne kadar küçülttüğü ve şifrelemenin
hangi kalemde ne kadar bedel eklediği.

#### Açık Yüzey Sayılabilir Bir Niceliktir

Güvenlik yapılandırması anlatılırken kullanılan sıfatlar — sıkı, gevşek, yeterli — karar
taşımaz. Ölçülebilir karşılığı **açık yüzeydir**: bir yapılandırmada tanımlı kimliklerin
okuyabildiği kayıtların toplamı. Yanında iki nicelik daha durur: yazılabilir kayıt ve kişisel
veri taşıyan okunabilir kayıt toplamı. Girdileri derlem boyları, deponun tanıdığı kimlikler ve
her kimliğe atanan rolün kapsamıdır; ilk ikisi kurulumdan gelir, üçüncüsü karardır.

#### Düzenek

**NS31 — kurulumda yedi derlem, 154.540 kayıt ve beş uygulama kimliği vardır; her kimliğin işi
belli birkaç derleme dokunur.** Gerekçe: kurs boyunca kullanılan katalog, üye, ödünç ve ceza
derlemlerine personel ve denetim derlemleri eklenir; bir arama hizmeti, bir ödünç ucu, bir ceza
işi, bir rapor işi ve bir yedek işi tipik bir ayrışmadır. **NS32 — aktarım şifrelemesinde bir
çerçeve en çok 16.384 bayt taşır ve 29 bayt ek alır; bağlantı kurulumu bağlantı başına 5.200
bayttır.** Gerekçe: ek bir başlık, bir sıra sayacı ve bir kimlik doğrulama etiketidir; sonucu
belirleyen sayının kendisi değil, **yanıt boyundan bağımsız olmasıdır**. **NS33 — durağan
şifreleme 4.096 baytlık bloklar üzerinde çalışır, blok başına 28 bayt üstveri tutar ve her blok
okunuşu bir ek çözme adımıdır.**

Düzenek bir **modeldir**: gerçek kurulum, ağ ya da kimlik sağlayıcı yoktur. Ölçüm sınıfında sayı
yoktur; NS31–NS33 varsayım, çıktıdaki her sayı onlardan türeyen hesaptır.

```js
// guvenlik/model.mjs — depo YAPILANDIRMASI modelidir: gercek kurulum, ag ya da kimlik
// saglayici yoktur. Rol kapsami ve sifreleme bedeli parametreden hesaplanir.
export const DERLEM = [
  // veritabani, derlem, kayit, kayit basina bayt, kisisel veri tasir mi
  ["kutuphane", "kitap", 20000, 368, false],
  ["kutuphane", "uye", 12000, 214, true],
  ["kutuphane", "odunc", 20000, 96, true],
  ["kutuphane", "ceza", 4800, 72, true],
  ["kutuphane", "ozet", 1560, 88, false],
  ["isletim", "personel", 180, 260, true],
  ["isletim", "denetim", 96000, 128, false],
];
export const KIMLIK = ["katalog-arama", "odunc-uc", "ceza-isleme", "rapor", "yedek"];
const izin = (vt, d, ...e) => ({ vt, d, e });
const HEPSI = [izin("*", "*", "oku", "yaz")];
const her = (r) => Object.fromEntries(KIMLIK.map((k) => [k, r]));
const VT = [izin("kutuphane", "*", "oku", "yaz")];
const AZ = {
  "katalog-arama": [izin("kutuphane", "kitap", "oku")],
  "odunc-uc": [izin("kutuphane", "kitap", "oku"), izin("kutuphane", "uye", "oku"),
    izin("kutuphane", "odunc", "oku", "yaz")],
  "ceza-isleme": [izin("kutuphane", "odunc", "oku"), izin("kutuphane", "ceza", "oku", "yaz")],
  rapor: [izin("kutuphane", "odunc", "oku"), izin("kutuphane", "ceza", "oku")],
  yedek: [izin("*", "*", "oku")],
};
export const YAPILANDIRMA = [
  { ad: "Y0 kimlik dogrulama kapali", kimlik: "hayir", rol: her(HEPSI) },
  { ad: "Y1 kume geneli tek rol", kimlik: "evet", rol: her(HEPSI) },
  { ad: "Y2 veritabani duzeyi rol", kimlik: "evet", rol: { ...her(VT), yedek: AZ.yedek } },
  { ad: "Y3 derlem duzeyi (en az yetki)", kimlik: "evet", rol: AZ },
  { ad: "Y4 derlem duzeyi + ozet derlemi", kimlik: "evet",
    rol: { ...AZ, rapor: [izin("kutuphane", "ozet", "oku")] } },
];

const uyar = (i, d) => (i.vt === "*" || i.vt === d[0]) && (i.d === "*" || i.d === d[1]);

export function yuzey(y) {
  const satir = KIMLIK.map((k) => {
    let oku = 0, yaz = 0, kisisel = 0;
    for (const d of DERLEM) {
      const ilgili = y.rol[k].filter((i) => uyar(i, d));
      if (ilgili.some((i) => i.e.includes("oku"))) { oku += d[2]; if (d[4]) kisisel += d[2]; }
      if (ilgili.some((i) => i.e.includes("yaz"))) yaz += d[2];
    }
    return { k, oku, yaz, kisisel };
  });
  const top = (a) => satir.reduce((s, r) => s + r[a], 0);
  const uyg = satir.filter((r) => r.k !== "yedek").map((r) => r.oku);
  return { satir, oku: top("oku"), yaz: top("yaz"), kisisel: top("kisisel"),
    enGenisUyg: Math.max(...uyg) };
}

export const CERCEVE = 16384, CERCEVE_EK = 29, ELSIKISMA = 5200, BLOK = 4096, BLOK_EK = 28;
export const cerceve = (b) => Math.ceil(b / CERCEVE);
export const blok = (b) => Math.ceil(b / BLOK);
export const bayt = (d) => d[2] * d[3];
export const TOPLAM_BAYT = DERLEM.reduce((s, d) => s + bayt(d), 0);
export const TOPLAM_BLOK = DERLEM.reduce((s, d) => s + blok(bayt(d)), 0);
const OZET = bayt(DERLEM.find((d) => d[1] === "ozet"));
export const ISYUKU = [
  // ad, istek sayisi, istek basina tasinan bayt, istek basina dokunulan blok
  ["katalog-arama noktasal", 180000, 1472, 1],
  ["odunc-uc noktasal", 20000, 640, 1],
  ["ceza-isleme noktasal", 4800, 208, 1],
  ["rapor ozet taramasi", 12, OZET, blok(OZET)],
  ["gecelik yedek", 1, TOPLAM_BAYT, TOPLAM_BLOK],
];
```

```js
// guvenlik/olc.mjs — ayni derlemler, ayni bes kimlik, bes yapilandirma; sonra ayni is yukunun
// aktarim ve duragan sifreleme bedeli
import { DERLEM, KIMLIK, YAPILANDIRMA, ISYUKU, yuzey, cerceve, blok, bayt,
  CERCEVE_EK, ELSIKISMA, BLOK_EK, TOPLAM_BAYT, TOPLAM_BLOK } from "./model.mjs";

const n = (x) => String(x).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
const s = (x, w) => n(x).padStart(w);
const y = (x) => (x * 100).toFixed(2).replace(".", ",");
const KAYIT = DERLEM.reduce((a, d) => a + d[2], 0);

console.log(`${DERLEM.length} derlem, ${n(KAYIT)} kayit, ${n(TOPLAM_BAYT)} bayt, ` +
  `${KIMLIK.length} uygulama kimligi; kayit sutunlari bes kimlik uzerinden toplanmistir.\n`);
console.log("yapilandirma                    | kimlik | okunabilir | en genis uyg | yazilabilir | kisisel");
console.log("--------------------------------|--------|------------|--------------|-------------|--------");
for (const yp of YAPILANDIRMA) {
  const r = yuzey(yp);
  console.log(`${yp.ad.padEnd(31)} | ${yp.kimlik.padEnd(6)} | ${s(r.oku, 10)} | ` +
    `${s(r.enGenisUyg, 12)} | ${s(r.yaz, 11)} | ${s(r.kisisel, 7)}`);
}
const yd = YAPILANDIRMA.map((yp) => yuzey(yp).satir.find((r) => r.k === "yedek"));
console.log(`\nYedek kimliginin okudugu kayit bes yapilandirmada: ` +
  `${yd.map((r) => n(r.oku)).join(", ")} (kisisel ${n(yd[4].kisisel)}).`);

const istek = ISYUKU.reduce((a, w) => a + w[1], 0);
const yuk = ISYUKU.reduce((a, w) => a + w[1] * w[2], 0);
const cerEk = ISYUKU.reduce((a, w) => a + w[1] * cerceve(w[2]) * CERCEVE_EK, 0);
console.log(`\nAyni gunun is yuku: ${n(istek)} istek, ${n(yuk)} bayt tasindi.`);
console.log("is yuku                |   istek | istek bayti | cerceve eki | oran   | cozme adimi");
console.log("-----------------------|---------|-------------|-------------|--------|------------");
for (const [ad, sayi, b, bl] of ISYUKU) {
  const ek = cerceve(b) * CERCEVE_EK;
  console.log(`${ad.padEnd(22)} | ${s(sayi, 7)} | ${s(b, 11)} | ${s(sayi * ek, 11)} | ` +
    `${("%" + y(ek / b)).padStart(6)} | ${s(sayi * bl, 11)}`);
}
console.log(`${"toplam".padEnd(22)} | ${s(istek, 7)} | ${s(yuk, 11)} | ${s(cerEk, 11)} | ` +
  `${("%" + y(cerEk / yuk)).padStart(6)} | ${s(ISYUKU.reduce((a, w) => a + w[1] * w[3], 0), 11)}`);

const HAVUZ = 64, kitap = DERLEM.find((d) => d[1] === "kitap");
console.log(`\nBaglanti kurulumu ${n(ELSIKISMA)} bayt: ${HAVUZ} baglantilik havuzla ` +
  `${n(HAVUZ * ELSIKISMA)} bayt (%${y(HAVUZ * ELSIKISMA / yuk)}), istek basina baglantiyla ` +
  `${n(istek * ELSIKISMA)} bayt (%${y(istek * ELSIKISMA / yuk)}).`);
console.log(`Duragan sifreleme ${n(TOPLAM_BLOK)} blok x ${BLOK_EK} bayt = ` +
  `${n(TOPLAM_BLOK * BLOK_EK)} bayt ek (%${y(TOPLAM_BLOK * BLOK_EK / TOPLAM_BAYT)}); tek noktasal ` +
  `okuma 1 blok cozer, kitap derleminin taranmasi ${n(blok(bayt(kitap)))}.`);
console.log(`\nkosumdan bagimsiz: cerceve eki ${CERCEVE_EK} bayt sabittir, orani yanit boyuna ` +
  `bolunur — 208 baytlik yanitta %${y(CERCEVE_EK / 208)}, dolu cercevede ` +
  `%${y(CERCEVE_EK / 16384)}, ${(16384 / 208).toFixed(0)} kat.`);
```

```
7 derlem, 154.540 kayit, 24.665.680 bayt, 5 uygulama kimligi; kayit sutunlari bes kimlik uzerinden toplanmistir.

yapilandirma                    | kimlik | okunabilir | en genis uyg | yazilabilir | kisisel
--------------------------------|--------|------------|--------------|-------------|--------
Y0 kimlik dogrulama kapali      | hayir  |    772.700 |      154.540 |     772.700 | 184.900
Y1 kume geneli tek rol          | evet   |    772.700 |      154.540 |     772.700 | 184.900
Y2 veritabani duzeyi rol        | evet   |    387.980 |       58.360 |     233.440 | 184.180
Y3 derlem duzeyi (en az yetki)  | evet   |    276.140 |       52.000 |      24.800 | 118.580
Y4 derlem duzeyi + ozet derlemi | evet   |    252.900 |       52.000 |      24.800 |  93.780

Yedek kimliginin okudugu kayit bes yapilandirmada: 154.540, 154.540, 154.540, 154.540, 154.540 (kisisel 36.980).

Ayni gunun is yuku: 204.813 istek, 305.071.440 bayt tasindi.
is yuku                |   istek | istek bayti | cerceve eki | oran   | cozme adimi
-----------------------|---------|-------------|-------------|--------|------------
katalog-arama noktasal | 180.000 |       1.472 |   5.220.000 |  %1,97 |     180.000
odunc-uc noktasal      |  20.000 |         640 |     580.000 |  %4,53 |      20.000
ceza-isleme noktasal   |   4.800 |         208 |     139.200 | %13,94 |       4.800
rapor ozet taramasi    |      12 |     137.280 |       3.132 |  %0,19 |         408
gecelik yedek          |       1 |  24.665.680 |      43.674 |  %0,18 |       6.024
toplam                 | 204.813 | 305.071.440 |   5.986.006 |  %1,96 |     211.232

Baglanti kurulumu 5.200 bayt: 64 baglantilik havuzla 332.800 bayt (%0,11), istek basina baglantiyla 1.065.027.600 bayt (%349,11).
Duragan sifreleme 6.024 blok x 28 bayt = 168.672 bayt ek (%0,68); tek noktasal okuma 1 blok cozer, kitap derleminin taranmasi 1.797.

kosumdan bagimsiz: cerceve eki 29 bayt sabittir, orani yanit boyuna bolunur — 208 baytlik yanitta %13,94, dolu cercevede %0,18, 79 kat.
```

#### Kimlik Doğrulamak Yüzeyi Küçültmüyor, Kapsam Küçültüyor

Birinci tablonun ilk iki satırı aynı: 772.700 okunabilir, 772.700 yazılabilir, 184.900 kişisel
kayıt. Kimlik doğrulamasını açmak açık yüzeyi bir kayıt bile küçültmedi.

Farkı tablo göstermiyor, çünkü tablo kimlik **sayısını** sabit tutuyor. Y0'da o sayı beş
değildir: depoya ulaşabilen her istemci bu beş satırdan biri gibi davranır, silme dahil. Y0'ın
açık yüzeyi 772.700 değil, erişebilen istemci sayısı çarpı 154.540'tır. Kimlik doğrulaması
yüzeyi küçülten değil, onu **sonlu bir kimlik kümesine bağlayan** ayardır; küçültme işini kapsam
yapar.

Kapsamın üç düzeyi üç sayı veriyor: veritabanı düzeyi okunabilir kaydı 387.980'e, derlem düzeyi
276.140'a indirdi. Asıl fark yazmadadır — 772.700 → 233.440 → 24.800, yani 31,2 kat — çünkü beş
kimlikten yalnız ikisinin yazması gerekir.

`en genis uyg` sütunu tek bir kimlik ele geçtiğinde görüneni veriyor: 154.540 → 58.360 →
52.000. Ama o sütun yedek kimliğini dışarıda bırakıyor; altındaki satıra göre yedeğin okuduğu
kayıt beş yapılandırmanın **hepsinde** 154.540 kaldı, 36.980'i kişisel. En az yetki en kötü
durumu değiştirmedi, onu tek bir kimliğe hapsetti; sınırı, işi gereği her şeyi okuyan kimliktir.

Y4 son adımı gösteriyor: rapor işine kişisel alan taşımayan bir özet derlemi verildiğinde
okunabilir kayıt 252.900'e, kişisel kayıt 93.780'e indi. Yetki daraltmanın bittiği yerde **veri
daraltmak** başlar.

#### Şifrelemenin Bedeli Sabittir, Oranı Değil

İkinci tablo aktarım şifrelemesini aynı günün iş yükünde hesaplıyor: toplam oran %1,96, yani
305.071.440 bayta karşılık 5.986.006 bayt. Yayılma büyüktür — 208 baytlık ceza yanıtında %13,94,
24.665.680 baytlık yedek aktarımında %0,18. Çerçeve eki 29 bayt olarak sabittir; oranı belirleyen
şifreleme değil **yanıtın küçüklüğüdür** ve son satır bunu koşumdan bağımsız veriyor: 79 kat.

Asıl kalem tablonun altındadır. Bağlantı kurulumu bağlantı başına 5.200 bayttır: 64 bağlantılık
havuzla günde 332.800 bayt, yükün %0,11'i. Aynı iş yükü her istek için yeni bağlantı açtığında
1.065.027.600 bayta, taşınan verinin %349'una çıkıyor. Aktarım şifrelemesini pahalı gösteren
çoğu kez bağlantı kurma biçimidir.

Durağan şifreleme ayrı bir kalemdir: 6.024 blok × 28 bayt = 168.672 bayt, verinin %0,68'i ve veri
boyundan bağımsız. Yerdeki bedel küçüktür; asıl bedel işlem adımıdır ve kayıt sayısıyla değil
**dokunulan blok sayısıyla** ölçülür: noktasal okuma 1, kitap derlemi taraması 1.797. Günün
204.813 isteği 211.232 çözme adımı üretiyor, 6.024'ü tek bir gecelik yedeğe ait.

#### Özet

- Açık yüzey sayılabilir bir niceliktir: tanımlı kimliklerin okuyabildiği kayıt toplamı, yanında
  yazılabilir ve kişisel kayıt toplamı.
- Kimlik doğrulamasını açmak açık yüzeyi hiç küçültmedi (772.700 → 772.700); o ayar yüzeyi
  **sonlu bir kimlik kümesine bağlar**. Kapalıyken açık yüzey erişebilen istemci sayısı çarpı
  154.540'tır, yazma ve silme dahil.
- Kapsamı küme geneli rolden derlem düzeyine indirmek okunabilir kaydı 276.140'a, yazılabilir
  kaydı 24.800'e (31,2 kat) indirdi.
- En az yetki en kötü durumu değiştirmedi: yedek kimliği beş yapılandırmanın hepsinde 154.540
  kaydın tamamını okudu. Yetki daraltmanın bittiği yerde veri daraltmak başlar — rapor işine özet
  derlemi verildiğinde kişisel kayıt 118.580'den 93.780'e indi.
- Aktarım şifrelemesinin çerçeve eki 29 bayt olarak sabittir: oranı 208 baytlık yanıtta %13,94,
  dolu çerçevede %0,18. Havuzsuz bağlantı kurulumu taşınan verinin %349'unu ekliyor.
- Durağan şifreleme yerde %0,68 tutar; işlem bedeli dokunulan blok sayısıdır — noktasal okumada
  1, kitap derlemi taramasında 1.797 ek çözme adımı.

#### Kurs Kapanışı

Bu kurs on dokuz derste tek bir soruyu sordu: aynı iş iki modelde ya da iki kararla
koşturulduğunda ölçülen fark nedir ve hangi güvenceden vazgeçilmiştir.

| Ders | Karşılaştırılan iki model/karar | Ölçülen fark | Vazgeçilen güvence |
|---|---|---|---|
| Anahtar–Değer Depoları | anahtar erişimi — dizin | ceza sorgusu 1 — 20.000 gidiş | alan üzerinde koşul |
| Belge Veritabanları | belge — ilişkisel | iç içe kayıt 1 — 4 gidiş | yazma anında denetim |
| Geniş Sütun Depoları | satır — aile yerleşimi | dolaşım özeti 1.867 — 46 sayfa | tek yerleşimin iki örüntüye yetmesi |
| Çizge Veritabanları | birleştirme — dolaşma | beşinci derecede 14.190.839 — 217.544 | maliyetin dereceden bağımsızlığı |
| Zaman Serisi Depoları | tam damga — fark kodlaması | 9.331.200 — 329.013 bayt | erişim birimi bölüt olur |
| Model Seçim Ölçütleri | tek örüntü — karışım | kazanan karışımla değişiyor | puanlamanın yazmayı sayması |
| Belge Veri Tipleri | açık — kısa ad — sabit şema | 7.357.008 — 6.082.574 — 3.139.138 bayt | alan adının depoda durması |
| Gömme ve Referans | gömülü — referanslı şema | şube sorgusu 6.149.233 — 781.096 bayt | kitap sayfasının tek gidişi |
| Sorgu İşleçleri | belge düzeyi — öge düzeyi bağlaç | 6.033 — 3.340 belge | koşulların aynı ögede karşılanması |
| Toplama Boru Hattı | süzme önce — gruplama önce | doruk bellek 2.418 — 40.276 | aşama sırasının serbestliği |
| Dizinleme | kesişim — öge düzeyi bileşik dizin | 6.033 — 3.340 okunan belge | bakım 10.840 — 173.994 giriş |
| Şema Doğrulama | yüzeysel — tam kural, `reddet` kipi | 2.500 — 3.200 yakalanan belge | sıradan yazmaların geçmesi |
| İşlemler | tek belge — çok belgeli işlem | 0/20 — 12/20 ihlal, 18 iptal | iptalsizlik, 604.903 bayt geri alma |
| Çoğaltma Kümeleri | üç–dört — beş üyeli küme | 12 yazmasız tur, geri alınan 9 — 6 | kabul edilmiş yazmanın kalıcılığı |
| Parçalama | `alinma_gunu` — `karma(alinma_gunu)` — `uye_id` | sıcak pay %100 — %59,6 — %27,0 | dengenin sıcak parçayı önlemesi |
| Okuma ve Yazma İlgileri | `w=1` — `w=çoğunluk` — `w=hepsi` | onay turu 0/10/16, kayıp yazma 3/0/0 | yazma erişilebilirliği |
| Tutarlılık Modelleri | yapışkan yönlendirme — oturum belirteci | nedensel kopma 24 → 12 → 0 | bayat okumanın 47/108 kalması |
| CAP ve PACELC | `w=çoğunluk`·çoğunluk — `w=1`·yerel | 16 red/16 yanıtsız — 0 yanıtsız | azınlıktaki kabulün kalıcılığı |
| Güvenlik Yapılandırması | küme geneli rol — en az yetki | yazılabilir 772.700 — 24.800 | tek kimliğin her işi görmesi |

Sağdaki iki sütun kursun kuralıdır: **aile değil erişim örüntüsü seçilir; her model bir
güvenceden vazgeçerek bir başarım satın alır.** Bir aileyi adıyla seçmek karar değildir — karar,
hangi işin kaç kat ucuzlayacağını ve hangi güvencenin düşeceğini yazmaktır. On dokuz satırın
hiçbirinde bedelsiz bir kazanç yok.

Bu on dokuz satırın söylenmemiş ortak bir varsayımı daha var. Anahtar–değer deposu değeri diske
yazdı, geniş sütun deposu bellekteki tabloyu dosyaya döktü, çoğaltma kümesi onayı yazma diske
indikten sonra verdi ve durağan şifreleme bu derste tam olarak o diski şifreledi. Verinin
**tamamının** bellekte tutulduğu ve dayanıklılığın kendisinin bir ayara dönüştüğü depolar hiç
ele alınmadı. Bir sonraki kurs — **Bellek İçi Depolar ve Önbellek Sistemleri** — oradan
başlıyor.

