---
title: 'Veri Katmanı Ölçekleme'
source: 'https://academia.sh/tr/kurslar/veri-olcekleme'
language: tr
updated: '2026-08-17T04:43:09+00:00'
license: 'CC BY-SA 4.0'
lessons: 18
outcomes:
    - 'Çoğaltma, parçalama ve federasyon kararlarını erişim örüntüsüne bağlar'
    - 'Önbellek katmanlarını yerleşim ve geçersizleştirme ile tasarlar'
    - 'Okuma ve yazma modellerini gerektiğinde ayırır'
---

# Veri Katmanı Ölçekleme

Tek depo varsayımının kaldırılması: veri deposu seçiminin erişim örüntüsüne bağlanması, çoğaltma, federasyon ve parçalama kararları, bölümleme stratejileri, okuma yolunu kısaltan denormalizasyon ile somutlaştırılmış görünümler, önbelleğin hangi katmanda hangi stratejiyle duracağı ve okuma ile yazma modellerinin gerektiğinde ayrılması; her karar giriş kursunda hesaplanan depo yükü ve saklanan veri satırlarına geri bağlanır.

## Veri Dağıtımı

Verinin nerede duracağına dair kararlar: ilişkisel ile ilişkisel olmayan seçiminin erişim örüntüsüne bağlanması, ana–kopya ve çok ana çoğaltma modelleri, işleve göre veritabanı bölme, anahtar seçimi ile yeniden dengeleme ve sıcak nokta sorunları, aralık, karma ve dizin tablosu bölümleme stratejileri, okuma yolunu kısaltan denormalizasyon, önceden hesaplanmış sonuçlar ve anahtar–değer, belge, geniş sütun ile çizge depolarının kullanım alanları.

### Veri Deposu Seçimi

Source: https://academia.sh/tr/kurslar/veri-olcekleme/veri-deposu-secimi

Depo türünün erişim örüntüsüne bağlanması: üç örüntünün istek ve kayıt hızlarının ayrılması, tek depo ile örüntüye göre ayrılmış düzenin dokunulan tablo ve işlenen kayıt cinsinden gerçek bir motorda ölçülmesi, karar ekseninin veri modelinden değil örüntü ile ölçekten çıkarılması ve ayrımın giriş kursundaki depo yükü ile saklanan veri satırlarına ödettiği bedelin hesaplanması.

Uygulama Katmanı ve Servis Etkileşimi kursu servisleri durumsuzlaştırdı, adreslerini bulmalarını
sağladı, işi kuyruğa aldı ve iş akışını koordine etti. Bütün o tasarım tek bir varsayımın
üzerinde durdu: veri **bir yerde** duruyordu ve o yerin ne olduğu hiç sorulmadı. Kanıtı
hesaptadır — K01'in tablosundaki `depoya ulasan istek/s` (138,89) ve `saklanan veri GB` (712,48)
satırları o kurs boyunca hiç oynamadı.

Bu kurs o satırları ele alır ve ilk soru deponun **türüdür**. İlişkisel modelin ne verdiği,
ilişkisel olmayan ailelerin neyi bıraktığı ve şema esnekliğinin gerçekte nereye taşındığı Veri
Modelleme ve İlişkisel Kuram kursunda kuruldu; burada veri modelleme öğretilmez. Buradaki eksen
başkadır: **erişim örüntüsü ve ölçek**. Aynı veri, farklı sorularla okunduğunda farklı depo
düzeni ister; bu ders o bağı ölçer.

#### Üç Erişim Örüntüsü

Bir depo kararı yalnız veriye bakarak savunulamaz; savunma sorunun biçimine dayanır. Gönderi
takip ve ücretlendirme servisinde üç örüntü vardır ve üçünün hızları K01'in hesabından gelir.

**P1 — takip numarasına göre tekil okuma.** Anahtar bellidir, yanıt küçüktür (V5: 480 bayt) ve
kenar önbelleğinin arkasında saniyede 41,67 istek kalır. Sorgu önceden bilinir ve değişmez.

**P2 — taşıyıcıya göre durum olayı yazma.** Taşıyıcı sistemleri gönderi başına yedi olay üretir
(V4); tepe hızı 97,22 istek/s'dir. Her olay yeni bir kayıttır, var olan bir kaydın üzerine
yazılmaz.

**P3 — satıcıya göre dönemsel toplu tarama.** Ücretlendirme işi otuz günü tarar (V11) ve dört
saatlik pencerede saniyede 833,33 kayıt okur. İstek sayısı olarak sıfıra yakındır, kayıt sayısı
olarak en büyüğüdür.

Üç örüntünün birlikte söylediği ilk şey şudur: **istek hızı ile kayıt hızı ayrı ölçülerdir.** P3
depoya günde bir kez uğrar ama P1'in bütün gününden çok kayıt okur. Bir depoyu istek hızıyla
boyutlandırmak, bu yüzden eksik bir hesaptır.

#### Örüntülerin Depoda Bıraktığı İz

Ölçüm için gerçek bir motor kullanılır. `node:sqlite` ile bellek içinde iki düzen kurulur: **tek
depo** düzeninde gönderi ve olay ayrı tablolardadır, **örüntüye göre ayrılmış** düzende P1 için
ayrıca bir belge tablosu vardır ve o tablo gönderinin durumu ile son üç rota adımını tek satırda
tutar (V5'in tarif ettiği yanıt). Ölçülen şey sorgu dilinin kendisi değil, motorun **dokunduğu
tablo sayısı** ile **işlediği kayıt sayısıdır**; kayıt sayımı motorun her satır için çağırdığı
bir sayaç işleviyle yapılır.

```js
// depo/oruntu.mjs — uc erisim oruntusunun iki duzende dokundugu tablo ve isledigi kayit.
// node:sqlite ile gercek bir motor kosar; olculen sey sorgu dili degil, motorun dokundugu
// tablo sayisi ile isledigi kayit sayisidir. Sorgu dili M17'de kuruldu, burada anlatilmaz.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, OLAY = 7;   // model olcegi: satici basina 3000 gonderi
const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INTEGER, gun INTEGER);
CREATE INDEX gonderi_satici ON gonderi(satici, gun);
CREATE TABLE olay(no TEXT, sira INTEGER, durum TEXT);
CREATE INDEX olay_no ON olay(no);
CREATE TABLE belge(no TEXT PRIMARY KEY, govde TEXT);`);

let sayac = 0;
db.function("say", { deterministic: false }, (x) => { sayac += 1; return x; });
const adim = (s) => db.prepare(`EXPLAIN QUERY PLAN ${s}`).all()
  .flatMap((r) => r.detail.match(/(?:SCAN|SEARCH) \w+/g) ?? []);

const g = db.prepare("INSERT INTO gonderi VALUES(?,?,?)"), o = db.prepare("INSERT INTO olay VALUES(?,?,?)");
const bl = db.prepare("INSERT INTO belge VALUES(?,?)");
db.exec("BEGIN");
for (let s = 0; s < SATICI; s += 1) for (let d = 0; d < GUN; d += 1) for (let k = 0; k < GUNLUK; k += 1) {
  const no = `TR-${s}-${d}-${k}`;
  g.run(no, s, d);
  for (let i = 0; i < OLAY; i += 1) o.run(no, i, `durum${i}`);
  bl.run(no, JSON.stringify({ durum: `durum${OLAY - 1}`, adim: [OLAY - 3, OLAY - 2, OLAY - 1] }));
}
db.exec("COMMIT");
console.log(`model: ${SATICI * GUN * GUNLUK} gonderi, ${SATICI * GUN * GUNLUK * OLAY} olay, ` +
  `${SATICI} satici, ${GUN} gun (satici basina ${GUN * GUNLUK} gonderi)`);

const OKUMA = [
  ["P1 takip no ile tekil okuma", "tek depo", "TR-10-15-50",
    "SELECT say(g.no), o.durum FROM gonderi g JOIN olay o ON o.no = g.no WHERE g.no = ? ORDER BY o.sira DESC LIMIT 3"],
  ["P1 takip no ile tekil okuma", "ayrilmis", "TR-10-15-50", "SELECT say(govde) FROM belge WHERE no = ?"],
  ["P3 saticiya gore donem taramasi", "tek depo", 10,
    "SELECT say(o.no) FROM olay o JOIN gonderi g ON g.no = o.no WHERE g.satici = ?"],
  ["P3 saticiya gore donem taramasi", "ayrilmis", 10,
    "SELECT say(no) FROM gonderi WHERE satici = ? AND gun BETWEEN 0 AND 29"],
];
console.log(`\n${"oruntu".padEnd(33)}${"duzen".padEnd(10)}${"tablo".padStart(6)}` +
  `${"islenen".padStart(9)}${"donen".padStart(7)}  motorun adimlari`);
for (const [ad, duzen, arg, sql] of OKUMA) {
  sayac = 0;
  const donen = db.prepare(sql).all(arg).length, a = adim(sql);
  console.log(`${ad.padEnd(33)}${duzen.padEnd(10)}${String(new Set(a).size).padStart(6)}` +
    `${String(sayac).padStart(9)}${String(donen).padStart(7)}  ${a.join(" + ")}`);
}

// P2: yazma yolunun uzunlugu — bir durum olayi kac tabloya dokunuyor
const yaz = (duzen) => {
  db.prepare("INSERT INTO olay VALUES(?,?,?)").run("TR-0-0-0", OLAY, "durumYeni");
  if (duzen === "tek depo") return 1;
  db.prepare("UPDATE belge SET govde = ? WHERE no = ?")
    .run(JSON.stringify({ durum: "durumYeni", adim: [OLAY - 2, OLAY - 1, OLAY] }), "TR-0-0-0");
  return 2;
};
console.log(`\nP2 tasiyicidan gelen durum olayi yazmasi: tek depo ${yaz("tek depo")} tablo, ` +
  `ayrilmis ${yaz("ayrilmis")} tablo`);
```

```
model: 60000 gonderi, 420000 olay, 20 satici, 30 gun (satici basina 3000 gonderi)

oruntu                           duzen      tablo  islenen  donen  motorun adimlari
P1 takip no ile tekil okuma      tek depo       2        7      3  SEARCH g + SEARCH o
P1 takip no ile tekil okuma      ayrilmis       1        1      1  SEARCH belge
P3 saticiya gore donem taramasi  tek depo       2    21000  21000  SEARCH g + SEARCH o
P3 saticiya gore donem taramasi  ayrilmis       1     3000   3000  SEARCH gonderi

P2 tasiyicidan gelen durum olayi yazmasi: tek depo 1 tablo, ayrilmis 2 tablo
```

Bu sayılar **ölçüm** sınıfındadır: bu makinede koşan bir motordan gelirler ve belirlenimlidirler,
ama plan seçimi motorun kendi kararıdır ve sürümüne bağlıdır. Model ölçeği de bir seçimdir; satıcı
başına 3000 gönderi, K01'in 100 gönderi/gün oranıyla otuz günün karşılığıdır.

Üç satır okunmalı. **P1 iki düzende iki kat farklı iş yapıyor.** Tek depoda motor iki tabloya
dokunuyor ve yedi olay kaydını işleyip üçünü döndürüyor; ayrılmış düzende tek tabloya dokunuyor ve
tek kayıt işliyor. Oran yedi katıdır ve bir dizin kararından gelmiyor — iki düzende de dizin var.
Fark, verinin **okunduğu biçimde durup durmamasıdır**.

**P3'ün oranı aynı yedi kat, ama karşıt yönde savunuluyor.** Ücretlendirme işi olay ayrıntısını
istemez, gönderinin ticari alanlarını ister; tek depoda birleştirme onu 21.000 kayda sürüklüyor,
ayrılmış düzende 3000 kayıtta kalıyor. Aynı ayrım P1'i ucuzlatan ayrımdır.

**P2 bedeli ödüyor.** Tek depoda bir durum olayı tek tabloya yazılır; ayrılmış düzende olay
tablosuna yazılır ve belge güncellenir, yani yazma yolu iki tabloya uzar. Okuma yolunun kısalması
bedava değildir; bedeli yazma yolunda toplanır.

#### Karar Ekseni

Ölçüm, seçimi dört soruya indirger. Sorular veri modeliyle değil örüntü ve ölçekle ilgilidir.

**Anahtar sabit mi.** P1 hep aynı anahtarla gelir ve tek kayıt ister. Bu, bir **anahtar–değer
deposunun** (key–value store) yaptığı işin tamamıdır; bildirimsel sorguya, birleştirmeye ve
şemada kısıta ihtiyaç yoktur.

**Kayıt tek parça hâlinde mi okunuyor.** P1'in istediği alanlar birlikte okunur ve birlikte
güncellenir; sınır bir **belge deposunun** (document store) doğal birimiyle örtüşür. P3'ün
istediği alanlar aynı sınırın içinde değildir.

**Sorgu önceden biliniyor mu.** Ücretlendirme dönem, satıcı, tarife ve sözleşme üzerinden değişken
süzme yapar ve soruları zamanla artar. Önceden bilinmeyen soru bildirimsel sorgu ister; ilişkisel
model burada kazanır ve kaydın kaynağı olarak kalır.

**Ölçek tek motoru aşıyor mu.** Yazma 97,22 istek/s, günlük artış 976 MB, saklanan veri 712,48 GB.
Üç sayı da bir tasarımın **tek motorla** taşıyabileceği büyüklüktedir; bu yüzden bu derste verilen
karar veriyi bölmek değil, okuma yolunu örüntüye göre ayırmaktır. Bölme kararı ölçek büyüdüğünde
gündeme gelir ve bu konunun sonraki dersleri onu ele alır.

Dört sorunun yanıtı tek bir depoya işaret etmeyebilir. İlişkisel olmayan aileler — anahtar–değer,
belge, **geniş sütun** (wide column) ve **çizge deposu** (graph store) — kayıt sisteminin yerine
geçmez, ondan türetilen ikincil depolardır; bu konunun son dersi ailelerin kullanım alanlarını ayrı
ele alır. Geniş sütun ailesi Veri Modelleme ve İlişkisel Kuram kursunda "sütun ailesi deposu"
adıyla geçmişti; ikisi aynı ailedir. Kayıt sisteminin tek kalması kuralı da o kursta kuruldu ve
burada değiştirilmez.

#### Hesaba Geri Dönüş

Ayrımın bedeli K01'in satırlarında görünür. İkincil depo yalnız yolda olan gönderiler için
gereklidir; teslim edilmiş bir gönderinin takip sayfası seyrek açılır.

**VD1 — takip belgesinin saklanma süresi 30 gün.** Gerekçe: gönderi teslim edildikten sonra takip
sorgusu seyrekleşir, oysa kaydın kendisi sözleşme uyuşmazlıkları için 730 gün durur (V12). Bu
varsayım K01'in tablosuna eklenmez; duyarlılığı aşağıda 90 günle verilir.

```js
// depo/hesap.mjs — oruntuye gore ayrilmis duzenin K01 hesabindaki satirlara etkisi
const OKUMA = 41.67, YAZMA = 97.22;        // K01: onbellek arkasi okuma/s, tepe yazma istek/s
const DEPO = 138.89, ORAN = 2.33;          // K01: depoya ulasan istek/s, depoda yazma/okuma orani
const TARAMA = 833.33;                     // K01: toplu is tarama kayit/s
const ARTIS = 976, SAKLAMA = 712.48;       // K01: gunluk veri artisi MB, saklanan veri GB
const V3 = 400_000, V5 = 480;              // K01: gunluk gonderi, takip yaniti govdesi (bayt)
const VD1 = 30;                            // bu dersin varsayimi: takip belgesi saklama (gun)

// Olculen duzenek: P1 tek depoda 7 kayit / 2 tablo, ayrilmis duzende 1 kayit / 1 tablo;
// P2 tek depoda 1 tabloya, ayrilmis duzende 2 tabloya yaziyor; P3 21000 / 3000 kayit.
const D = [["tek depo", 7, 1, 21000], ["oruntuye gore ayrilmis", 1, 2, 3000]];
const b = (x) => x.toFixed(2);

console.log(`${"duzen".padEnd(24)}${"okuma kayit/s".padStart(15)}${"depo islem/s".padStart(14)}` +
  `${"yazma/okuma".padStart(13)}${"tarama kayit/s".padStart(16)}`);
for (const [ad, kayit, hedef, tarama] of D) {
  const okumaKayit = OKUMA * kayit, islem = OKUMA + YAZMA * hedef;
  console.log(`${ad.padEnd(24)}${b(okumaKayit).padStart(15)}${b(islem).padStart(14)}` +
    `${b((YAZMA * hedef) / OKUMA).padStart(13)}${b(TARAMA * (tarama / 3000)).padStart(16)}`);
}
console.log(`K01 tabani: depoya ulasan ${DEPO} istek/s, yazma/okuma orani ${ORAN}, ` +
  `toplu tarama ${TARAMA} kayit/s`);

const belgeGunluk = (V3 * V5) / 1e6;                 // MB/gun
const belgeSaklanan = (V3 * V5 * VD1) / 1e9;         // GB
console.log(`\n${"buyume kalemi".padEnd(26)}${"K01".padStart(10)}${"ayrilmis".padStart(11)}${"kat".padStart(8)}`);
console.log(`${"gunluk yazilan MB".padEnd(26)}${b(ARTIS).padStart(10)}${b(ARTIS + belgeGunluk).padStart(11)}` +
  `${b((ARTIS + belgeGunluk) / ARTIS).padStart(8)}`);
console.log(`${"saklanan veri GB".padEnd(26)}${b(SAKLAMA).padStart(10)}${b(SAKLAMA + belgeSaklanan).padStart(11)}` +
  `${b((SAKLAMA + belgeSaklanan) / SAKLAMA).padStart(8)}`);
console.log(`takip belgesi deposu ${b(belgeSaklanan)} GB = saklanan verinin ` +
  `1/${(SAKLAMA / belgeSaklanan).toFixed(0)}'i; VD1 = 90 gun olsaydi ${b((V3 * V5 * 90) / 1e9)} GB`);
```

```
duzen                     okuma kayit/s  depo islem/s  yazma/okuma  tarama kayit/s
tek depo                         291.69        138.89         2.33         5833.31
oruntuye gore ayrilmis            41.67        236.11         4.67          833.33
K01 tabani: depoya ulasan 138.89 istek/s, yazma/okuma orani 2.33, toplu tarama 833.33 kayit/s

buyume kalemi                    K01   ayrilmis     kat
gunluk yazilan MB             976.00    1168.00    1.20
saklanan veri GB              712.48     718.24    1.01
takip belgesi deposu 5.76 GB = saklanan verinin 1/124'i; VD1 = 90 gun olsaydi 17.28 GB
```

Bu satırlar **hesap** sınıfındadır; girdileri K01'in hesapları, ölçülen kayıt oranları ve VD1'dir.

`depoda yazma/okuma orani` 2,33'ten 4,67'ye çıkıyor, çünkü her durum olayı iki yere yazılıyor ve
okuma yolu kısaldığı için okuma tarafı büyümüyor. `depoya ulasan istek/s`'nin yerini alan depo
işlem hızı 138,89'dan 236,11'e çıkıyor: 1,70 kat. Karşılığında okuma tarafında işlenen kayıt hızı
291,69'dan 41,67'ye, toplu taramanın kayıt hızı da 5833,31'den 833,33'e iniyor. Yani **karar yükü
yok etmiyor, okuma yolundan yazma yoluna taşıyor.**

İki büyüme kalemi de birbirinden ayrılıyor. Günlük yazılan bayt 976 MB'den 1168 MB'ye çıkıyor
(1,20 kat), ama saklanan veri yalnız 712,48 GB'den 718,24 GB'ye gidiyor (1,01 kat), çünkü belge
otuz gün sonra düşüyor. Sonucun tasarım karşılığı çarpıcıdır: **P1'in bütün okuma yükünü karşılayan
depo, saklanan verinin 1/124'üdür.** VD1 üç katına çıksa 17,28 GB olurdu ve oran yine kırkta bir
mertebesinde kalırdı. Depo türü kararı bu yüzden veri hacmine değil, hacmin **hangi payının
okunduğuna** bakar.

#### Özet

- Bir depo kararı erişim örüntüsüyle savunulur; P1 41,67 istek/s tekil okuma, P2 97,22 istek/s
  ekleme, P3 saniyede 833,33 kayıt tarama olarak ayrılır ve istek hızı ile kayıt hızı ayrı
  ölçülerdir.
- Aynı okuma tek depoda 2 tabloya dokunup 7 kayıt işlerken, örüntüye göre ayrılmış düzende 1
  tabloya dokunup 1 kayıt işliyor; toplu tarama 21.000 kayıttan 3000 kayda iniyor.
- Bedel yazma yolundadır: bir durum olayı bir tablo yerine iki tabloya yazılıyor.
- Seçim dört soruyla verilir — anahtar sabit mi, kayıt tek parça mı okunuyor, sorgu önceden
  biliniyor mu, ölçek tek motoru aşıyor mu; ilişkisel model önceden bilinmeyen sorular yüzünden
  kaydın kaynağı olarak kalır.
- K01'e dönüş: depo işlem hızı 138,89'dan 236,11 işlem/s'ye (1,70 kat) ve yazma/okuma oranı
  2,33'ten 4,67'ye çıkıyor; okuma tarafında işlenen kayıt 291,69'dan 41,67'ye iniyor.
- VD1 = 30 gün ile takip belgesi deposu 5,76 GB, yani saklanan verinin 1/124'ü; günlük yazılan
  bayt 976'dan 1168 MB'ye çıkarken saklanan veri 712,48'den 718,24 GB'ye çıkıyor.

#### Sonraki Adım

Depo türü kararı verildi ve okuma yolu kısaldı, ama ortada hâlâ **tek bir kopya** var. O kopya
saniyede 236,11 işlem alıyor, 718,24 GB taşıyor ve düştüğünde takip sorgusunun da ücretlendirmenin
de yanıtı yok. Okuma yükünü birden çok makineye dağıtmanın ilk yolu veriyi bölmek değil,
çoğaltmaktır: aynı veriyi birden çok yerde tutmak. Sonraki ders bu kararı ele alır — ana–kopya
düzeninde okuma kaç kopyaya bölünüyor, yazma neden bölünmüyor, her kopyanın gördüğü yazma/okuma
oranı ne oluyor, saklanan veri kaç katına çıkıyor ve yazmayı birden çok yerde kabul etmek hangi
sayıyı doğuruyor.

### Çoğaltma

Source: https://academia.sh/tr/kurslar/veri-olcekleme/cogaltma

Aynı verinin birden çok yerde tutulmasının ölçekleme karşılığı: ana–kopya düzeninde okumanın bölünüp yazmanın her düğümde tekrarlanması, kopya eklemenin azalan kazancı, saklanan verinin kopya sayısıyla çarpılması, asenkron gerikliğin bayat takip yanıtına çevrilmesi ve çok ana düzende iki bölgenin aynı gönderiye yazmasının kalıcı ayrışma olarak sayılması.

Önceki ders deponun türünü erişim örüntüsüne bağladı ve okuma yolunu kısalttı. Ortada hâlâ tek bir
kopya var: saniyede 236,11 işlem alan, 718,24 GB taşıyan ve düştüğünde takip sorgusunun da
ücretlendirmenin de yanıtsız kaldığı tek bir düğüm. Yükü birden çok makineye dağıtmanın ilk yolu
veriyi bölmek değil, **çoğaltmaktır** (replication): aynı veriyi birden çok yerde tutmak.

Çoğaltmanın mekaniği İlişkisel Veritabanı Yönetimi kursunda kuruldu — günlük akışı, çoğaltma
gecikmesi, senkron onayın bedeli, çoğunluk kuralı ve devralma orada anlatıldı ve ölçüldü. Burada
mekanik tekrarlanmaz. Bu dersin sorusu bir **ölçekleme kararıdır**: kopya eklemek hangi sayıyı
düşürür, hangi sayıyı düşürmez ve karşılığında ne büyür. Tutarlılık modellerinin tanımları da
Sistem Tasarımına Giriş kursunda kuruldu; burada yalnız kullanılırlar.

#### Çoğaltma Neyi Böler

**Ana–kopya** (leader–follower) düzeninde bir düğüm yazmaları kabul eder, ötekiler o düğümün
değişikliklerini uygular ve okumaları karşılar. Bölünen şey okumadır; yazma bölünmez, çünkü her
düğüm **her** yazmayı uygulamak zorundadır. Aşağıdaki hesap bu asimetriyi kopya sayısına göre
açar ve ikinci tabloda asenkron gerikliğin okuma tarafındaki karşılığını verir.

**VD2 — bir gönderinin yolda kalma süresi 3 gün.** Gerekçe: kabul ile teslim arasında geçen süre;
V4'ün yedi durum olayı bu süreye yayılır ve takip sorguları bu süredeki gönderilere gelir.
Duyarlılığı doğrusaldır — süre iki katına çıkarsa yoldaki gönderi sayısı iki katına çıkar ve bayat
yanıt oranı yarıya iner. Bu varsayım K01'in tablosuna eklenmez.

```js
// cogaltma/ana-kopya.mjs — okuma kopyalara bolunurken yazmanin her dugumde tekrarlanmasi,
// ve asenkron gerikligin bayat takip yanitina cevrilmesi. Hepsi aritmetiktir.
const OKUMA = 41.67;        // K01: onbellek arkasi okuma/s
const YAZMA = 194.44;       // 01. ders: ayrilmis duzende depo yazma islem/s
const SAKLAMA = 718.24;     // 01. ders: saklanan veri GB
const OLAY = 97.22;         // K01: tepe yazma istek/s (durum olayi)
const V3 = 400_000;         // K01: gunluk gonderi
const VD2 = 3;              // bu dersin varsayimi: gonderinin yolda kalma suresi (gun)
const YOLDA = V3 * VD2, GUN = 86_400;
const b = (x, n = 2) => x.toFixed(n);

console.log(`${"kopya".padStart(6)}${"kopya basina okuma/s".padStart(21)}${"dugum basina yazma/s".padStart(21)}` +
  `${"dugum yuku".padStart(12)}${"yazma/okuma".padStart(13)}${"kopya kazanci".padStart(15)}${"toplam GB".padStart(11)}`);
let onceki = null;
for (const n of [1, 2, 3, 4, 5]) {
  const okuma = OKUMA / n, yuk = YAZMA + okuma;
  console.log(`${String(n).padStart(6)}${b(okuma).padStart(21)}${b(YAZMA).padStart(21)}` +
    `${b(yuk).padStart(12)}${b(YAZMA / okuma).padStart(13)}` +
    `${(onceki === null ? "-" : b(onceki - yuk)).padStart(15)}${b(SAKLAMA * (1 + n)).padStart(11)}`);
  onceki = yuk;
}
console.log(`tek dugum (01. ders): yuk ${b(YAZMA + OKUMA)} islem/s, yazma/okuma ${b(YAZMA / OKUMA)}, ` +
  `${b(SAKLAMA)} GB`);

console.log(`\nyolda olan gonderi = ${YOLDA.toLocaleString("tr-TR")} (VD2 = ${VD2} gun)`);
console.log(`${"gerilik s".padStart(10)}${"penceredeki degisen gonderi".padStart(28)}` +
  `${"bayat okuma orani".padStart(19)}${"bayat yanit/s".padStart(15)}${"bayat yanit/gun".padStart(17)}`);
for (const L of [0.5, 1, 5, 10]) {
  const degisen = OLAY * L, oran = degisen / YOLDA, bayat = OKUMA * oran;
  console.log(`${b(L, 1).padStart(10)}${b(degisen).padStart(28)}${oran.toExponential(2).padStart(19)}` +
    `${b(bayat, 5).padStart(15)}${b(bayat * GUN, 1).padStart(17)}`);
}
```

```
 kopya kopya basina okuma/s dugum basina yazma/s  dugum yuku  yazma/okuma  kopya kazanci  toplam GB
     1                41.67               194.44      236.11         4.67              -    1436.48
     2                20.84               194.44      215.28         9.33          20.84    2154.72
     3                13.89               194.44      208.33        14.00           6.95    2872.96
     4                10.42               194.44      204.86        18.66           3.47    3591.20
     5                 8.33               194.44      202.77        23.33           2.08    4309.44
tek dugum (01. ders): yuk 236.11 islem/s, yazma/okuma 4.67, 718.24 GB

yolda olan gonderi = 1.200.000 (VD2 = 3 gun)
 gerilik s penceredeki degisen gonderi  bayat okuma orani  bayat yanit/s  bayat yanit/gun
       0.5                       48.61            4.05e-5        0.00169            145.8
       1.0                       97.22            8.10e-5        0.00338            291.7
       5.0                      486.10            4.05e-4        0.01688           1458.4
      10.0                      972.20            8.10e-4        0.03376           2916.8
```

Bu sayılar **hesap** sınıfındadır; girdileri K01'in hesapları, 01. dersin sonuçları ve VD2'dir.

#### Sayıların Okunması

**Kopya eklemek yazma yükünü hiç düşürmüyor.** Düğüm başına yazma sütunu beş satırda da 194,44
işlem/s. Bu bir taban değil, tavanla ilgisi olmayan bir zemin: kopya sayısı ne olursa olsun her
düğüm her durum olayını uygular. Bölünen tek sütun okumadır ve o da 41,67'den başlıyor.

**Kazanç hızla tükeniyor.** Birinci kopyadan ikinciye geçiş düğüm yükünü 20,84 işlem/s düşürüyor;
ikinciden üçüncüye 6,95, dördüncüden beşinciye 2,08. Beşinci kopya ilk kopyanın kazandırdığının
onda birini kazandırıyor, çünkü indirilebilen tek pay okumadır ve okuma zaten yükün yüzde 18'i.
Ana–kopya çoğaltması bu yüzden **okuma ağırlıklı bir yükün aracıdır**; K01'in hesabı ise deponun
gördüğü yerde yazma ağırlıklıdır ve 01. dersin belge güncellemesi bu ağırlığı büyütmüştü.

**`depoda yazma/okuma orani` her kopyada büyüyor.** K01'de 2,33'tü, 01. dersten sonra 4,67 oldu, üç
kopyada 14,00'e çıkıyor. Oran bir bozulma göstergesi değil, düzenin tanımıdır: okuma dağıldıkça her
düğümün gördüğü karışım yazmaya kayar. Bir düğümün yazma için ayarlanması gerektiği kararı bu
sütundan okunur.

**`saklanan veri GB` doğrudan çarpılıyor.** Tek düğümde 718,24 GB olan veri, üç kopyayla 2872,96
GB, beş kopyayla 4309,44 GB oluyor. Çoğaltmanın faturası budur ve K01'in saklama satırını en çok
oynatan karardır: 712,48 GB'lik özgün hesap altı katına çıkabiliyor.

**Gerilik ikinci tabloda bir yanıt sayısına dönüşüyor.** Bir saniyelik çoğaltma gecikmesinde
penceredeki değişen gönderi 97,22'dir; yoldaki 1.200.000 gönderiye oranı 8,10e-5 ve depoya ulaşan
41,67 okumanın karşılığı günde 291,7 bayat takip yanıtıdır. Beş saniyede 1458,4. Sayı küçüktür ve
küçüklüğünün nedeni de tablodadır: bayat yanıt oranı, değişim hızının **yoldaki gönderi sayısına**
oranıdır ve yoldaki gönderi sayısı büyüktür. Aynı hesap kısa ömürlü ve çok değişen bir veri
kümesinde başka bir sonuç verirdi. Kopyadan okumanın kabul edilebilirliği bu yüzden bir ilke değil,
bu bölmenin sonucudur.

Kopyanın ikinci karşılığı hizmet erişilebilirliğidir: ana düştüğünde kopyalardan biri yükseltilir.
Devralma ve çoğunluk kuralı İlişkisel Veritabanı Yönetimi kursunda ölçüldü ve burada
tekrarlanmaz.

#### Çok Ana ve Çakışma

Yazma tek düğümde kaldığı sürece yazma ölçeklenmez. **Çok ana** (multi-leader) düzeni bu kısıtı
kaldırır: birden çok düğüm yazma kabul eder ve birbirinin değişikliklerini uygular. Karşılığında
tek ananın bedava verdiği bir şey kaybolur — yazmaların tek bir sırası.

Örüntü bunu somutlaştırıyor. P2, taşıyıcıya göre durum olayı yazmasıdır ve bir gönderi aktarma
anında iki bölgenin arasındadır: çıkış bölgesi "aktarmaya verildi", varış bölgesi "aktarmadan
alındı" olayını birkaç saniye arayla yazar.

**VD3 — bir gönderinin iki bölgeden yakın zamanlı yazma alma oranı 0,02.** Gerekçe: yedi olayın
biri aktarma anına düşer ve o anda iki bölge de yazar. Duyarlılığı doğrusaldır; aşağıda 0,04 ile
verilir. K01'in tablosuna eklenmez.

```js
// cogaltma/cok-ana.mjs — iki ananin ayni gonderiye yazmasi. Bu bir MODELDIR: tur soyut bir
// adimdir, yayilma turu bir parametredir, olculmus sure degildir. Sayilan sey, yayilma
// bittikten sonra iki dugumun ayni son durumu gosterip gostermedigidir.
const OLAY = 20_000, GONDERI = 4000, YAYILMA = 3;   // model parametreleri
const VD3 = 0.02;            // varsayim: gonderinin iki bolgeden yakin zamanli yazma alma orani
let s = 20260730 % 2147483647;                       // sabit tohum: her kosumda ayni dizi
const rast = () => (s = (s * 48271) % 2147483647) / 2147483647;

const olaylar = [];
for (let t = 0; t < OLAY; t += 1) {
  const g = Math.floor(rast() * GONDERI), bolge = rast() < 0.5 ? "A" : "B";
  olaylar.push({ t, g, bolge, ad: `o${t}` });
  if (rast() < VD3) {                                // aktarma ani: karsi bolge de yaziyor
    const gecikme = 1 + Math.floor(rast() * YAYILMA);
    olaylar.push({ t: t + gecikme, g, bolge: bolge === "A" ? "B" : "A", ad: `o${t}b` });
  }
}
const gonderiOlay = new Map();
for (const o of olaylar) (gonderiOlay.get(o.g) ?? gonderiOlay.set(o.g, []).get(o.g)).push(o);
let cakisan = 0;
for (const liste of gonderiOlay.values())
  if (liste.some((a) => liste.some((c) => a.bolge !== c.bolge && Math.abs(a.t - c.t) <= YAYILMA))) cakisan += 1;

function kosum(kural) {
  const dugum = { A: new Map(), B: new Map() };
  for (const d of ["A", "B"]) {
    const gelen = olaylar.map((o) => ({ varis: o.bolge === d ? o.t : o.t + YAYILMA, yerel: o.bolge === d ? 1 : 0, o }));
    gelen.sort((x, y) => x.varis - y.varis || y.yerel - x.yerel);   // varis sirasi, yerel once
    for (const { o } of gelen) {
      if (kural === "son-yazan") { dugum[d].set(o.g, o.ad); continue; }
      const k = dugum[d].get(o.g) ?? dugum[d].set(o.g, []).get(o.g);   // ekleme: olay kumesi
      k.push(o);
    }
  }
  const durum = (d, g) => {
    const v = dugum[d].get(g);
    if (kural === "son-yazan") return v;
    return v.slice().sort((x, y) => y.t - x.t || (x.ad < y.ad ? 1 : -1))[0].ad;   // belirlenimli kural
  };
  let ayrisan = 0;
  for (const g of dugum.A.keys()) if (durum("A", g) !== durum("B", g)) ayrisan += 1;
  const kayit = kural === "son-yazan" ? dugum.A.size
    : [...dugum.A.values()].reduce((a, v) => a + v.length, 0);
  return { ayrisan, kayit, dokunulan: dugum.A.size };
}

console.log(`model: ${olaylar.length} olay, ${gonderiOlay.size} gonderi dokunuldu, ` +
  `yayilma ${YAYILMA} tur, VD3 = ${VD3}`);
console.log(`iki bolgeden yakin zamanli yazma alan gonderi = ${cakisan} ` +
  `(dokunulanlarin %${((100 * cakisan) / gonderiOlay.size).toFixed(2)}'si)`);
console.log(`\n${"birlestirme kurali".padEnd(22)}${"ayrisan gonderi".padStart(17)}` +
  `${"cakisanlarin payi".padStart(19)}${"dugumde tutulan kayit".padStart(23)}`);
for (const kural of ["son-yazan", "ekleme"]) {
  const r = kosum(kural);
  console.log(`${kural.padEnd(22)}${String(r.ayrisan).padStart(17)}` +
    `${(r.ayrisan / cakisan).toFixed(4).padStart(19)}${String(r.kayit).padStart(23)}`);
}

const GUNLUK_OLAY = 2_800_000, V4 = 7;                // K01: gunluk durum olayi, olay/gonderi
const oran = kosum("son-yazan").ayrisan / gonderiOlay.size;
console.log(`\nK01 olcegine tasima: gunde ${GUNLUK_OLAY.toLocaleString("tr-TR")} olay ve ` +
  `${(GUNLUK_OLAY / V4).toLocaleString("tr-TR")} gonderi (V4 = ${V4})`);
console.log(`son-yazan kuralinda gunde ${Math.round((GUNLUK_OLAY / V4) * oran).toLocaleString("tr-TR")} ` +
  `gonderi iki bolgede farkli son durum gosterir; VD3 = 0,04 olsaydi yaklasik iki kati`);
```

```
model: 20365 olay, 3971 gonderi dokunuldu, yayilma 3 tur, VD3 = 0.02
iki bolgeden yakin zamanli yazma alan gonderi = 363 (dokunulanlarin %9.14'si)

birlestirme kurali      ayrisan gonderi  cakisanlarin payi  dugumde tutulan kayit
son-yazan                            66             0.1818                   3971
ekleme                                0             0.0000                  20365

K01 olcegine tasima: gunde 2.800.000 olay ve 400.000 gonderi (V4 = 7)
son-yazan kuralinda gunde 6.648 gonderi iki bolgede farkli son durum gosterir; VD3 = 0,04 olsaydi yaklasik iki kati
```

Model üç şey söylüyor. Birincisi, çakışma seyrek değildir: gönderi başına ortalama beş olay
düştüğünde, olay başına yüzde ikilik bir aktarma oranı gönderilerin yüzde 9,14'ünü iki bölgeden
yazılan gönderi hâline getiriyor.

İkincisi, çakışan yazmaların hepsi ayrışma üretmiyor — 363 gönderiden 66'sı, yani beşte biri.
Ayrışma yalnız iki olayın **iki düğüme farklı sırayla ulaştığı** durumda doğuyor. Son yazanın
kazandığı kuralda düğüm A yerel olayını önce uygular, düğüm B karşıdakini; yayılma bittiğinde iki
düğümde iki farklı son durum kalır ve kendiliğinden düzelmez. K01 ölçeğine taşındığında günde 6648
gönderi, iki bölgede farklı son durum gösterir. VD3 iki katına çıkarsa sayı da yaklaşık iki katına
çıkar.

Üçüncüsü ve tasarım açısından önemlisi: **ayrışma verinin değil, saklama biçiminin sonucudur.**
Ekleme kuralında aynı olay dizisi sıfır ayrışma veriyor, çünkü birleştirme işlemi kümelerin
birleşimidir ve birleşim sıradan bağımsızdır. Son durum, kümeden belirlenimli bir kuralla türetilir
ve iki düğüm aynı kümeye sahip olduğu için aynı sonucu bulur. Bedeli son sütundadır: düğümde
tutulan kayıt 3971'den 20.365'e çıkıyor, 5,13 kat. Bu kursun akışında bedel kâğıt üzerindedir,
çünkü K01 zaten bütün olayları saklıyor (V6, V12); asıl değişen, **son durumun üzerine yazılan bir
alan olmaktan çıkıp türetilen bir değer olmasıdır.**

P2'nin biçimi bu yüzden belirleyicidir. Durum olayı ekleme olduğu için çok ana düzenine uyar;
üzerine yazılan bir alan olsaydı uymazdı. Çok ana kararı veriyi değil, **yazmanın biçimini**
sorgular. Yayılma bittikten sonra kopyaların aynı yere gelmesi sonunda tutarlılığın söz verdiği
şeydir ve tanımı Sistem Tasarımına Giriş kursunda verildi; bu ölçüm o sözün ancak birleştirme kuralı
sıradan bağımsız olduğunda tutulduğunu gösteriyor.

#### Özet

- Ana–kopya düzeninde okuma bölünür, yazma bölünmez: düğüm başına yazma beş kopyada da 194,44
  işlem/s'de kalıyor, kopya başına okuma 41,67'den 8,33'e iniyor.
- Kopya eklemenin kazancı hızla tükeniyor: düğüm yükü birinci eklemede 20,84 işlem/s düşerken
  dördüncüde 2,08 düşüyor; `depoda yazma/okuma orani` 4,67'den üç kopyada 14,00'e çıkıyor.
- `saklanan veri GB` doğrudan çarpılıyor: 718,24 GB üç kopyayla 2872,96, beş kopyayla 4309,44 GB.
- Gerilik bir yanıt sayısına çevrilir: VD2 = 3 gün ile yoldaki 1.200.000 gönderide bir saniyelik
  gecikme günde 291,7, beş saniyelik gecikme 1458,4 bayat takip yanıtı üretir.
- Çok anada VD3 = 0,02 ile gönderilerin yüzde 9,14'ü iki bölgeden yazılıyor; son yazanın kazandığı
  kuralda bunların beşte biri kalıcı ayrışma veriyor, K01 ölçeğinde günde 6648 gönderi.
- Ayrışma saklama biçiminden doğuyor: ekleme kuralında sıfır ayrışma çıkıyor, bedeli düğümde
  tutulan kaydın 3971'den 20.365'e (5,13 kat) çıkmasıdır.

#### Sonraki Adım

Çoğaltma okumayı böldü ama her düğüme aynı 194,44 yazmayı bıraktı ve saklanan veriyi kopya sayısıyla
çarptı. İki sınır da aynı yerden geliyor: bütün veri her yerde duruyor. Oysa deponun taşıdığı veri
tek bir bütün değil — ücretlendirmenin sorduğu sorularla teslimat operasyonunun sorduğu sorular
farklı kayıtlara dokunuyor ve bunu 01. dersin tarama ölçümü göstermişti. Sonraki ders veriyi
anahtara göre değil **işleve göre** bölmeyi ele alır: iki bağlam kendi deposuna ayrıldığında her
deponun aldığı istek ve taşıdığı veri ne oluyor, yazma yolu kaç depoya uzuyor, iki bağlama birden
dokunan sorgu kaç depoya gidiyor ve ortak alanların yinelenmesi saklanan veriye ne ekliyor.

### Federasyon

Source: https://academia.sh/tr/kurslar/veri-olcekleme/federasyon

Verinin anahtara göre değil işleve göre bölünmesi: ücretlendirme ile teslimat operasyonu bağlamlarının kendi depolarına ayrılması, iki bağlama birden dokunan sorgunun dokunduğu depo ve depolar arasında taşınan anahtar sayısının gerçek bir motorda ölçülmesi, ortak alanların yinelenmesinin saklanan veriye eklediği payın hesaplanması ve toplu taramanın çevrimiçi depodan çıkarılması.

Çoğaltma okumayı böldü ama her düğüme aynı 194,44 yazmayı bıraktı ve saklanan veriyi kopya
sayısıyla çarptı. İki sınır da aynı yerden geliyor: **bütün veri her yerde duruyor.** Oysa deponun
taşıdığı veri tek bir bütün değil. Ücretlendirmenin sorduğu sorularla teslimat operasyonunun
sorduğu sorular farklı kayıtlara dokunuyor; 01. dersin tarama ölçümü bunu göstermişti — dönem
taraması olay ayrıntısına hiç ihtiyaç duymadan 21.000 kayıttan 3000 kayda inmişti.

**Federasyon** (federation), veriyi anahtara göre değil **işleve göre** bölmektir: her bağlam kendi
deposunu alır ve o depo yalnız o bağlamın sorularına hizmet eder. Ayrım anahtarla ilgili değildir
— aynı takip numarası iki depoda da geçebilir; ayrım, o numaranın hangi soruyla sorulduğuyla
ilgilidir.

#### İki Bağlam, İki Depo

Gönderi takip ve ücretlendirme servisinde iki bağlam vardır. **Teslimat operasyonu** P1 ile P2'yi
taşır: takip numarasına göre tekil okuma ve taşıyıcıdan gelen durum olayı yazması. **Ücretlendirme**
P3'ü taşır: satıcıya göre dönemsel toplu tarama, tarife, sözleşme ve fatura satırı.

Bölme kusursuz değildir, çünkü iki bağlam bazı kayıtlara birlikte ihtiyaç duyar. Ücretlendirme
gönderinin ne zaman kabul edildiğini ve ne zaman teslim edildiğini bilmek zorundadır, ama aradaki
rota adımlarını bilmesi gerekmez.

**VD4 — ücretlendirmeyi ilgilendiren olay oranı 2/7.** Gerekçe: fatura kabul ve teslim zamanına
bağlıdır; V4'ün yedi olayından yalnız ikisi ücreti etkiler. Duyarlılığı aşağıda 3/7 ile verilir.

**VD5 — gönderi kaydı iki bağlama bölündüğünde payları 500 ve 600 bayttır.** Gerekçe: V7'nin 900
baytlık kaydında ağırlık, hacim, sözleşme ve tarife ücretlendirmeye, bölge ve rota alanları
operasyona gider; takip numarası, satıcı kimliği ve dönem etiketi ise iki depoda da durmak zorunda
olduğu için toplam 1100 bayta çıkar, yinelenen pay 200 bayttır. Duyarlılığı aşağıda 400 bayt
yinelenme ile verilir.

İki varsayım da K01'in tablosuna eklenmez.

#### Bölünen Sorgu Kaç Depoya Dokunuyor

Aşağıdaki düzenek üç depoyu bellek içinde kurar — federasyonsuz tek depo ile iki federe depo — ve
aynı soruları her ikisine sorar. Ölçülen şey **dokunulan depo sayısı**, **işlenen kayıt** ve
federe düzende **depolar arasında taşınan anahtar** sayısıdır.

```js
// federasyon/capraz.mjs — ayni sorgularin tek depoda ve iki federe depoda dokundugu depo,
// isledigi kayit ve depolar arasinda tasidigi anahtar. node:sqlite ile gercek bir motor kosar.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, OLAY = 7, SON = OLAY - 1;   // model olcegi
const [tek, ucret, ops] = [0, 1, 2].map(() => new DatabaseSync(":memory:"));
tek.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INT, gun INT, tarife INT, bolge INT);
CREATE INDEX g_satici ON gonderi(satici, gun);
CREATE TABLE olay(no TEXT, sira INT, durum TEXT); CREATE INDEX o_no ON olay(no);`);
ucret.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INT, gun INT, tarife INT);
CREATE INDEX g_satici ON gonderi(satici, gun);
CREATE TABLE olay(no TEXT, sira INT, durum TEXT); CREATE INDEX o_no ON olay(no);`);
ops.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INT, bolge INT);
CREATE TABLE olay(no TEXT, sira INT, durum TEXT); CREATE INDEX o_no ON olay(no);`);

const sayac = { tek: 0, ucret: 0, ops: 0 };
for (const [ad, db] of [["tek", tek], ["ucret", ucret], ["ops", ops]])
  db.function("say", { deterministic: false }, (x) => { sayac[ad] += 1; return x; });
const toplam = () => sayac.tek + sayac.ucret + sayac.ops;

const gT = tek.prepare("INSERT INTO gonderi VALUES(?,?,?,?,?)"), oT = tek.prepare("INSERT INTO olay VALUES(?,?,?)");
const gU = ucret.prepare("INSERT INTO gonderi VALUES(?,?,?,?)"), oU = ucret.prepare("INSERT INTO olay VALUES(?,?,?)");
const gO = ops.prepare("INSERT INTO gonderi VALUES(?,?,?)"), oO = ops.prepare("INSERT INTO olay VALUES(?,?,?)");
for (const d of [tek, ucret, ops]) d.exec("BEGIN");
for (let s = 0; s < SATICI; s += 1) for (let d = 0; d < GUN; d += 1) for (let k = 0; k < GUNLUK; k += 1) {
  const no = `TR-${s}-${d}-${k}`;
  gT.run(no, s, d, 100 + s, d % 5); gU.run(no, s, d, 100 + s); gO.run(no, s, d % 5);
  for (let i = 0; i < OLAY; i += 1) {
    oT.run(no, i, `durum${i}`); oO.run(no, i, `durum${i}`);
    if (i === 0 || i === SON) oU.run(no, i, `durum${i}`);      // VD4: yalniz kabul ve teslim
  }
}
for (const d of [tek, ucret, ops]) d.exec("COMMIT");
const adet = (db, t) => db.prepare(`SELECT count(*) c FROM ${t}`).get().c;
console.log(`model: ${adet(tek, "gonderi")} gonderi; olay kaydi -> tek depo ${adet(tek, "olay")}, ` +
  `teslimat ${adet(ops, "olay")}, ucretlendirme ${adet(ucret, "olay")}`);

const capraz = () => {                                          // anahtarlari tasiyip ikinci depoya sormak
  const a = ucret.prepare("SELECT say(no) AS no FROM gonderi WHERE satici = ?").all(10).map((r) => r.no);
  const q = ops.prepare(`SELECT say(no) FROM olay WHERE no = ? AND sira = ${SON}`);
  for (const k of a) q.all(k);
  return a.length;
};
const IS = [
  ["P1 tekil okuma / federe", 1, () =>
    (ops.prepare("SELECT say(no) FROM olay WHERE no = ? ORDER BY sira DESC LIMIT 3").all("TR-10-15-50"), 0)],
  ["P3 donem taramasi / federe", 1, () =>
    (ucret.prepare("SELECT say(no) FROM gonderi WHERE satici = ? AND gun BETWEEN 0 AND 29").all(10), 0)],
  ["S4 son rota adimi / tek depo", 1, () => (tek.prepare(
    `SELECT say(g.no), o.durum FROM gonderi g JOIN olay o ON o.no = g.no WHERE g.satici = ? AND o.sira = ${SON}`).all(10), 0)],
  ["S4 son rota adimi / federe", 2, capraz],
  ["S5 teslim tarihine gore ucret / federe", 1, () => (ucret.prepare(
    `SELECT say(g.no), g.tarife FROM gonderi g JOIN olay o ON o.no = g.no WHERE g.satici = ? AND o.sira = ${SON}`).all(10), 0)],
  ["S5 teslim olayi kopyalanmasaydi", 2, capraz],
];
console.log(`\n${"sorgu / duzen".padEnd(40)}${"depo".padStart(6)}${"islenen kayit".padStart(15)}${"tasinan anahtar".padStart(17)}`);
for (const [ad, depo, calis] of IS) {
  sayac.tek = 0; sayac.ucret = 0; sayac.ops = 0;
  const anahtar = calis();
  console.log(`${ad.padEnd(40)}${String(depo).padStart(6)}${String(toplam()).padStart(15)}${String(anahtar).padStart(17)}`);
}
const bayt = Buffer.byteLength("TR-10-15-50");
console.log(`\nanahtar ${bayt} bayt: federe duzende S4 icin depolar arasinda ` +
  `${(3000 * bayt) / 1000} kB tasiniyor, tek depoda 0`);
```

```
model: 60000 gonderi; olay kaydi -> tek depo 420000, teslimat 420000, ucretlendirme 120000

sorgu / duzen                             depo  islenen kayit  tasinan anahtar
P1 tekil okuma / federe                      1              7                0
P3 donem taramasi / federe                   1           3000                0
S4 son rota adimi / tek depo                 1           3000                0
S4 son rota adimi / federe                   2           6000             3000
S5 teslim tarihine gore ucret / federe       1           3000                0
S5 teslim olayi kopyalanmasaydi              2           6000             3000

anahtar 11 bayt: federe duzende S4 icin depolar arasinda 33 kB tasiniyor, tek depoda 0
```

Bu sayılar **ölçüm** sınıfındadır; bu makinede koşan bir motordan gelirler ve belirlenimlidirler.

İlk iki satır iyi haberi veriyor: bağlamın kendi soruları kendi deposunda kalıyor ve tek depodaki
maliyetleriyle aynı. Federasyon bu sorulara ne kazandırıyor ne kaybettiriyor.

Dört ve beşinci satırlar asıl bilgiyi taşıyor. **S4 — bir satıcının dönemindeki gönderilerin son
rota adımı** iki bağlamın kesişiminde duruyor: satıcı ve dönem ücretlendirmenin, rota adımı
operasyonun alanıdır. Tek depoda bu tek bir sorgudur, motor birleştirmeyi kendi içinde yapar ve
3000 kayıt işler. Federe düzende sorgu ikiye bölünüyor: ücretlendirmeden 3000 anahtar okunuyor,
uygulama bunları taşıyıp teslimat deposuna soruyor ve toplam işlenen kayıt 6000'e çıkıyor. Kaybolan
şey birleştirmenin kendisidir; yerine geçen şey **depolar arasında taşınan 3000 anahtar**, yani 33
kilobayttır. Federasyonun uygulamaya asıl dayattığı bedel budur — motorun bedava yaptığı işi
uygulama kodu para vererek yapar.

Son iki satır VD4'ün gerekçesini ölçüyor. **S5 — teslim tarihine göre ücret**, teslim olayının
kopyası ücretlendirme deposunda olduğu için tek depoda kalıyor ve 3000 kayıtla bitiyor. Kopya
olmasaydı aynı soru S4 ile aynı biçime düşer: 2 depo, 6000 kayıt, 3000 taşınan anahtar. Yani
**yinelenen 2/7 olay, bir çapraz sorguyu tek depoya indiriyor.** Bir bağlam sınırının doğru çizilip
çizilmediğinin ölçüsü budur: sınırın doğru yerde olması, çapraz soru sayısının küçük olmasıdır.

#### Hesaba Geri Dönüş

Yinelemenin bedeli K01'in iki büyüme satırında toplanıyor.

```js
// federasyon/hesap.mjs — iki baglama bolunen deponun K01 satirlarindaki karsiligi
const V3 = 400_000, V4 = 7, V6 = 220, V7 = 900, V8 = 3, V12 = 730;   // K01 varsayimlari
const OKUMA = 41.67, OLAY_YAZMA = 97.22, TARAMA = 833.33;            // K01 hesaplari
const ARTIS = 976, SAKLAMA = 712.48;                                  // K01 hesaplari
const BELGE = 194.44;          // 01. ders: durum olayi teslimat tarafinda 2 hedefe yaziliyor
const VD4 = 2 / 7;             // varsayim: ucretlendirmeyi ilgilendiren olay orani
const VD5 = [500, 600];        // varsayim: bolunen gonderi kaydinin paylari (ucret, ops) bayt
const GONDERI_YAZMA = 13.89;   // K01 turevi: (V3 / 86.400) x V8 = gonderi olusturma, tepe kayit/s
const b = (x, n = 2) => x.toFixed(n);

const ops = GONDERI_YAZMA + BELGE + OKUMA;
const uc = GONDERI_YAZMA + OLAY_YAZMA * VD4;
console.log(`${"depo".padEnd(24)}${"istek+islem/s".padStart(14)}${"tarama kayit/s".padStart(16)}` +
  `${"gunluk MB".padStart(11)}${"saklanan GB".padStart(13)}`);
const opsMB = (V3 * VD5[1] + V3 * V4 * V6) / 1e6, ucMB = (V3 * VD5[0] + V3 * V4 * VD4 * V6) / 1e6;
console.log(`${"teslimat operasyonu".padEnd(24)}${b(ops).padStart(14)}${"0".padStart(16)}` +
  `${b(opsMB).padStart(11)}${b((opsMB * V12) / 1000).padStart(13)}`);
console.log(`${"ucretlendirme".padEnd(24)}${b(uc).padStart(14)}${b(TARAMA).padStart(16)}` +
  `${b(ucMB).padStart(11)}${b((ucMB * V12) / 1000).padStart(13)}`);
console.log(`${"tek depo (federasyonsuz)".padEnd(24)}${b(GONDERI_YAZMA + BELGE + OKUMA).padStart(14)}` +
  `${b(TARAMA).padStart(16)}${b(ARTIS).padStart(11)}${b(SAKLAMA).padStart(13)}`);

const toplamMB = opsMB + ucMB;
console.log(`\ntoplam islem ${b(ops + uc)} /s = tek deponun ${b((ops + uc) / (GONDERI_YAZMA + BELGE + OKUMA))} kati`);
console.log(`gunluk yazilan ${b(toplamMB)} MB (K01: ${ARTIS}) = ${b(toplamMB / ARTIS)} kat`);
console.log(`saklanan ${b((toplamMB * V12) / 1000)} GB (K01: ${SAKLAMA}) = ${b((toplamMB * V12) / 1000 / SAKLAMA)} kat`);
const yol = (GONDERI_YAZMA * 2 + OLAY_YAZMA * (1 + VD4)) / (GONDERI_YAZMA + OLAY_YAZMA);
console.log(`yazma yolunun uzunlugu = ${b(yol, 3)} depo (gonderi 2, durum olayi ${b(1 + VD4, 3)})`);

const duyarli = (vd4, yin) => ((V3 * (V7 + yin) + V3 * V4 * V6 * (1 + vd4)) / 1e6);
console.log(`\nduyarlilik: VD4 = 3/7 -> gunluk ${b(duyarli(3 / 7, 200))} MB, ` +
  `saklanan ${b((duyarli(3 / 7, 200) * V12) / 1000)} GB`);
console.log(`            yinelenen kimlik 400 bayt -> gunluk ${b(duyarli(VD4, 400))} MB, ` +
  `saklanan ${b((duyarli(VD4, 400) * V12) / 1000)} GB`);
console.log(`ucu birden: 01'in belge deposu 5,76 GB eklenir, 02'nin uc kopyasiyla ` +
  `${b(3 * ((toplamMB * V12) / 1000 + 5.76))} GB`);
```

```
depo                     istek+islem/s  tarama kayit/s  gunluk MB  saklanan GB
teslimat operasyonu             250.00               0     856.00       624.88
ucretlendirme                    41.67          833.33     376.00       274.48
tek depo (federasyonsuz)        250.00          833.33     976.00       712.48

toplam islem 291.67 /s = tek deponun 1.17 kati
gunluk yazilan 1232.00 MB (K01: 976) = 1.26 kat
saklanan 899.36 GB (K01: 712.48) = 1.26 kat
yazma yolunun uzunlugu = 1.375 depo (gonderi 2, durum olayi 1.286)

duyarlilik: VD4 = 3/7 -> gunluk 1320.00 MB, saklanan 963.60 GB
            yinelenen kimlik 400 bayt -> gunluk 1312.00 MB, saklanan 957.76 GB
ucu birden: 01'in belge deposu 5,76 GB eklenir, 02'nin uc kopyasiyla 2715.36 GB
```

Bu satırlar **hesap** sınıfındadır.

**Toplu tarama çevrimiçi depodan çıkıyor.** Teslimat operasyonu deposunun tarama sütunu sıfırdır;
saniyede 833,33 kayıt okuyan dört saatlik iş artık takip sorgusuyla aynı diski, aynı belleği ve aynı
kilit yöneticisini paylaşmıyor. Federasyonun ölçekleme gerekçesi buradadır ve tek bir sayıyla
söylenebilir: **çevrimiçi yolun gördüğü kayıt hızı 833,33'ten sıfıra iniyor.**

**Toplam işlem 1,17 katına çıkıyor.** 250,00 işlem/s'lik tek depo, 250,00 ve 41,67'ye bölünüyor;
fazla olan 41,67 ücretlendirmenin kendi kopyalarını yazmasıdır. Yazma yolunun uzunluğu 1,375 depoya
çıkıyor: gönderi oluşturma iki depoya, durum olayı ortalama 1,286 depoya yazılıyor. Yazma yolunun
uzaması bir dayanıklılık sorusu doğurur — iki depodan biri yazmayı kabul etmezse ne olur — ama o
soru bu kursun konusu değildir.

**`gunluk veri artisi MB` ve `saklanan veri GB` birlikte 1,26 katına çıkıyor.** 976 MB 1232 MB
oluyor, 712,48 GB 899,36 GB oluyor. Artışın kaynağı ikidir ve ayrı ayrı okunabilir: yinelenen
kimlik alanları gönderi kaydını 900 bayttan 1100 bayta çıkarıyor, yinelenen kabul ve teslim
olayları günde 176 MB ekliyor. VD4 üçte bire çıkarsa saklanan veri 963,60 GB, yinelenen kimlik 400
bayta çıkarsa 957,76 GB olur; iki varsayım da aynı büyüklükte etkilidir.

Son satır üç kararın birleşimini veriyor. 01. dersin belge deposu, bu dersin federasyonu ve 02.
dersin üç kopyası birlikte uygulandığında saklanan veri 2715,36 GB'dir — K01'in 712,48 GB'sinin
3,81 katı. **Veri katmanı kararlarının ortak yönü budur: hepsi aynı sayıyı büyütür ve etkileri
çarpışır, toplanmaz.**

#### Özet

- Federasyon veriyi anahtara göre değil işleve göre böler; sınır bağlamın sorularıyla çizilir, aynı
  takip numarası iki depoda da geçebilir.
- Bağlamın kendi soruları kendi deposunda kalıyor, ama iki bağlamın kesişimindeki S4 sorusu 1
  depodan 2 depoya, 3000 kayıttan 6000 kayda çıkıyor ve depolar arasında 3000 anahtar (33 kB)
  taşınıyor.
- Yinelenen 2/7 olay bir çapraz soruyu (S5) tek depoya indiriyor; sınırın doğruluğunun ölçüsü
  çapraz soru sayısıdır.
- Toplu tarama çevrimiçi depodan çıkıyor: teslimat operasyonu deposunun gördüğü tarama hızı
  833,33 kayıt/s'den sıfıra iniyor.
- Toplam işlem 250,00'den 291,67 işlem/s'ye (1,17 kat), yazma yolu 1,375 depoya uzuyor.
- Yineleme K01'in iki büyüme satırını 1,26 katına çıkarıyor (976 → 1232 MB, 712,48 → 899,36 GB);
  01 ve 02'nin kararlarıyla birlikte saklanan veri 2715,36 GB olur.

#### Sonraki Adım

Federasyon deponun işini böldü ama her bağlamın kendi verisi hâlâ bütün olarak tek bir düğümde
duruyor. Teslimat operasyonu deposu 624,88 GB taşıyor ve saniyede 250,00 işlem alıyor; bu sayılar
bağlam bölünerek küçülmez, çünkü artık bölünecek bağlam kalmadı. Kalan tek yol veriyi **kendi
içinde** bölmektir: aynı bağlamın kayıtlarını bir anahtara göre bağımsız düğümlere dağıtmak.
Sonraki ders bu kararı ele alır — anahtar nasıl seçilir, seçim üç erişim örüntüsünün her birinin
dokunduğu düğüm sayısını nasıl değiştirir, en yüklü düğüm ötekilerin kaç katını taşır ve düğüm
sayısı değiştiğinde kaç bayt yer değiştirir.

### Parçalama

Source: https://academia.sh/tr/kurslar/veri-olcekleme/parcalama

Aynı bağlamın verisinin bir anahtara göre bağımsız düğümlere dağıtılması: üç aday parça anahtarının veri dağılımı, sıcak nokta oranı ve erişim örüntüsü başına dokunduğu düğüm sayısıyla karşılaştırılması, en yüklü düğümün taşıdığı veri ve isteğin ölçülmesi, yeniden dengelemede yer değiştiren anahtar oranının taşınan bayta çevrilmesi ve anahtar seçiminin en sık örüntüye göre yapılması.

Federasyon deponun işini böldü ama her bağlamın kendi verisi hâlâ bütün olarak tek bir düğümde
duruyor. Teslimat operasyonu deposu 624,88 GB taşıyor ve saniyede 250,00 işlem alıyor; bu sayılar
bağlam bölünerek küçülmez, çünkü bölünecek bağlam kalmadı. Kalan tek yol veriyi **kendi içinde**
bölmektir.

**Parçalama** (sharding), aynı bağlamın kayıtlarını bir anahtara göre bağımsız düğümlere
dağıtmaktır. İlişkisel Veritabanı Yönetimi kursundaki **bölümleme** ile karıştırılmamalıdır:
bölümleme bir tabloyu aynı motorun içinde parçalara ayırır, parçalama parçaları **ayrı motorlara ve
ayrı makinelere** dağıtır. O kurs parçalamanın uygulamaya dayattığı bedelleri saydı — birleştirme,
benzersizlik ve işlem sınırı, dağıt–topla kalıbının kuyruğu bozması, yeniden dengelemede eşleme
yönteminin taşıma oranını belirlemesi. Burada o bedeller yeniden anlatılmaz. Bu dersin sorusu bir
**ölçekleme kararıdır**: hangi anahtar seçilir ve seçim erişim örüntülerine ne yapar.

#### Anahtar Adayları ve Dağılımları

Gönderi verisinde üç aday vardır ve üçü de gerçek bir sorunun anahtarıdır: **takip numarası**
(P1'in anahtarı), **taşıyıcı** (P2'nin anahtarı) ve **satıcı** (P3'ün anahtarı). Adayları ayıran şey
iki özelliktir — kaç farklı değer aldıkları ve değerlerin ne kadar eşit dağıldığı.

**VD6 — üç aday anahtarın dağılımı.** Takip numarası tekdüzedir ve her gönderi için ayrıdır;
taşıyıcı 12 farklı değer alır ve en büyük taşıyıcının payı 0,35'tir; satıcı 4000 farklı değer alır
ve en büyük yüzde 1'lik dilim (40 satıcı) gönderilerin 0,30'unu üretir. Gerekçe: takip numarası
üretilmiş bir kimliktir; taşıma az sayıda büyük oyuncu arasında bölünür; satıcı hacmi az sayıda
büyük satıcıda yoğunlaşır. Satıcı sayısı K01'in hesabından gelir (günde 4000 fatura satırı = 4000
satıcı), ötekiler bu dersin varsayımıdır ve K01'in tablosuna eklenmez. Duyarlılık aşağıda taşıyıcı
payı 0,50 ile verilir.

Anahtarın düğüme eşlenmesi **tutarlı karma** ile yapılır. Kuralın kendisi ve düğüm sayısı
değiştiğinde yer değiştiren anahtar oranı Trafik Katmanı kursunda ölçüldü; burada yalnız veri
yerleşimi için kullanılıyor ve o ölçüm tekrarlanmıyor.

```js
// parcalama/anahtar.mjs — uc aday parca anahtarinin veri dagilimi, sicak noktasi, dokundugu dugum
// ve yeniden dengeleme maliyeti. Yerlestirme kurali tutarli karmadir; kuralin kendisi ve yer
// degistirme orani M19/K02'de olculdu, burada yalnizca veri yerlesimi icin kullaniliyor. MODELDIR.
const GONDERI = 100_000, PARCA = 8, SANAL = 200;      // model parametreleri
const TASIYICI = 12, TASIYICI_PAY = 0.35;             // VD6
const SATICI = 4000, BUYUK = 40, BUYUK_PAY = 0.30;    // VD6
let s = 20260731 % 2147483647;
const rast = () => (s = (s * 48271) % 2147483647) / 2147483647;

function karma(metin) {                                // FNV-1a; islev M01/K03'te kuruldu
  let h = 2166136261;
  for (let i = 0; i < metin.length; i += 1) h = Math.imul(h ^ metin.charCodeAt(i), 16777619) >>> 0;
  h ^= h >>> 15; h = Math.imul(h, 2246822507) >>> 0; h ^= h >>> 13;
  return h >>> 0;
}
const halka = (n) => {
  const h = [];
  for (let d = 0; d < n; d += 1) for (let v = 0; v < SANAL; v += 1) h.push([karma(`d${d}#${v}`), d]);
  return h.sort((a, b) => a[0] - b[0]);
};
const yerlestir = (anahtar, h) => {
  const c = karma(anahtar);
  if (c > h[h.length - 1][0]) return h[0][1];
  let alt = 0, ust = h.length - 1;
  while (alt < ust) { const o = (alt + ust) >> 1; if (h[o][0] < c) alt = o + 1; else ust = o; }
  return h[alt][1];
};

const kayitlar = [];
for (let i = 0; i < GONDERI; i += 1) kayitlar.push({
  no: `TR-${1_000_000 + i}`,
  tasiyici: `T${rast() < TASIYICI_PAY ? 0 : 1 + Math.floor(rast() * (TASIYICI - 1))}`,
  satici: `S${rast() < BUYUK_PAY ? Math.floor(rast() * BUYUK) : BUYUK + Math.floor(rast() * (SATICI - BUYUK))}`,
});

const ADAY = ["no", "tasiyici", "satici"];
const h8 = halka(PARCA), h9 = halka(PARCA + 1);
const OKUMA = 41.67, YAZMA = 97.22;                   // K01: P1 ve P2'nin depoya ulasan istek/s'si
const GB = 624.88, GUNLUK_MB = 856;                   // 03. ders: teslimat operasyonu deposu
const DOKUNAN = { no: [1, 1, PARCA], tasiyici: [PARCA, 1, PARCA], satici: [PARCA, PARCA, 1] };
const b = (x, n = 2) => x.toFixed(n);

console.log(`model: ${GONDERI.toLocaleString("tr-TR")} gonderi, ${PARCA} parca, ${SANAL} sanal dugum`);
console.log(`\n${"parca anahtari".padEnd(16)}${"farkli anahtar".padStart(15)}${"sicak nokta".padStart(13)}` +
  `${"en yuklu GB".padStart(13)}${"en yuklu islem/s".padStart(18)}${"8->9 oynayan".padStart(14)}${"tasinan GB".padStart(12)}`);
for (const a of ADAY) {
  const y8 = kayitlar.map((k) => yerlestir(k[a], h8)), y9 = kayitlar.map((k) => yerlestir(k[a], h9));
  const say = new Array(PARCA).fill(0);
  for (const d of y8) say[d] += 1;
  const enYuklu = Math.max(...say) / GONDERI, sicak = enYuklu * PARCA;
  const oynayan = y8.filter((d, i) => d !== y9[i]).length / GONDERI;
  console.log(`${a.padEnd(16)}${String(new Set(kayitlar.map((k) => k[a])).size).padStart(15)}` +
    `${b(sicak, 3).padStart(13)}${b(GB * enYuklu).padStart(13)}` +
    `${b((OKUMA + YAZMA) * enYuklu).padStart(18)}${b(oynayan, 4).padStart(14)}${b(GB * oynayan).padStart(12)}`);
}
console.log(`kusursuz dagilimda sicak nokta 1,000; ${PARCA} parcada dugum basina ` +
  `${b(GB / PARCA)} GB ve ${b((OKUMA + YAZMA) / PARCA)} islem/s`);
{
  const y8 = kayitlar.map((k) => yerlestir(k.no, h8)), y9 = kayitlar.map((k) => yerlestir(k.no, h9));
  const oynayan = y8.filter((d, i) => d !== y9[i]).length / GONDERI, tasinan = GB * oynayan * 1000;
  console.log(`takip numarasi anahtarinda tasinan ${b(tasinan / 1000)} GB = teslimat deposunun ` +
    `${b(tasinan / GUNLUK_MB, 1)} gunluk artisi (03. ders: ${GUNLUK_MB} MB/gun)`);
}

console.log(`\n${"parca anahtari".padEnd(16)}${"P1 dugum".padStart(10)}${"P2 dugum".padStart(10)}` +
  `${"P3 dugum".padStart(10)}${"cevrimici dokunus/s".padStart(21)}${"taban kati".padStart(12)}`);
for (const a of ADAY) {
  const [p1, p2, p3] = DOKUNAN[a], dokunus = OKUMA * p1 + YAZMA * p2;
  console.log(`${a.padEnd(16)}${String(p1).padStart(10)}${String(p2).padStart(10)}${String(p3).padStart(10)}` +
    `${b(dokunus).padStart(21)}${b(dokunus / (OKUMA + YAZMA)).padStart(12)}`);
}
console.log(`parcalanmamis depoda cevrimici dokunus ${b(OKUMA + YAZMA)} /s = K01'in depoya ulasan istek/s'si`);

// VD6 duyarliligi: en buyuk tasiyicinin payi 0,35 yerine 0,50 olsaydi (tasiyici alani yeniden uretilir)
{
  const say = new Array(PARCA).fill(0);
  for (const k of kayitlar)
    say[yerlestir(`T${rast() < 0.5 ? 0 : 1 + Math.floor(rast() * (TASIYICI - 1))}`, h8)] += 1;
  const enYuklu = Math.max(...say) / GONDERI;
  console.log(`\nduyarlilik: en buyuk tasiyicinin payi 0,50 olsaydi sicak nokta ` +
    `${b(enYuklu * PARCA, 3)} ve en yuklu dugum ${b(GB * enYuklu)} GB olurdu (tabloda 0,35 ile ` +
    `${b(3.748, 3)} ve ${b(292.78)} GB)`);
}
```

```
model: 100.000 gonderi, 8 parca, 200 sanal dugum

parca anahtari   farkli anahtar  sicak nokta  en yuklu GB  en yuklu islem/s  8->9 oynayan  tasinan GB
no                       100000        1.116        87.14             19.37        0.1193       74.54
tasiyici                     12        3.748       292.78             65.07        0.1182       73.85
satici                     4000        1.148        89.63             19.92        0.1100       68.74
kusursuz dagilimda sicak nokta 1,000; 8 parcada dugum basina 78.11 GB ve 17.36 islem/s
takip numarasi anahtarinda tasinan 74.54 GB = teslimat deposunun 87.1 gunluk artisi (03. ders: 856 MB/gun)

parca anahtari    P1 dugum  P2 dugum  P3 dugum  cevrimici dokunus/s  taban kati
no                       1         1         8               138.89        1.00
tasiyici                 8         1         8               430.58        3.10
satici                   8         8         1              1111.12        8.00
parcalanmamis depoda cevrimici dokunus 138.89 /s = K01'in depoya ulasan istek/s'si

duyarlilik: en buyuk tasiyicinin payi 0,50 olsaydi sicak nokta 4.713 ve en yuklu dugum 368.15 GB olurdu (tabloda 0,35 ile 3.748 ve 292.78 GB)
```

Bu sayılar **hesap** sınıfındadır: belirlenimlidirler, sabit tohumlu bir üreteçten ve sabit bir
yerleştirme kuralından çıkarlar, makineye bağlı değildirler.

#### Sıcak Nokta Anahtarın Kaç Değer Aldığına Bağlı

Birinci tablonun sıcak nokta sütunu üç adayı hemen ayırıyor. **Sıcak nokta** (hot spot), en yüklü
düğümün payının kusursuz paya oranıdır; 1,000 kusursuz dağılımdır.

Takip numarası 1,116 ve satıcı 1,148 veriyor; ikisi de kabul edilebilir. Taşıyıcı 3,748 veriyor: en
yüklü düğüm kusursuz payın neredeyse dört katını taşıyor, 292,78 GB ve saniyede 65,07 işlem. Sekiz
düğümlü bir kümede düğüm başına 78,11 GB ve 17,36 işlem/s beklenirken bir düğüm bunun dört katını
alıyor — parçalama o düğüm için hiç yapılmamış gibidir.

Nedeni anahtarın **kaç farklı değer aldığıdır**. Taşıyıcı yalnız 12 değer alıyor ve en büyüğü tek
başına yükün 0,35'ini taşıyor; hiçbir yerleştirme kuralı bir anahtarı iki düğüme bölemez, çünkü
anahtar bölünmezlik biriminin kendisidir. Satıcı da çarpıktır — 40 satıcı hacmin 0,30'unu üretiyor
— ama 4000 farklı değere yayıldığı için çarpıklık yerleştirmede erir; en büyük satıcının tek başına
payı 0,0075'tir ve tek bir düğümü ele geçiremez. **Kural şudur: sıcak noktayı yaratan çarpıklık
değil, çarpıklığın az sayıda anahtar değerinde toplanmasıdır.** Duyarlılık satırı bunu doğruluyor;
taşıyıcı payı 0,50'ye çıkarsa sıcak nokta 4,713'e ve en yüklü düğüm 368,15 GB'ye çıkıyor.

#### Dokunulan Düğüm Sayısı Örüntüye Göre Değişiyor

İkinci tablo seçimi tersine çeviriyor. Bir istek, parça anahtarını süzemiyorsa bütün düğümlere
gitmek zorundadır.

Takip numarası anahtar olduğunda P1 ve P2 birer düğüme dokunuyor: takip sorgusu numarayı zaten
biliyor, durum olayı da numarayı taşıyor. Çevrimiçi dokunuş hızı 138,89 düğüm-dokunuşu/s'de kalıyor
ve bu, **K01'in `depoya ulasan istek/s` sayısının kendisidir** — parçalama çevrimiçi yola hiçbir
ek dokunuş eklemiyor. Taşıyıcı anahtar olduğunda P1 numaradan taşıyıcıyı bilemediği için sekiz
düğüme birden soruyor ve hız 430,58'e, tabanın 3,10 katına çıkıyor. Satıcı anahtar olduğunda hem P1
hem P2 sekiz düğüme gidiyor: 1111,12 dokunuş/s, tabanın tam sekiz katı.

Üçüncü örüntü tabloyu tamamlıyor. P3 yalnız satıcı anahtarında tek düğümde kalıyor; ötekilerde
sekiz düğüme dağılıyor. Ama P3 günde bir kez koşan ve dört saatlik penceresi olan bir iştir; sekiz
düğüme dağılması onun için bir gecikme sorunu değil, bir eşgüdüm işidir. P1 ve P2 ise saniyede
138,89 istekle sürekli akar ve takip okumasının 200 milisaniyelik eşiği vardır.

Karar bu iki tablonun kesişiminden çıkıyor ve tek bir cümleyle söylenebilir: **parça anahtarı, en
sık gelen ve eşiği en sıkı olan örüntünün süzdüğü alandır.** Takip numarası her iki tabloda da
kazanıyor — sıcak noktası 1,116, çevrimiçi dokunuşu taban değerde. Taşıyıcı P2'yi tek düğümde
tutuyor ama P1'i sekize dağıtıp üstüne 3,748 sıcak nokta getiriyor; satıcı P3'ü kurtarıyor ama
çevrimiçi yolu sekiz katına çıkarıyor.

#### Yeniden Dengelemenin Bedeli

Son iki sütun düğüm eklendiğinde ne olduğunu veriyor. Sekiz parçadan dokuza geçişte takip numarası
anahtarında anahtarların 0,1193'ü yer değiştiriyor — kuramsal alt sınır olan 1/9, yani 0,1111'e
yakın. Oranın kendisi Trafik Katmanı kursunun ölçtüğü şeydi; burada **bayta çevriliyor**: 624,88
GB'lik depoda 74,54 GB.

Sayının büyüklüğü tek başına anlaşılmaz; bir ölçüye bağlanması gerekir. Teslimat operasyonu deposu
günde 856 MB büyüyor (03. ders), yani 74,54 GB o deponun **87,1 günlük artışına** denktir. Bir
düğüm eklemek, üç aylık büyümenin tamamını ağ üzerinden bir kez daha taşımak demektir. Yeniden
dengelemenin planlı bir iş olmasının, kesintisiz yapılabilmesinin ve kendi bant genişliği payını
istemesinin nedeni budur — anlık bir yapılandırma değişikliği değil, veri taşıma işidir.

Taşıyıcı anahtarında oran benzer (0,1182) ama anlamı değişir: 12 anahtardan biri yer değiştirdiğinde
o anahtarın bütün verisi tek seferde taşınır ve taşıma sırasında iki düğüm birden yükün büyük bir
payını taşır. Az sayıda değer alan anahtarın ikinci bedeli budur.

#### Özet

- Parçalama aynı bağlamın kayıtlarını ayrı motorlara dağıtır; tablo bölümlemesi ise aynı motorun
  içinde kalır ve ikisi ayrı kararlardır.
- Sıcak noktayı çarpıklık değil, çarpıklığın az sayıda anahtar değerinde toplanması yaratıyor:
  12 değerli taşıyıcıda sıcak nokta 3,748 (292,78 GB, 65,07 işlem/s), 4000 değerli satıcıda 1,148,
  tekdüze takip numarasında 1,116.
- Parça anahtarını süzemeyen örüntü bütün düğümlere gidiyor: çevrimiçi dokunuş hızı takip numarası
  anahtarında 138,89/s (taban), taşıyıcıda 430,58 (3,10 kat), satıcıda 1111,12 (8,00 kat).
- Anahtar, en sık gelen ve eşiği en sıkı olan örüntünün süzdüğü alandır; dört saatlik penceresi olan
  toplu tarama dağıt–topla maliyetini karşılayabilir, saniyede 138,89 isteklik çevrimiçi yol
  karşılayamaz.
- Sekiz parçada düğüm başına 78,11 GB ve 17,36 işlem/s düşüyor; VD6'da taşıyıcı payı 0,50 olsaydı
  en yüklü düğüm 368,15 GB'ye çıkardı.
- Sekizden dokuza geçişte anahtarların 0,1193'ü yer değiştiriyor ve bu 74,54 GB eder — teslimat
  operasyonu deposunun 87,1 günlük artışı.

#### Sonraki Adım

Parçalama kararı verildi ve anahtar seçildi: takip numarası. Ama bir soru açık kaldı ve bu derste
tek bir yanıtla geçiştirildi — anahtarın **hangi kurala göre** parçalara dağıtılacağı. Burada
tutarlı karma kullanıldı, çünkü Trafik Katmanı kursunda ölçülmüştü; oysa bu tek seçenek değildir.
Anahtar bir aralığa göre bölünebilir, karmasına göre dağıtılabilir ya da anahtardan parçaya eşlemeyi
ayrı bir dizin tablosunda tutmak da bir seçenektir. Üçü aynı veriyi aynı düğümlere farklı biçimlerde
yerleştirir ve üçü yeniden dengelemeyi başka türlü etkiler: birinde aralık taraması tek düğümde
kalır ama yeni kayıtlar hep aynı düğüme düşer, ötekinde dağılım düzelir ama aralık taraması dağılır,
üçüncüsünde eşleme serbestçe değiştirilebilir ama her arama bir dolaylılık katmanından geçer.
Sonraki ders bu üç stratejiyi karşılaştırır.

### Bölümleme Stratejileri

Source: https://academia.sh/tr/kurslar/veri-olcekleme/bolumleme-stratejileri

Aynı anahtarın aralık, karma ve dizin tablosu kurallarıyla parçalara dağıtılması: üç erişim örüntüsünde sıcak nokta oranının, dönemsel taramanın dokunduğu bölüm ile taradığı kayıt sayısının ve erişim başına depo dokunuşunun ölçülmesi, yeniden dengelemede yer değiştiren anahtar oranı ve bölünmeyen en büyük grubun dengeye koyduğu taban.

Parçalama kararı verildi ve anahtar seçiminin sıcak noktayı belirlediği gösterildi. Karar yarım
kaldı. Anahtar seçildikten sonra o anahtarın **hangi kurala göre** parçalara dağıtılacağı ayrı bir
sorudur: aynı takip numarası aralıklara bölünebilir, bir karma işlevinden geçirilebilir ya da her
anahtar grubunun yeri ayrı bir tabloda tutulabilir. Üç kural aynı anahtarla üç ayrı yerleşim
üretir ve yeniden dengelemeyi üç ayrı biçimde etkiler.

**Bölümleme stratejisi** (partitioning strategy), bölüm anahtarını bölüm numarasına çeviren
kuraldır. İlişkisel Veritabanı Yönetimi kursundaki Tablo Bölümleme dersi aralık, liste ve karma
biçimlerinin mekaniğini tek bir motorun içinde kurdu; Parçalama Kalıpları dersi aynı bölümleri
ayrı depolara dağıttı. Bu derste mekanik yeniden anlatılmaz — sorulan şey seçimdir: aynı üç
erişim örüntüsünde hangi kural hangi sayıyı ne kadar oynatıyor. Terim ayrımı bir cümlede durur:
bölümleme tek bir deponun içinde tabloyu parçalara ayırır, parçalama o parçaları ayrı depolara
koyar; buradaki kural ikisinde de aynı kuraldır, sonucu farklı yerde görünür.

#### Üç Kural

**Aralık bölümlemesinde** (range partitioning) bölüm, anahtarın düştüğü değer aralığıdır. Takip
numarası oluşturma sırasına göre artan verildiğinden aralık ekseni zaman eksenidir: ardışık
numaralar aynı bölüme düşer.

**Karma bölümlemesinde** (hash partitioning) bölüm numarası anahtarın karmasından çıkar. Komşu
anahtarlar birbirinden uzağa düşer; yerleşimi seçmek diye bir şey yoktur. Önceki dersin kullandığı
tutarlı karma bu ailenin bir üyesidir ve yerleşim açısından aynı davranır.

**Dizin tablosunda** (lookup table) anahtar grubundan bölüm numarasına eşleme ayrı bir tabloda
tutulur. Kural aritmetik değil veridir: yerleşim seçilebilir, tek grup için değiştirilebilir ve
bir grup gerektiğinde ikiye bölünebilir. Burada grup satıcıdır ve takip numarası satıcı önekini
taşır, böylece tekil okuma da dönemsel tarama da aynı eşlemeyi kullanır. Bedel de aynı yerden
gelir — her erişim önce eşlemeyi okumak zorundadır.

Ölçüm için üç yeni varsayım gerekir. Bunlar Sistem Tasarımına Giriş kursundaki varsayım tablosuna
**eklenmez**; bu dersin kendi varsayımlarıdır.

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| D1 | satıcı hacim dağılımının üssü | 0,9 | 4000 satıcının (K01: günlük fatura satırı) payı $i^{-0{,}9}$ ile azalır; az sayıda büyük satıcı, uzun bir kuyruk |
| D2 | bölüm sayısı | 8 | başlangıç kapasitesi; duyarlılığı 16 bölümle ölçülür |
| D3 | etkin pencere | 5 gün | okuma ve yazmanın düştüğü gönderi yaşı: teslim edilmiş gönderi ne sorulur ne güncellenir |

Üç erişim örüntüsü K01'den gelir: takip numarasına göre tekil okuma, taşıyıcıdan gelen durum
olayının yazılması ve satıcının otuz günlük dönem raporu. İlk ikisi D3 nedeniyle aynı gönderi
kümesine düşer, bu yüzden aynı dağılımı paylaşır.

```js
// bolum/strateji.mjs — ayni anahtarin uc ayri kuralla bolumlere dagitilmasi.
// MODEL: bolumler birer sayactir, gercek bir kume degildir; sonuclar belirlenimlidir.
export const GUNLUK = 400_000, SAKLAMA = 730, DONEM = 30;   // V3, V12, V11
export const SATICI = 4000, USS = 0.9, PENCERE = 5;         // D1, D3
export const DEPO_ISTEK = 138.89, SAKLANAN_GB = 712.48;     // K01 hesaplari

export function paylar(uss = USS, kat = 1) {
  const p = []; let H = 0;
  for (let i = 1; i <= SATICI; i++) { const a = Math.pow(i, -uss) * (i === 1 ? kat : 1); p.push(a); H += a; }
  return p.map((a) => a / H);
}
export const karma = (n) => {
  let z = Math.imul(n ^ (n >>> 16), 0x21f0aaad) >>> 0;
  z = Math.imul(z ^ (z >>> 15), 0x735a2d97) >>> 0;
  return (z ^ (z >>> 15)) >>> 0;
};
export const oran = (k) => Math.max(...k) / (k.reduce((a, b) => a + b, 0) / k.length);

export function ilkAtama(grup, P) {           // hacme gore azalan sirada en az yuklu bolume
  const yuk = new Array(P).fill(0), yer = new Map();
  for (const x of [...grup].sort((a, b) => b.agirlik - a.agirlik)) {
    let en = 0; for (let p = 1; p < P; p++) if (yuk[p] < yuk[en]) en = p;
    yer.set(x.ad, en); yuk[en] += x.agirlik;
  }
  return yer;
}
export const yukHesap = (grup, yer, P) => {
  const y = new Array(P).fill(0);
  for (const x of grup) y[yer.get(x.ad)] += x.agirlik;
  return y;
};

export function istekAralik(P) {              // bolum = takip numarasinin dustugu numara araligi
  const dilim = (SAKLAMA * GUNLUK) / P, k = new Array(P).fill(0);
  for (let g = SAKLAMA - PENCERE; g < SAKLAMA; g++)
    for (let s = 0; s < GUNLUK; s += 1000) k[Math.floor((g * GUNLUK + s) / dilim)] += 1000;
  return k;
}
export function istekKarma(P) {               // bolum = takip numarasinin karmasi
  const k = new Array(P).fill(0);
  for (let g = SAKLAMA - PENCERE; g < SAKLAMA; g++)
    for (let s = 0; s < GUNLUK; s++) k[karma(g * GUNLUK + s) % P] += 1;
  return k;
}

export function tablo(P) {
  const grup = paylar().map((a, i) => ({ ad: `${i}`, agirlik: a }));
  const D = yukHesap(grup, ilkAtama(grup, P), P);
  const enYuklu = Math.max(...D) / D.reduce((a, b) => a + b, 0);
  return [
    ["aralik", oran(istekAralik(P)), 1, DONEM * GUNLUK, 1],
    ["karma", oran(istekKarma(P)), P, DONEM * GUNLUK, 1],
    ["dizin tablosu", oran(D), 1, Math.round(DONEM * GUNLUK * enYuklu), 2],
  ];
}

if (import.meta.filename === process.argv[1]) {
  const pay = paylar();
  console.log(`D1: ${SATICI} satici, en buyugun payi %${(pay[0] * 100).toFixed(2)}, ` +
    `ilk sekiz %${(pay.slice(0, 8).reduce((a, b) => a + b, 0) * 100).toFixed(2)}, ` +
    `en kucuk gunde ${(GUNLUK * pay[SATICI - 1]).toFixed(1)} gonderi`);
  for (const P of [8, 16]) {
    console.log(`\nP = ${P} bolum`);
    console.log("strateji         istek sicak nokta  donem raporu bolum  taranan kayit  depo dokunusu");
    for (const [ad, i, b, t, d] of tablo(P))
      console.log(ad.padEnd(17) + i.toFixed(2).padStart(17) + String(b).padStart(20) +
        t.toLocaleString("tr-TR").padStart(15) + String(d).padStart(15));
  }
  console.log(`\nK01 donusu: depoya ulasan ${DEPO_ISTEK} istek/s, saklanan ${SAKLANAN_GB} GB, P = 8`);
  for (const [ad, i, , , d] of tablo(8))
    console.log(`  ${ad.padEnd(15)} en yuklu bolum ${(DEPO_ISTEK * i / 8).toFixed(2).padStart(6)} istek/s` +
      ` ve ${(SAKLANAN_GB / 8).toFixed(2)} GB | depoya ulasan toplam ${(DEPO_ISTEK * d).toFixed(2)} istek/s`);
}
```

```
D1: 4000 satici, en buyugun payi %7.41, ilk sekiz %21.92, en kucuk gunde 17.0 gonderi

P = 8 bolum
strateji         istek sicak nokta  donem raporu bolum  taranan kayit  depo dokunusu
aralik                        8.00                   1     12.000.000              1
karma                         1.00                   8     12.000.000              1
dizin tablosu                 1.00                   1      1.500.137              2

P = 16 bolum
strateji         istek sicak nokta  donem raporu bolum  taranan kayit  depo dokunusu
aralik                       16.00                   1     12.000.000              1
karma                         1.00                  16     12.000.000              1
dizin tablosu                 1.19                   1        889.566              2

K01 donusu: depoya ulasan 138.89 istek/s, saklanan 712.48 GB, P = 8
  aralik          en yuklu bolum 138.89 istek/s ve 89.06 GB | depoya ulasan toplam 138.89 istek/s
  karma           en yuklu bolum  17.42 istek/s ve 89.06 GB | depoya ulasan toplam 138.89 istek/s
  dizin tablosu   en yuklu bolum  17.36 istek/s ve 89.06 GB | depoya ulasan toplam 277.78 istek/s
```

Sayılar hesap sınıfındadır: D1–D3 ile K01'in varsayımlarından aritmetikle çıkarlar, hiçbiri
ortama bağlı değildir.

#### Tabloda Kimse Kazanmıyor

Sıcak nokta sütunu en yüklü bölümün yükünü bölüm başına ortalamaya böler; 1,00 tam denge, 8,00 da
sekiz bölümün yalnız birinin çalışması demektir.

**Aralık bölümlemesi bölünmüş bir depoda hiçbir şeyi bölmüyor.** Oran 8,00. Neden D3'tedir:
okunan ve yazılan gönderiler son beş günün gönderileridir, ardışık takip numaraları aynı aralığa
düştüğü için hepsi son bölüme iner. Diğer yedi bölüm 725 günlük veriyi taşıyor ve hemen hiç istek
görmüyor. Saklama sütunu bunu doğruluyor: saklanan 712,48 GB sekiz bölüme eşit bölünmüş, bölüm
başına 89,06 GB. **Aralık bölümlemesi depolamayı dengeler, isteği dengelemez** — ve K01'in
oynatmak istediği satır istek satırıdır.

**Karma bölümlemesi isteği dengeliyor, sorguyu dağıtıyor.** Oran 1,00: on iki milyon takip
numarasının karması sekiz bölüme neredeyse eşit düşüyor. Bedel dönem raporu sütununda: satıcının
kayıtları sekiz bölüme dağıldığı için otuz günlük rapor sekiz bölüme birden dokunuyor. Toplam
taranan kayıt aynı kalıyor (12.000.000), ama sorgu Parçalama Kalıpları dersinde ölçülen dağıt–topla
davranışına giriyor.

**Dizin tablosu ikisini de veriyor, karşılığında bir dokunuş alıyor.** Satıcılar hacme göre
azalan sırada en az yüklü bölüme yerleştirildiğinde oran 1,00 çıkıyor ve bir satıcının bütün
kayıtları tek bölümde kaldığı için dönem raporu bir bölüme dokunuyor. Taranan kayıt 12.000.000
yerine 1.500.137: taranan pencere aynı otuz gün, ama o bölümde yalnız oraya atanmış satıcıların
kayıtları var. Sekiz kat azalma buradan gelir.

Bedel son sütunda duruyor. Her erişim önce eşlemeyi okuyor, sonra veriye gidiyor: erişim başına
iki depo dokunuşu. K01'in `depoya ulasan istek/s` satırı bu yüzden 138,89'dan 277,78'e çıkıyor.
İki sayı birlikte okunmalıdır: en yüklü bölümün yükü 138,89'dan 17,36'ya inerken depoya ulaşan
toplam istek iki katına çıktı. Üstelik dizin tablosunun kendisi bu isteğin tamamını görüyor, yani
sekiz bölümün yükünü dengelemek için dokuzuncu bir sıcak nokta kuruldu. Bu tablonun bellekte
tutulup tutulmayacağı bir önbellek kararıdır ve sonraki konunun sorusudur.

P = 16 satırları duyarlılığı veriyor. Aralığın oranı 16,00'ya çıkıyor: bölüm eklemek sıcak noktayı
büyütüyor. Dizin tablosunun taranan kaydı 889.566'ya inerken oranı 1,19'a çıkıyor, çünkü en büyük
satıcının payı yüzde 7,41 ve bu, bir bölümün payı olan yüzde 6,25'in üstünde.

#### Yeniden Dengeleme

Yerleşim bir kez kurulup bırakılmaz. Bir satıcı büyüdüğünde üç strateji üç ayrı şey yapar.

```js
// bolum/dengele.mjs — en buyuk satici uc katina ciktiginda uc stratejinin davranisi.
// MODEL: yeniden dengeleyici, mevcut yerlesimden baslayip en yuklu bolumden en az yukluye
// tek grup tasir; tasinan agirlik toplanir. Sonuclar belirlenimlidir.
import { paylar, ilkAtama, yukHesap, oran, istekAralik, istekKarma, SATICI, DONEM }
  from "./strateji.mjs";
const P = 8, KAT = 3, HEDEF = 1.20;

function enAzTasima(grup, yer0, hedef) {
  const yer = new Map(yer0), y = yukHesap(grup, yer, P);
  let tasinan = 0, adim = 0;
  while (oran(y) > hedef) {
    let h = 0, c = 0;
    for (let p = 1; p < P; p++) { if (y[p] > y[h]) h = p; if (y[p] < y[c]) c = p; }
    const aday = grup.filter((x) => yer.get(x.ad) === h && y[c] + x.agirlik < y[h])
      .sort((a, b) => b.agirlik - a.agirlik)[0];
    if (aday === undefined) break;
    yer.set(aday.ad, c); y[h] -= aday.agirlik; y[c] += aday.agirlik;
    tasinan += aday.agirlik; adim += 1;
  }
  return { yer, oran: oran(y), tasinan, adim };
}
const bicim = (r) => `oran ${r.oran.toFixed(3)} | tasinan anahtar %${(r.tasinan * 100).toFixed(2)}` +
  ` | ${r.adim} tasima`;

const once = paylar(), sonra = paylar(0.9, KAT);
const gSonra = sonra.map((a, i) => ({ ad: `${i}`, agirlik: a }));
const yer0 = ilkAtama(once.map((a, i) => ({ ad: `${i}`, agirlik: a })), P);
console.log(`en buyuk satici payi %${(once[0] * 100).toFixed(2)} -> %${(sonra[0] * 100).toFixed(2)}`);
const [oA, oK] = [oran(istekAralik(P)), oran(istekKarma(P))];   // ikisi de saticidan bagimsiz
console.log(`aralik: oran ${oA.toFixed(2)} -> ${oA.toFixed(2)} | tasima yazma sicak noktasini kaldirmaz`);
console.log(`karma:  oran ${oK.toFixed(2)} -> ${oK.toFixed(2)} | bolum takip numarasindan cikar`);
console.log(`dizin tablosu, dokunulmazsa: oran ${oran(yukHesap(gSonra, yer0, P)).toFixed(3)}`);
console.log(`dizin tablosu, grup = satici:     ${bicim(enAzTasima(gSonra, yer0, HEDEF))}`);

const gBol = [];                                  // yalniz sicak satici gun gruplarina bolunur
for (let g = 0; g < DONEM; g++) gBol.push({ ad: `0/${g}`, agirlik: sonra[0] / DONEM });
for (let i = 1; i < SATICI; i++) gBol.push({ ad: `${i}`, agirlik: sonra[i] });
const yerBol = new Map(yer0);
for (let g = 0; g < DONEM; g++) yerBol.set(`0/${g}`, yer0.get("0"));
const r = enAzTasima(gBol, yerBol, HEDEF);
const dagilim = new Set();
for (let g = 0; g < DONEM; g++) dagilim.add(r.yer.get(`0/${g}`));
console.log(`dizin tablosu, grup = satici-gun: ${bicim(r)}`);
console.log(`  bedeli: sicak saticinin donem raporu ${dagilim.size} bolume dokunuyor (digerleri 1),` +
  ` dizin satiri ${SATICI} -> ${gBol.length}`);

console.log("\nD1 duyarliligi: dagilim ussu buyudukce bolunmeyen en buyuk grup bir taban koyar");
for (const uss of [0.9, 1.1, 1.3]) {
  const p = paylar(uss);
  console.log(`  uss ${uss.toFixed(1)}: en buyuk satici payi %${(p[0] * 100).toFixed(2)},` +
    ` ulasilabilir en iyi oran ${Math.max(1, p[0] * P).toFixed(2)}`);
}
```

```
en buyuk satici payi %7.41 -> %19.37
aralik: oran 8.00 -> 8.00 | tasima yazma sicak noktasini kaldirmaz
karma:  oran 1.00 -> 1.00 | bolum takip numarasindan cikar
dizin tablosu, dokunulmazsa: oran 1.904
dizin tablosu, grup = satici:     oran 1.549 | tasinan anahtar %4.43 | 468 tasima
dizin tablosu, grup = satici-gun: oran 1.181 | tasinan anahtar %9.04 | 14 tasima
  bedeli: sicak saticinin donem raporu 8 bolume dokunuyor (digerleri 1), dizin satiri 4000 -> 4029

D1 duyarliligi: dagilim ussu buyudukce bolunmeyen en buyuk grup bir taban koyar
  uss 0.9: en buyuk satici payi %7.41, ulasilabilir en iyi oran 1.00
  uss 1.1: en buyuk satici payi %16.07, ulasilabilir en iyi oran 1.29
  uss 1.3: en buyuk satici payi %27.36, ulasilabilir en iyi oran 2.19
```

Karma satırında satıcı üç katına çıksa da oran 1,00 kalıyor, çünkü bölüm satıcıdan değil takip
numarasından çıkıyor. Karma bölümlemesi satıcı kaynaklı sıcak nokta üretmez — ama dönem raporunu
da hiçbir zaman tek bölüme indiremez. Aralık satırında taşımanın anlamı yok: sıcak nokta belirli
bir satıcıdan değil zaman ekseninden geliyor.

Dizin tablosunun üç satırı asıl ölçümdür. Hiçbir şey yapılmazsa oran 1,904 oluyor. En büyük satıcı
olduğu gibi taşınmaya çalışıldığında 468 taşımadan ve anahtarların yüzde 4,43'ünden sonra oran
ancak 1,549'a iniyor ve orada duruyor: o bölümde artık tek başına yüzde 19,37'lik bir satıcı var,
bölünmediği sürece sekiz bölümün ortalamasının 1,549 katı olmaktan çıkamaz. **Bölünmeyen en büyük
grup dengenin tabanıdır**; duyarlılık satırları aynı şeyi üç dağılım için tekrar ediyor: üs 1,3'te
en büyük satıcı tek başına yüzde 27,36 tutuyor ve ulaşılabilecek en iyi oran 2,19.

Tabanı düşürmenin tek yolu grubu yeniden tanımlamaktır. Sıcak satıcı satıcı-gün gruplarına
bölündüğünde 14 taşımayla oran 1,181'e iniyor. Taşınan anahtar oranı yüzde 9,04, yani ilk
denemenin iki katı; ama sonuç ilk denemede ulaşılamayan bir yerde. Bedeli son satır yazıyor:
sıcak satıcının dönem raporu artık sekiz bölüme dokunuyor, yani o satıcı için dizin tablosunun
bütün kazancı geri verildi. Diğer 3999 satıcı için rapor hâlâ tek bölüme dokunuyor. Karar bu
yüzden şudur: **dizin tablosu dengeyi seçici olarak kurar; bedeli yalnız bölünen grubun sorgusuna
yazılır.** Parçalama Kalıpları dersi parça sayısı değiştiğinde taşınan oranı ölçmüştü; buradaki
soru ayrıdır — parça sayısı sabitken yerleşimin düzeltilmesi.

#### Özet

- Bölümleme stratejisi anahtarı bölüme çeviren kuraldır; anahtar seçimiyle aynı şey değildir ve
  aynı anahtar üç kuralla üç ayrı yerleşim üretir.
- Aralık bölümlemesi 712,48 GB'ı sekiz bölüme eşit dağıttı ama isteği hiç bölmedi: sıcak nokta
  oranı 8,00, en yüklü bölüm K01'in 138,89 istek/s'sinin tamamını görüyor.
- Karma bölümlemesi oranı 1,00'a indirdi (bölüm başına 17,42 istek/s) ama satıcının dönem raporunu
  sekiz bölüme birden dağıttı.
- Dizin tablosu hem oranı 1,00'a indirdi hem raporu tek bölümde tuttu ve taranan kaydı
  12.000.000'dan 1.500.137'ye düşürdü; karşılığında erişim başına iki depo dokunuşu istedi ve
  `depoya ulasan istek/s` 138,89'dan 277,78'e çıktı.
- Bölünmeyen en büyük grup dengeye taban koyar: en büyük satıcı üç katına çıktığında satıcı
  düzeyinde en iyi oran 1,549'da kaldı, grup satıcı-güne bölününce yüzde 9,04 anahtar taşınarak
  1,181'e indi ve o satıcının raporu sekiz bölüme dağıldı.

#### Sonraki Adım

Üç stratejinin ortak bir sessizliği var. Hepsi verinin **nereye** konacağını çözüyor, hiçbiri
verinin **hangi biçimde** durduğunu sormuyor. Dizin tablosunun taradığı 1.500.137 kayıt, satıcının
raporunu üretmek için okunan kayıt sayısıdır ve bu sayının bir bölümü şemanın kendisinden gelir:
bir raporu tamamlamak için gönderi kaydının yanına tarife, bölge ve sözleşme kayıtları da
getirilmek zorundaysa okuma yolu tek bir bölümde bile birden çok tabloya uzanır. Sonraki ders o
yolu kısaltmayı ele alır: okunan kaydın yanına neyin kopyalanacağını, kopyanın okumada kaç tabloyu
ve kaç kaydı düşürdüğünü, karşılığında yazma yolunun kaç adıma uzadığını ve saklanan 712,48 GB ile
günlük 976 MB artışın ne kadar büyüdüğünü sayar.

### Denormalizasyon

Source: https://academia.sh/tr/kurslar/veri-olcekleme/denormalizasyon

Okuma yolunun bir ölçekleme kararı olarak kısaltılması: dar ve geniş kopyanın ikincil tablolardan çekilen kayıt sayısını sıfıra indirmesi, dokunulan tablo sayısının neden yanıltıcı bir ölçü olduğu, kopyanın gönderi kaydına eklediği baytın gerçek bir motorda ölçülmesi ve kopyanın kaynağı izlemek zorunda olduğu kapsamın depodaki yazma/okuma oranını 2,33'ten kaça çıkardığı.

Bölümleme stratejisi verinin nereye konacağını çözdü, hangi biçimde durduğunu sormadı. Dizin
tablosunun taradığı 1.500.137 kayıt bir sorunun yanıtını üretmek için okunan kayıt sayısıydı ve o
sayının hepsi gönderi tablosundan gelmiyor. Satıcının fatura satırı ağırlığı tarifeyle, tarifeyi
bölge katsayısıyla ve sonucu sözleşme indirimiyle çarpar; üç çarpan üç ayrı tabloda durur. Okuma
yolu tek bir bölümün içinde bile birden çok tabloya uzanır, bölümler ayrı depolara dağıtılmışsa
bölüm sınırını da aşar.

**Denormalizasyon** (denormalization), normalleştirilmiş bir şemaya okuma başarımı için bilinçli
olarak tekrar eklemektir. Tanım, normal biçimler ve türetilmiş sütunun tutarlılık kuralı Veri
Modelleme ve İlişkisel Kuram kursundaki Denormalizasyon dersinde kuruldu; kaynağı değiştiren her
yazma yolunun kopyayı doldurmak zorunda olduğu ve kaynaktan yeniden hesaplayan bir denetim işinin
gerektiği orada gösterildi. Burada o kurallar yeniden anlatılmaz. Bu dersin sorusu ölçekleme
sorusudur: kopyanın okumadan **kaç kayıt** düşürdüğü, gönderi kaydına **kaç bayt** eklediği ve
kaynağı izlemek zorunda kaldığında K01'in yazma/okuma oranını **kaça** çıkardığı.

#### Dar Kopya ve Geniş Kopya

Kopyanın genişliği ayrı bir karardır. **Dar kopya** yalnız hesap için gereken sayıları taşır:
tarife oranı, bölge katsayısı, sözleşme indirimi. **Geniş kopya** raporun gösterdiği adları da
taşır: tarife adı ve bölge adı. İkisi de aynı okumayı hedefler, ama sakladıkları ve izlemek
zorunda kaldıkları farklıdır.

Ölçüm gerçek bir motorda yapılır. Model ölçeği 01. dersin ölçeğidir — 20 satıcı, 30 gün, günde
100 gönderi — böylece sayılar o dersin sayılarıyla karşılaştırılabilir. Ölçülen üç şey vardır:
sorgunun **ikincil tablolardan çektiği kayıt sayısı**, gönderi tablosunun **kapladığı bayt** ve
bir kaynak alanı değiştiğinde **yeniden yazılan satır sayısı**.

```js
// sema/kopya.mjs — ayni fatura sorgusunun uc semada cektigi kayit, kapladigi bayt ve
// bir kaynak degistiginde yeniden yazilan satir sayisi. node:sqlite ile gercek bir motor
// kosar; SQL ogretilmez, olculen sey kayit sayisi ile bayttir.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, TARIFE = 40, BOLGE = 12;   // 01. dersin model olcegi
const tarifeAd = (i) => `TARIFE-${String(i).padStart(3, "0")} ekspres yurt ici`;
const bolgeAd = (i) => `BOLGE-${String(i).padStart(2, "0")} bati anadolu`;
const EK = { normal: "", dar: ", oran REAL, katsayi REAL, indirim REAL",
  genis: ", oran REAL, katsayi REAL, indirim REAL, tarife_ad TEXT, bolge_ad TEXT" };

function kur(sema) {
  const db = new DatabaseSync(":memory:");
  db.exec(`CREATE TABLE tarife(id INTEGER PRIMARY KEY, ad TEXT, oran REAL);
CREATE TABLE bolge(id INTEGER PRIMARY KEY, ad TEXT, katsayi REAL);
CREATE TABLE sozlesme(satici INTEGER PRIMARY KEY, indirim REAL);
CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INTEGER, gun INTEGER,
  tarife_id INTEGER, bolge_id INTEGER, agirlik REAL${EK[sema]});
CREATE INDEX gonderi_satici ON gonderi(satici, gun);`);
  db.exec("BEGIN");
  for (let i = 0; i < TARIFE; i += 1)
    db.prepare("INSERT INTO tarife VALUES(?,?,?)").run(i, tarifeAd(i), 12 + (i % 7) * 0.5);
  for (let i = 0; i < BOLGE; i += 1)
    db.prepare("INSERT INTO bolge VALUES(?,?,?)").run(i, bolgeAd(i), 1 + (i % 5) * 0.1);
  for (let i = 0; i < SATICI; i += 1)
    db.prepare("INSERT INTO sozlesme VALUES(?,?)").run(i, (i % 9) * 0.01);
  const alan = { normal: 6, dar: 9, genis: 11 }[sema];
  const g = db.prepare(`INSERT INTO gonderi VALUES(${"?,".repeat(alan - 1)}?)`);
  for (let s = 0; s < SATICI; s += 1) for (let d = 0; d < GUN; d += 1)
    for (let k = 0; k < GUNLUK; k += 1) {
      const ti = (s * 7 + d + k) % TARIFE, bi = (s + k) % BOLGE;
      const t = [`TR-${s}-${d}-${k}`, s, d, ti, bi, 1 + (k % 30)];
      const n = [12 + (ti % 7) * 0.5, 1 + (bi % 5) * 0.1, (s % 9) * 0.01];
      g.run(...(sema === "normal" ? t : sema === "dar" ? [...t, ...n]
        : [...t, ...n, tarifeAd(ti), bolgeAd(bi)]));
    }
  db.exec("COMMIT");
  return db;
}

const SORGU = {                       // saticinin 30 gunluk fatura satiri, tarife kirilimiyla
  normal: `SELECT t.ad, sum(g.agirlik * say(t.oran) * say(b.katsayi) * (1 - say(s.indirim)))
    FROM gonderi g JOIN tarife t ON t.id = g.tarife_id JOIN bolge b ON b.id = g.bolge_id
    JOIN sozlesme s ON s.satici = g.satici
    WHERE g.satici = ? AND g.gun BETWEEN 0 AND 29 GROUP BY t.ad`,
  dar: `SELECT say(t.ad), x.tutar FROM (SELECT tarife_id,
      sum(agirlik * oran * katsayi * (1 - indirim)) AS tutar FROM gonderi
      WHERE satici = ? AND gun BETWEEN 0 AND 29 GROUP BY tarife_id) x
    JOIN tarife t ON t.id = x.tarife_id`,
  genis: `SELECT tarife_ad, sum(agirlik * oran * katsayi * (1 - indirim)) FROM gonderi
    WHERE satici = ? AND gun BETWEEN 0 AND 29 GROUP BY tarife_ad`,
};

console.log(`model: ${SATICI * GUN * GUNLUK} gonderi, ${SATICI} satici, ${GUN} gun, ` +
  `${TARIFE} tarife, ${BOLGE} bolge; fatura satiri tarife kirilimlidir`);
const olcu = {};
let taban = 0;
for (const sema of ["normal", "dar", "genis"]) {
  const db = kur(sema);
  let sayac = 0;
  db.function("say", (x) => { sayac += 1; return x; });
  db.prepare(SORGU[sema]).all(10);
  const plan = db.prepare(`EXPLAIN QUERY PLAN ${SORGU[sema]}`).all()
    .flatMap((r) => r.detail.match(/(?:SCAN|SEARCH) \w+/g) ?? []);
  const bayt = db.prepare("SELECT sum(pgsize) AS b FROM dbstat WHERE name LIKE 'gonderi%'").get().b;
  if (taban === 0) taban = bayt;
  const say = (s, ...a) => db.prepare(s).run(...a).changes;
  const gosterim = say("UPDATE tarife SET ad = 'TARIFE-003 yeni ad' WHERE id = 3") +
    (sema === "genis" ? say("UPDATE gonderi SET tarife_ad = 'TARIFE-003 yeni ad' WHERE tarife_id = 3") : 0);
  const hesap = say("UPDATE sozlesme SET indirim = 0.05 WHERE satici = 10") +
    (sema === "normal" ? 0 : say("UPDATE gonderi SET indirim = 0.05 WHERE satici = 10"));
  olcu[sema] = { tablo: new Set(plan).size, ikincil: sayac, bayt,
    ek: (bayt - taban) / (SATICI * GUN * GUNLUK), gosterim, hesap };
}
const yaz = (baslik, alanlar) => {
  console.log(`\n${baslik.padEnd(26)}${["normal", "dar", "genis"].map((s) => s.padStart(12)).join("")}`);
  for (const [ad, f, b] of alanlar)
    console.log(ad.padEnd(26) + ["normal", "dar", "genis"]
      .map((s) => (b ? b(olcu[s][f]) : String(olcu[s][f])).padStart(12)).join(""));
};
yaz("okuma yolu", [["dokunulan tablo", "tablo"], ["ikincil tablodan kayit", "ikincil"],
  ["gonderi tablosu bayt", "bayt", (x) => x.toLocaleString("tr-TR")],
  ["gonderi basina ek bayt", "ek", (x) => x.toFixed(2)]]);
yaz("yazma yolu (satir)", [["tarife adi degisimi", "gosterim"], ["sozlesme indirimi degisimi", "hesap"]]);
console.log(`\nana tablodan okunan kayit uc semada da ${GUN * GUNLUK}`);
```

```
model: 60000 gonderi, 20 satici, 30 gun, 40 tarife, 12 bolge; fatura satiri tarife kirilimlidir

okuma yolu                      normal         dar       genis
dokunulan tablo                      4           3           1
ikincil tablodan kayit            9000          40           0
gonderi tablosu bayt         2.437.120   3.641.344   6.709.248
gonderi basina ek bayt            0.00       20.07       71.20

yazma yolu (satir)              normal         dar       genis
tarife adi degisimi                  1           1        1498
sozlesme indirimi degisimi           1        3001        3001

ana tablodan okunan kayit uc semada da 3000
```

Bu sayılar **ölçüm** sınıfındadır: bu makinede koşan bir motordan gelirler, belirlenimlidirler,
ama sayfa doluluğu ve plan seçimi motorun kendi kararıdır.

#### Dokunulan Tablo Yanlış Ölçü

İlk satır tek başına okunduğunda dar kopya işe yaramaz görünüyor: dokunulan tablo dörtten üçe
iniyor, üç hâlâ birden büyük. İkinci satır bunu düzeltiyor. Normalleştirilmiş şemada motor
ikincil tablolardan **9000 kayıt** çekiyor — üç bin gönderinin her biri için tarife, bölge ve
sözleşme satırı ayrı ayrı aranıyor. Dar kopyada aynı sayı **40**: hesap tek tablodan bitiyor,
tarife adları toplamadan **sonra** ve tarife kırılımı başına bir kez aranıyor. Yani dar kopya
dokunulan tablo sayısını bir azalttı, çekilen kayıt sayısını 225 kat azalttı.

Sonuç bir okuma kuralıdır: **bir okumanın maliyeti dokunduğu tablo sayısıyla değil, ikincil
tablolardan çektiği kayıt sayısıyla ölçülür.** Küçük ve seyrek değişen bir tabloya kırılım başına
bir kez bakmak, satır başına bir kez bakmakla aynı şey değildir.

Bu ayrım bölünmüş bir katmanda niteliğini değiştirir. Bölümleme Stratejileri dersinde bölümler
ayrı depolara dağıtıldı; normalleştirilmiş şemada o 9000 aramanın her biri, tarife ve bölge
tabloları her bölüme kopyalanmadıysa bölüm sınırını aşan bir erişimdir. Dar kopya bu geçişleri
sıfıra indirir ve geriye kalan 40 arama, bölüm başına bir kez kopyalanmış küçük bir tablodan
karşılanabilir.

Geniş kopya çekilen kaydı 40'tan 0'a indiriyor. Kazanç 40 kayıttır; bedeli iki satır aşağıda
duruyor.

#### Yazma Yolu Nerede Uzuyor

Alt tablo iki kaynak değişimini ayırıyor ve ikisi aynı şey değil.

**Tarife adının değişmesi** bir gösterim alanıdır. Normalleştirilmiş ve dar şemada tek satır
güncellenir. Geniş kopyada 1498 satır güncellenir, çünkü ad altmış bin gönderinin ilgili
kırılımına kopyalanmıştır. Geniş kopyanın 40 kayıtlık okuma kazancının bedeli budur.

**Sözleşme indiriminin değişmesi** bir hesap alanıdır ve iki kopyalı şemada da 3001 satır
güncellenir. Fark burada şemadan değil **sözleşmeden** gelir: indirim geriye dönük mü uygulanacak.
Ücretlendirmede geçmiş fatura dondurulur — kesilmiş bir fatura sonradan değişmez — bu yüzden
kopya bir **anlık görüntü** olarak tanımlanabilir ve o tanımla yayılım sıfıra iner. Aynı kopya
kaynağı izlemek zorundaysa yayılım kaydın saklandığı süre boyunca sürer. Kopyanın hangisi olduğu
şemaya bakarak anlaşılmaz; yazılı bir karardır.

#### Hesaba Geri Dönüş

Ölçülen ek bayt ve yayılım oranları K01'in satırlarına çevrilir. Yayılımın kapsamı için bir
varsayım gerekir ve bu varsayım K01'in tablosuna **eklenmez**.

**D5 — günlük kaynak alanı değişimi: 1 tarife, 5 sözleşme.** Gerekçe: tarife ve sözleşme alanları
ticari mutabakata bağlıdır ve gün içinde seyrek değişir. Duyarlılığı on katıyla verilir.

```js
// sema/bedel.mjs — olculen ek baytlarin ve yazma yayiliminin K01 hesabindaki satirlara etkisi
const V3 = 400_000, V4 = 7, V6 = 220, V7 = 900, V12 = 730;   // K01 varsayimlari
const ARTIS = 976, SAKLAMA = 712.48, ORAN = 2.33;            // K01 hesaplari
const GUN = 86_400;
const OKUMA = (V3 * 30 * 0.1) / GUN;                         // K01: onbellek arkasi okuma, ortalama
const YAZMA = (V3 * V4) / GUN;                               // K01: durum olayi yazma, ortalama
const EK = { normal: 0, dar: 20.07, genis: 71.20 };          // sema/kopya.mjs olcumu
const TARIFE_PAYI = 1497 / 60_000;                           // sema/kopya.mjs: bir tarifeye bagli oran
const SATICI_GUNLUK = V3 / 4000;                             // K01: gunluk fatura satiri 4000
const D5 = { tarife: 1, sozlesme: 5 };                       // bu dersin varsayimi

const artis = (ek) => (V3 * (V7 + ek) + V3 * V4 * V6) / 1e6;
console.log(`ortalama depo okuma ${OKUMA.toFixed(2)}/s, yazma ${YAZMA.toFixed(2)}/s, ` +
  `oran ${(YAZMA / OKUMA).toFixed(2)} (K01: ${ORAN})`);
console.log(`\n${"sema".padEnd(8)}${"kayit bayt".padStart(12)}${"gunluk MB".padStart(12)}` +
  `${"kat".padStart(7)}${"saklanan GB".padStart(13)}${"kat".padStart(7)}`);
for (const [ad, ek] of Object.entries(EK)) {
  const a = artis(ek), s = (a * V12) / 1000;
  console.log(ad.padEnd(8) + (V7 + ek).toFixed(2).padStart(12) + a.toFixed(2).padStart(12) +
    (a / ARTIS).toFixed(3).padStart(7) + s.toFixed(2).padStart(13) + (s / SAKLAMA).toFixed(3).padStart(7));
}

console.log(`\ngenis kopyada kaynak degisiminin yayilimi (D5: gunde ${D5.tarife} tarife, ` +
  `${D5.sozlesme} sozlesme alani)`);
console.log(`${"yayilim kapsami".padEnd(18)}${"tarife kayit".padStart(14)}${"sozlesme kayit".padStart(16)}` +
  `${"yeniden yazma/s".padStart(17)}${"yazma/okuma".padStart(13)}`);
for (const [ad, g] of [["gecmise, 730 gun", V12], ["doneme, 30 gun", 30], ["acik gonderi, 5 gun", 5]]) {
  const t = g * V3 * TARIFE_PAYI, s = g * SATICI_GUNLUK;
  const yeniden = (D5.tarife * t + D5.sozlesme * s) / GUN;
  console.log(ad.padEnd(18) + Math.round(t).toLocaleString("tr-TR").padStart(14) +
    Math.round(s).toLocaleString("tr-TR").padStart(16) + yeniden.toFixed(2).padStart(17) +
    ((YAZMA + yeniden) / OKUMA).toFixed(2).padStart(13));
}
const kapsam = 30, t = kapsam * V3 * TARIFE_PAYI, s = kapsam * SATICI_GUNLUK;
for (const kat of [1, 10]) {
  const y = kat * (D5.tarife * t + D5.sozlesme * s) / GUN;
  console.log(`D5 duyarliligi (30 gun, ${kat} kat): yeniden yazma ${y.toFixed(2)}/s, ` +
    `yazma/okuma ${((YAZMA + y) / OKUMA).toFixed(2)}`);
}
```

```
ortalama depo okuma 13.89/s, yazma 32.41/s, oran 2.33 (K01: 2.33)

sema      kayit bayt   gunluk MB    kat  saklanan GB    kat
normal        900.00      976.00  1.000       712.48  1.000
dar           920.07      984.03  1.008       718.34  1.008
genis         971.20     1004.48  1.029       733.27  1.029

genis kopyada kaynak degisiminin yayilimi (D5: gunde 1 tarife, 5 sozlesme alani)
yayilim kapsami     tarife kayit  sozlesme kayit  yeniden yazma/s  yazma/okuma
gecmise, 730 gun       7.285.400          73.000            88.55         8.71
doneme, 30 gun           299.400           3.000             3.64         2.60
acik gonderi, 5 gun        49.900             500             0.61         2.38
D5 duyarliligi (30 gun, 1 kat): yeniden yazma 3.64/s, yazma/okuma 2.60
D5 duyarliligi (30 gun, 10 kat): yeniden yazma 36.39/s, yazma/okuma 4.95
```

İlk satır bir denetimdir: ortalama hızlardan çıkan yazma/okuma oranı 2,33, yani K01'in tepe
hızlardan çıkardığı oranın aynısı. İki hız da aynı tepe çarpanını taşıdığı için oran değişmiyor
ve bu, sonraki satırların K01'le karşılaştırılabilir olduğunu gösteriyor.

**Saklama bedeli küçük.** Dar kopya gönderi kaydını 900 bayttan 920,07 bayta çıkarıyor: günlük
artış 976 MB'den 984,03 MB'ye, saklanan veri 712,48 GB'den 718,34 GB'ye. Geniş kopyada bile
oran 1,029. Denormalizasyonun pahalı olduğu yer disk değildir.

**Yazma bedeli kapsama bağlı ve aralık çok geniş.** Kopya geçmişe yayılıyorsa bir tarife
değişimi 7.285.400 kaydı yeniden yazdırır; D5 ile günlük yeniden yazma hızı 88,55 kayıt/s olur
ve `depoda yazma/okuma orani` 2,33'ten **8,71**'e çıkar. Aynı kopya yalnız dönem kayıtlarını
izliyorsa oran 2,60, yalnız açık gönderileri izliyorsa 2,38. **Kopyanın genişliği okuma kazancını
belirler, kapsamı yazma bedelini belirler ve ikisi ayrı kararlardır.**

Duyarlılık satırları kapsamın sıklıktan daha belirleyici olduğunu söylüyor: D5 on katına
çıktığında otuz günlük kapsamda oran 2,60'tan 4,95'e gidiyor, ama geçmişe yayılan kapsam tek
katta bile 8,71'de. Sıklığı yanlış tahmin etmek kapsamı yanlış seçmekten daha ucuzdur.

#### Özet

- Denormalizasyon burada bir ölçekleme kararıdır: ölçüsü kopyanın okumadan düşürdüğü kayıt,
  kayda eklediği bayt ve kaynağı izlediği kapsamdır.
- Bir okumanın maliyeti dokunulan tablo sayısıyla ölçülmez: dar kopya tablo sayısını dörtten üçe
  indirirken ikincil tablolardan çekilen kaydı 9000'den 40'a indirdi.
- Bölünmüş bir katmanda o 9000 arama bölüm sınırını aşan erişimdir; dar kopya onları sıfırlar,
  kalan 40 arama bölüm başına kopyalanmış küçük bir tablodan karşılanır.
- Saklama bedeli küçüktür: gönderi kaydı 900 bayttan 920,07 (dar) ve 971,20 bayta (geniş)
  çıkıyor, saklanan veri 712,48 GB'den 718,34 ve 733,27 GB'ye.
- Asıl bedel yayılımın kapsamındadır: `depoda yazma/okuma orani` geçmişe yayılan kopyada
  2,33'ten 8,71'e, dönem kapsamında 2,60'a, açık gönderi kapsamında 2,38'e çıkıyor.
- Geniş kopya bir gösterim alanı değiştiğinde 1498 satır yazdırıyor; anlık görüntü olarak
  tanımlanan kopyada aynı değişim tek satırda kalır.

#### Sonraki Adım

Kopya, okumanın çektiği kayıt sayısını düşürdü ama okumanın **taradığı** kayıt sayısını hiç
değiştirmedi: satıcının fatura satırı hâlâ üç bin gönderiyi tek tek okuyup topluyor, K01
ölçeğinde bu dört saatlik pencerede saniyede 833,33 kayıttır. Toplamın kendisi her istekte
yeniden yapılıyor, oysa aynı dönemin aynı toplamı gün içinde değişmiyor. Sonraki ders bu
tekrarı ele alır: toplamın önceden hesaplanıp saklanması, sorgunun taradığı kaydın kaça indiği,
saklanan sonucun tazelenmesinin kaça mal olduğu ve tazeleme aralığının kabul edilen gerilik
penceresini nasıl belirlediği.

### Somutlaştırılmış Görünümler

Source: https://academia.sh/tr/kurslar/veri-olcekleme/somutlastirilmis-gorunumler

Tekrarlanan bir toplamın önceden hesaplanıp saklanması: sonucun taneciğinin taradığı kayıt sayısını belirlemesi, artımlı tazelemenin tam yeniden hesaba göre maliyeti, saklama bedelinin neden ihmal edilebilir kaldığı ve tazeleme aralığının bayatlık penceresi ile depodaki yazma/okuma oranı arasında kurduğu ödünleşim.

Kopya, okumanın çektiği kayıt sayısını düşürdü ama taradığı kayıt sayısına dokunmadı. Satıcının
fatura satırı hâlâ üç bin gönderiyi tek tek okuyup topluyor; K01 ölçeğinde bu, dört saatlik
pencerede saniyede 833,33 kayıttır. Toplam her istekte yeniden yapılıyor, oysa kapanmış bir günün
toplamı bir daha değişmiyor. Aynı aritmetik günde bir kez yapılıp saklanabilir.

**Somutlaştırılmış görünüm** (materialized view), bir sorgunun sonucunun hesaplanıp saklanmasıdır.
SQL Temelleri kursundaki Görünümler dersinde kurulan görünüm veri tutmaz, sorguyu saklar ve her
çağrıldığında yeniden çalışır; somutlaştırılmış görünüm **sonucu** tutar, bu yüzden okuması ucuz,
doğruluğu ise zamana bağlıdır. Bu ders sorgu dili öğretmez; ölçtüğü şey sorgunun taradığı kayıt
sayısı, tazelemenin maliyeti ve saklanan sonucun ne kadar geride kalabildiğidir.

#### Taneciğin Belirlediği Şey

Saklanacak sonuç bir seçim gerektirir: hangi düzeyde toplanacak. **Tanecik** kaba olduğunda satır
sayısı düşer, taranan kayıt azalır, ama sonuç daha az soruya yanıt verir. İki tanecik ölçülür:
satıcı-gün-tarife ve satıcı-gün. Kaynak, gönderi taneciğidir.

Model ölçeği yine 01. dersin ölçeğidir. Bir satıcının günde 100 gönderisi ve sekiz ayrı tarifesi
vardır; sekiz sayısı model seçimidir, gerçek bir ölçüm değil.

```js
// gorunum/tazele.mjs — ayni raporun kaynaktan ve iki ayri tanecikteki somutlastirilmis
// gorunumden okunmasi. node:sqlite ile gercek bir motor kosar; olculen sey taranan kayit,
// saklanan bayt ve tazelemenin tarayip yazdigi satir sayisidir.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, TARIFE = 40, TARIFE_GUN = 8;   // 01. dersin olcegi
const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INTEGER, gun INTEGER,
  tarife_id INTEGER, tutar REAL);
CREATE INDEX gonderi_satici ON gonderi(satici, gun);
CREATE INDEX gonderi_gun ON gonderi(gun);
CREATE TABLE ozet_gun_tarife(satici INTEGER, gun INTEGER, tarife_id INTEGER,
  kayit INTEGER, tutar REAL, PRIMARY KEY(satici, gun, tarife_id));
CREATE TABLE ozet_gun(satici INTEGER, gun INTEGER, kayit INTEGER, tutar REAL,
  PRIMARY KEY(satici, gun));`);
db.exec("BEGIN");
const g = db.prepare("INSERT INTO gonderi VALUES(?,?,?,?,?)");
for (let s = 0; s < SATICI; s += 1) for (let d = 0; d < GUN; d += 1)
  for (let k = 0; k < GUNLUK; k += 1)
    g.run(`TR-${s}-${d}-${k}`, s, d, (s * 7 + d * 3 + (k % TARIFE_GUN)) % TARIFE, 12 + (k % 30) * 0.5);
db.exec("COMMIT");

let sayac = 0;
db.function("say", (x) => { sayac += 1; return x; });
const olc = (sql, ...a) => { sayac = 0; const r = db.prepare(sql).all(...a); return [sayac, r.length]; };

const TAZELE = {                        // artimli tazeleme: yalniz verilen gunun kayitlari
  ozet_gun_tarife: `INSERT OR REPLACE INTO ozet_gun_tarife
    SELECT satici, gun, tarife_id, count(*), sum(tutar) FROM gonderi
    WHERE gun = ? GROUP BY satici, gun, tarife_id`,
  ozet_gun: `INSERT OR REPLACE INTO ozet_gun
    SELECT satici, gun, count(*), sum(tutar) FROM gonderi WHERE gun = ? GROUP BY satici, gun`,
};
db.exec("BEGIN");
for (let d = 0; d < GUN; d += 1) for (const s of Object.values(TAZELE)) db.prepare(s).run(d);
db.exec("COMMIT");

const RAPOR = {
  gonderi: ["gonderi | tanecik: gonderi",
    `SELECT tarife_id, sum(say(tutar)) FROM gonderi
     WHERE satici = ? AND gun BETWEEN 0 AND 29 GROUP BY tarife_id`],
  ozet_gun_tarife: ["ozet | tanecik: satici-gun-tarife",
    `SELECT tarife_id, sum(say(tutar)) FROM ozet_gun_tarife
     WHERE satici = ? AND gun BETWEEN 0 AND 29 GROUP BY tarife_id`],
  ozet_gun: ["ozet | tanecik: satici-gun",
    `SELECT sum(say(tutar)) FROM ozet_gun WHERE satici = ? AND gun BETWEEN 0 AND 29`],
};
const bayt = (t) => db.prepare("SELECT sum(pgsize) AS b FROM dbstat WHERE name = ?").get(t).b;
const satirSayisi = (t) => db.prepare(`SELECT count(*) AS n FROM ${t}`).get().n;

console.log(`model: ${SATICI * GUN * GUNLUK} gonderi, ${SATICI} satici, ${GUN} gun, ` +
  `${TARIFE} tarife, satici-gun basina ${TARIFE_GUN} tarife`);
console.log(`\n${"okunan tablo".padEnd(34)}${"satir".padStart(8)}${"taranan".padStart(9)}` +
  `${"donen".padStart(7)}${"kat".padStart(8)}${"bayt".padStart(10)}${"bayt/satir".padStart(12)}`);
let taban = 0;
for (const [tablo, [ad, sql]] of Object.entries(RAPOR)) {
  const [taranan, donen] = olc(sql, 10);
  if (taban === 0) taban = taranan;
  const n = satirSayisi(tablo), b = bayt(tablo);
  console.log(ad.padEnd(34) + n.toLocaleString("tr-TR").padStart(8) + String(taranan).padStart(9) +
    String(donen).padStart(7) + (taban / taranan).toFixed(2).padStart(8) +
    b.toLocaleString("tr-TR").padStart(10) + (b / n).toFixed(1).padStart(12));
}

console.log(`\n${"tazeleme".padEnd(18)}${"artimli taranan".padStart(16)}${"artimli yazilan".padStart(16)}` +
  `${"tam taranan".padStart(13)}${"tam yazilan".padStart(13)}`);
for (const [tablo, sql] of Object.entries(TAZELE)) {
  const yazilan = db.prepare(sql).run(GUN - 1).changes;
  console.log(tablo.padEnd(18) + String(SATICI * GUNLUK).padStart(16) + String(yazilan).padStart(16) +
    String(SATICI * GUN * GUNLUK).padStart(13) + String(satirSayisi(tablo)).padStart(13));
}
```

```
model: 60000 gonderi, 20 satici, 30 gun, 40 tarife, satici-gun basina 8 tarife

okunan tablo                         satir  taranan  donen     kat      bayt  bayt/satir
gonderi | tanecik: gonderi          60.000     3000     40    1.00 1.761.280        29.4
ozet | tanecik: satici-gun-tarife    4.800      240     40   12.50    94.208        19.6
ozet | tanecik: satici-gun             600       30      1  100.00    16.384        27.3

tazeleme           artimli taranan artimli yazilan  tam taranan  tam yazilan
ozet_gun_tarife               2000             160        60000         4800
ozet_gun                      2000              20        60000          600
```

Bu sayılar **ölçüm** sınıfındadır; bayt sütunu sayfa doluluğuna bağlıdır ve satır sayısı küçükken
satır başına düşen bayt olduğundan büyük görünür.

Üç satır bir gradyan veriyor. Rapor kaynaktan okunduğunda 3000 kayıt taranıyor; satıcı-gün-tarife
taneciğinde 240, satıcı-gün taneciğinde 30. Kabalaştıkça taranan kayıt 12,5 ve 100 kat düşüyor.

Dördüncü sütun bedeli söylüyor. Satıcı-gün-tarife taneciği raporun kırılımını koruyor: kaynakla
aynı 40 satır dönüyor. Satıcı-gün taneciğinde dönen satır 1'e iniyor — o özet tarife kırılımını
**taşımıyor**, dolayısıyla "hangi tarifeden ne kadar" sorusu ona sorulamaz. **Tanecik seçimi bir
başarım ayarı değil, yanıtlanabilir soruların kümesini daraltan bir tasarım kararıdır.**

Alt tablo tazelemenin iki biçimini ayırıyor. **Artımlı tazeleme** yalnız değişen günün kayıtlarını
tarar: 2000 kayıt okur, satıcı-gün-tarife için 160, satıcı-gün için 20 satır yazar. **Tam yeniden
hesap** her seferinde 60.000 kaydı tarar ve özetin tamamını yazar. Aradaki oran taranan tarafta
otuz kat, yazılan tarafta otuz kat; ve bu oran saklama süresiyle birlikte büyür, çünkü artımlı
tazelemenin taradığı kayıt gün sayısına bağlı değildir.

#### Hesaba Geri Dönüş

Ölçülen tanecik oranları ve satır başına baytlar K01'in satırlarına çevrilir. Ölçekleme
doğrudandır: satıcı-gün taneciğinin dönem satır sayısı 4000 satıcı çarpı 30 gün, yani K01'in
`donem satici-gun` olarak zaten hesapladığı 120.000 satırdır.

```js
// gorunum/pencere.mjs — olculen tanecik oranlarinin K01 hesabindaki satirlara etkisi ve
// tazeleme araliginin bayatlik penceresi ile yazma yuku arasindaki degisimi. Hepsi aritmetiktir.
const V3 = 400_000, V10 = 4, V12 = 730, DONEM = 30;          // K01 varsayimlari
const SATICI = 4000, TARIFE_GUN = 8;                         // K01: gunluk fatura satiri; model olcumu
const TARAMA = 833.33, OKUNAN_GB = 10.80, SAKLANAN = 712.48; // K01 hesaplari
const GUN = 86_400, PENCERE = V10 * 3600;
const OKUMA = (V3 * DONEM * 0.1) / GUN, YAZMA = (V3 * 7) / GUN;   // K01 ortalamalari
const BAYT = { "satici-gun-tarife": 19.6, "satici-gun": 27.3 };   // gorunum/tazele.mjs olcumu

const donem = { gonderi: DONEM * V3, "satici-gun-tarife": SATICI * DONEM * TARIFE_GUN,
  "satici-gun": SATICI * DONEM };
console.log(`${"okunan tablo".padEnd(20)}${"donem satiri".padStart(14)}${"tarama kayit/s".padStart(16)}` +
  `${"kat".padStart(8)}${"okunan MB".padStart(12)}${"saklama GB".padStart(12)}${"kat".padStart(8)}`);
for (const [ad, n] of Object.entries(donem)) {
  const bayt = BAYT[ad] ?? (OKUNAN_GB * 1e9) / donem.gonderi;
  const saklama = ad === "gonderi" ? SAKLANAN : SAKLANAN + (n / DONEM) * V12 * bayt / 1e9;
  console.log(ad.padEnd(20) + n.toLocaleString("tr-TR").padStart(14) +
    (n / PENCERE).toFixed(2).padStart(16) + (donem.gonderi / n).toFixed(2).padStart(8) +
    ((n * bayt) / 1e6).toFixed(2).padStart(12) + saklama.toFixed(2).padStart(12) +
    (saklama / SAKLANAN).toFixed(4).padStart(8));
}
console.log(`K01 tabani: toplu is tarama ${TARAMA} kayit/s, okunan ${OKUNAN_GB} GB, ` +
  `saklanan ${SAKLANAN} GB`);

console.log(`\ntazeleme araligi ile bayatlik penceresi ve yazma yuku (artimli tazeleme)`);
console.log(`${"aralik".padEnd(10)}${"bayatlik".padStart(11)}` +
  `${"satici-gun yaz/s".padStart(18)}${"oran".padStart(7)}` +
  `${"satici-gun-tarife yaz/s".padStart(25)}${"oran".padStart(7)}`);
for (const [ad, T] of [["gunde bir", GUN], ["saatte bir", 3600], ["5 dakika", 300], ["1 dakika", 60]]) {
  const hucre = [SATICI, SATICI * TARIFE_GUN].map((satir) => {
    const y = satir / T;
    return [y.toFixed(2), ((YAZMA + y) / OKUMA).toFixed(2)];
  });
  const sure = T >= 3600 ? `${T / 3600} sa` : `${T / 60} dk`;
  console.log(ad.padEnd(10) + sure.padStart(11) + hucre[0][0].padStart(18) +
    hucre[0][1].padStart(7) + hucre[1][0].padStart(25) + hucre[1][1].padStart(7));
}
console.log(`K01 tabani: ortalama yazma ${YAZMA.toFixed(2)}/s, okuma ${OKUMA.toFixed(2)}/s, oran ` +
  `${(YAZMA / OKUMA).toFixed(2)}`);
for (const T of [GUN, 300]) {
  const tam = (GUN / T) * donem.gonderi;
  console.log(`  ${T === GUN ? "gunde bir" : "5 dakika"} tazelemede gunluk taranan kayit: ` +
    `artimli ${V3.toLocaleString("tr-TR")}, tam yeniden hesap ${tam.toLocaleString("tr-TR")}`);
}
```

```
okunan tablo          donem satiri  tarama kayit/s     kat   okunan MB  saklama GB     kat
gonderi                 12.000.000          833.33    1.00    10800.00      712.48  1.0000
satici-gun-tarife          960.000           66.67   12.50       18.82      712.94  1.0006
satici-gun                 120.000            8.33  100.00        3.28      712.56  1.0001
K01 tabani: toplu is tarama 833.33 kayit/s, okunan 10.8 GB, saklanan 712.48 GB

tazeleme araligi ile bayatlik penceresi ve yazma yuku (artimli tazeleme)
aralik       bayatlik  satici-gun yaz/s   oran  satici-gun-tarife yaz/s   oran
gunde bir       24 sa              0.05   2.34                     0.37   2.36
saatte bir       1 sa              1.11   2.41                     8.89   2.97
5 dakika         5 dk             13.33   3.29                   106.67  10.01
1 dakika         1 dk             66.67   7.13                   533.33  40.73
K01 tabani: ortalama yazma 32.41/s, okuma 13.89/s, oran 2.33
  gunde bir tazelemede gunluk taranan kayit: artimli 400.000, tam yeniden hesap 12.000.000
  5 dakika tazelemede gunluk taranan kayit: artimli 400.000, tam yeniden hesap 3.456.000.000
```

Üst tablo K01'in en büyük okuma kalemini dağıtıyor. Gün sonu işi kaynaktan okuduğunda saniyede
833,33 kayıt tarıyor ve 10.800 MB okuyor; satıcı-gün özetinden okuduğunda saniyede 8,33 kayıt
tarıyor ve 3,28 MB okuyor. Bant genişliği tarafındaki karşılık K01'in en çarpıcı bulgusunu tersine
çeviriyor: orada gün sonu işi tepe okuma çıkışının 3,75 katı bant genişliği istiyordu, burada o
kalem üç binde bire iniyor.

Saklama sütunu bedeli neredeyse yok gösteriyor. Yedi yüz otuz gün boyunca tutulan satıcı-gün özeti
saklanan veriyi 712,48 GB'den 712,56 GB'ye çıkarıyor, satıcı-gün-tarife özeti 712,94 GB'ye. İkisi
de binde birin altında. **Önceden hesaplanmış sonucun bedeli diskte değildir.**

#### Bayatlık Penceresi

Alt tablo bedelin nerede olduğunu söylüyor. Saklanan sonuç, tazelenene kadar geride kalır ve bu
gecikmeye **bayatlık penceresi** denir; artımlı tazelemede pencere, tazeleme aralığının kendisidir.

Aralık kısaldıkça pencere küçülüyor ve yazma yükü büyüyor. Satıcı-gün taneciğinde günde bir
tazeleme `depoda yazma/okuma orani`nı 2,33'ten 2,34'e çıkarıyor — ölçülemeyecek kadar küçük bir
bedel. Beş dakikada bir tazelemede oran 3,29, dakikada bir tazelemede 7,13. Daha ince tanecikte
aynı eğri daha diktir: satıcı-gün-tarife özeti beş dakikada bir tazelendiğinde oran 10,01, dakikada
bir tazelendiğinde 40,73. **İnce tanecik okumayı az kazandırır, tazelemeyi çok pahalılaştırır.**

Karar K01'in bir varsayımından çıkıyor. V10, gün sonu işinin penceresini dört saat olarak
koyuyor: satıcı raporu sabah isteniyor. Yirmi dört saatlik bir bayatlık penceresi bu gereksinimi
karşılar, çünkü rapor zaten kapanmış günleri kapsar. Ücretlendirme için doğru aralık günde birdir
ve bedeli oranın 2,33'ten 2,34'e çıkmasıdır. Aynı özetin satıcı panosunda **canlı** gösterilmesi
istenirse pencere dakikalara iner ve bedel oranın üç ila yedi katına çıkmasıdır; bu, aynı özetin
iki ayrı kullanımıdır ve tek bir tazeleme aralığıyla ikisi birden karşılanmaz.

Son iki satır tazeleme biçiminin sınırını çiziyor. Artımlı tazeleme aralık ne olursa olsun günde
400.000 kayıt tarar — her gönderi bir kez. Tam yeniden hesap günde bir çalıştığında 12.000.000,
beş dakikada bir çalıştığında 3.456.000.000 kayıt tarar. **Tazeleme aralığını kısaltmak ancak
tazeleme artımlıysa bir seçenektir.**

#### Özet

- Somutlaştırılmış görünüm sorguyu değil sonucu saklar; saklanan sorgu her çağrıldığında yeniden
  çalışır, saklanan sonuç ise tazelenene kadar geride kalır.
- Tanecik yanıtlanabilir soruları belirler: satıcı-gün-tarife özeti taranan kaydı 3000'den 240'a
  indirip kırılımı korudu, satıcı-gün özeti 30'a indirdi ama tarife sorusunu yanıtlayamaz oldu.
- K01'e dönüş: gün sonu işinin taraması 833,33 kayıt/s ve 10.800 MB'den 8,33 kayıt/s ve 3,28 MB'ye
  iniyor, yani yüz kat.
- Saklama bedeli ihmal edilebilir: 730 günlük özet saklanan veriyi 712,48 GB'den 712,56 GB'ye
  çıkarıyor.
- Bedel tazelemededir: `depoda yazma/okuma orani` günde bir tazelemede 2,33'ten 2,34'e, beş
  dakikada bir tazelemede 3,29'a, dakikada bir tazelemede 7,13'e çıkıyor; ince tanecikte aynı
  aralıklar 2,36, 10,01 ve 40,73 veriyor.
- Aralığı kısaltmak yalnız artımlı tazelemede seçenektir: artımlı tazeleme günde 400.000 kayıt
  tararken tam yeniden hesap beş dakikalık aralıkta 3.456.000.000 kayıt tarar.

#### Sonraki Adım

Bu konunun bütün kararları tek bir varsayımı korudu: veri, satırları ve sütunları olan bir tabloda
duruyor. Ölçülen üç örüntü aynı depo biçiminde karşılandı — takip numarasına göre tekil okuma,
durum olayının eklenmesi ve dönemsel tarama. Oysa bu üç örüntünün istediği şey birbirinden çok
farklıdır: biri tek anahtarla tek kayıt, biri sürekli ekleme, biri geniş bir aralık üzerinde
toplam. Sonraki ders aynı üç örüntüyü dört ayrı depo türünde ölçer: anahtar–değer, belge, geniş
sütun ve çizge deposunda her örüntünün dokunduğu kayıt sayısı ile gereken sorgu sayısı ne oluyor
ve bir örüntüyü ucuzlatan yapı hangi örüntüyü pahalılaştırıyor.

### Depo Türleri

Source: https://academia.sh/tr/kurslar/veri-olcekleme/depo-turleri

Anahtar–değer, belge, geniş sütun ve çizge deposunun aynı erişim örüntüleriyle ölçülmesi: her türün bir örüntü için gerektirdiği sorgu sayısı ile dokunduğu kayıt sayısı, bir örüntüyü ucuzlatan yapının hangi örüntüyü pahalılaştırdığı ve seçimin giriş kursundaki depo istek hızı ile yazma/okuma oranına ödettiği bedel.

Buraya kadarki bütün kararlar satırları ve sütunları olan tek bir depo biçimi üzerinde verildi.
Oysa ölçülen örüntülerin istediği şeyler birbirinden çok farklıdır: takip sorgusu tek anahtarla
tek kayıt ister, durum olayı sürekli ekleme yapar, dönem taraması geniş bir aralıkta toplam alır.
Bir depo türü bu işlerden birini yapısına yazdığında ötekileri uygulamaya bırakır ve bu ders o
alışverişi sayar.

Ailelerin tanımı — anahtar–değer, belge, geniş sütun ve çizge deposunun neyi bırakıp neyi verdiği
— Veri Modelleme ve İlişkisel Kuram kursunda kuruldu; ilişkisel ile ilişkisel olmayan seçimi ve
kayıt sisteminin tek kalması kuralı bu konunun ilk dersinde ölçüldü. İkisi de tekrarlanmaz.
Buradaki soru dardır: **her tür, her örüntü için kaç sorgu ister ve kaç kayda dokunur.**

#### Türleri Ayıran Şey

Bir depo türünü, yerel olarak yanıtlayabildiği erişim biçimi tanımlar. Yanıtlayamadığı her biçim
uygulamaya düşer ve uygulamaya düşen her adım ayrı bir depo isteğidir.

**Anahtar–değer deposu** yalnız birincil anahtarı bilir. Değerin içi kendisi için anlamsızdır,
bu yüzden ikincil erişim uygulamanın tuttuğu bir anahtar listesiyle yapılır ve listedeki her
kayıt ayrı bir istek olur.

**Belge deposu** değerin alanlarını görür ve bir alan üzerinde ikincil dizin kurabilir. İkincil
bir süzme tek istekte biter.

**Geniş sütun deposu** satırları bölüm anahtarına göre gruplar ve grup içinde sıralı bir aralığı
tek istekte okur. Farklı bir anahtarla süzme, aynı verinin ikinci bir bölüm anahtarıyla ikinci
kez yazılmasını gerektirir.

**Çizge deposu** düğüm ve kenarları birinci sınıf nesne yapar; bir dolaşma depo içinde yürür ve
istemciye tek istek olarak görünür.

Ölçüm için üç örüntünün yanına dördüncüsü konur, çünkü ilk üçünün hiçbiri çizge deposunun
kazandığı yerde durmaz. **P4 — rota dolaşması:** bir aktarma merkezi durduğunda o merkezden geçen
gönderilerin bulunması, sonraki duraklarının çıkarılması ve o duraklardan geçen gönderilere kadar
üç adım ilerlenmesi. Teslimat operasyonunun sorduğu bir sorudur ve bir kaydın kendisiyle değil
kayıtlar arasındaki bağla ilgilidir.

```js
// tur/erisim.mjs — ayni erisim oruntulerinin dort depo turunde gerektirdigi sorgu ve
// dokundugu kayit. MODEL: depolar birer nesnedir, sayaclar aciktir; bir urun degil,
// erisim yeteneklerinin modelidir. Sonuclar belirlenimlidir.
const GONDERI = 60_000, SATICI = 20, MERKEZ = 8;   // 01. dersin olcegi: satici basina 3000
const gonderi = Array.from({ length: GONDERI }, (_, i) => ({
  no: `TR-${i}`, satici: i % SATICI,
  rota: [0, 1, 2].map((j) => (i * 3 + j) % MERKEZ),
}));
const saticiDizin = new Map(), merkezDizin = new Map(), rotaSonraki = new Map();
for (const g of gonderi) {
  if (!saticiDizin.has(g.satici)) saticiDizin.set(g.satici, []);
  saticiDizin.get(g.satici).push(g);
  for (let j = 0; j < g.rota.length; j += 1) {
    const m = g.rota[j];
    if (!merkezDizin.has(m)) merkezDizin.set(m, []);
    merkezDizin.get(m).push(g);
    if (j + 1 < g.rota.length) {
      if (!rotaSonraki.has(m)) rotaSonraki.set(m, new Set());
      rotaSonraki.get(m).add(g.rota[j + 1]);
    }
  }
}

class Depo {                       // her cagri bir depo istegidir; dokunulan kayit sayilir
  constructor() { this.sorgu = 0; this.kayit = 0; }
  iste(n) { this.sorgu += 1; this.kayit += n; return n; }
}

// P1: takip numarasina gore tekil okuma (durum + son uc rota adimi)
// P2: tasiyicidan gelen bir durum olayinin yazilmasi
// P3: bir saticinin donem taramasi
// P4: bir merkezden baslayip H adim ilerleyen rota dolasmasi
const H = 3;   // dolasma derinligi
const ORUNTU = {
  "anahtar-deger": {                        // yalniz birincil anahtar; ikincil dizin uygulamada
    P1: (d) => d.iste(1),                                   // deger butun halinde gelir
    P2: (d) => { d.iste(1); d.iste(1); },                   // oku, sonra butun degeri yaz
    P3: (d) => { const n = saticiDizin.get(3).length; d.iste(1);   // once dizin anahtari
      for (let i = 0; i < n; i += 1) d.iste(1); },                     // sonra kayit basina bir istek
    P4: (d, derinlik) => { let m = new Set([0]);
      for (let h = 0; h < derinlik; h += 1) { const s = new Set();
        for (const x of m) { for (const g of merkezDizin.get(x)) d.iste(1);
          for (const y of rotaSonraki.get(x) ?? []) s.add(y); d.iste(1); }
        m = s; } },
  },
  "belge": {                                // belge alanlari uzerinde ikincil dizin
    P1: (d) => d.iste(1),
    P2: (d) => d.iste(1),                                   // belgenin olay dizisine ekleme
    P3: (d) => d.iste(saticiDizin.get(3).length),
    P4: (d, derinlik) => { let m = new Set([0]);
      for (let h = 0; h < derinlik; h += 1) { const s = new Set(); let k = 0;
        for (const x of m) { k += merkezDizin.get(x).length;
          for (const y of rotaSonraki.get(x) ?? []) s.add(y); }
        d.iste(k); m = s; } },
  },
  "genis sutun": {                          // bolum anahtari + bolum icinde sirali aralik
    P1: (d) => d.iste(3),                                   // son uc olay satiri
    P2: (d) => { d.iste(1); d.iste(1); },                   // olay tablosu + satici tablosu
    P3: (d) => d.iste(saticiDizin.get(3).length),
    P4: (d, derinlik) => { let m = new Set([0]);
      for (let h = 0; h < derinlik; h += 1) { const s = new Set(); let k = 0;
        for (const x of m) { k += merkezDizin.get(x).length;
          for (const y of rotaSonraki.get(x) ?? []) s.add(y); }
        d.iste(k); m = s; } },
  },
  "cizge": {                                // dugum ve kenarlar; dolasma depo icinde yurur
    P1: (d) => d.iste(1 + 3),                               // gonderi dugumu + son uc kenar
    P2: (d) => d.iste(2),                                   // olay dugumu + kenar
    P3: (d) => d.iste(1 + 2 * saticiDizin.get(3).length),   // satici dugumu, kenar, gonderi
    P4: (d, derinlik) => { let m = new Set([0]), k = 0;
      for (let h = 0; h < derinlik; h += 1) { const s = new Set();
        for (const x of m) { k += 1 + merkezDizin.get(x).length;
          for (const y of rotaSonraki.get(x) ?? []) s.add(y); }
        m = s; }
      d.iste(k); },
  },
};

console.log(`model: ${GONDERI} gonderi, ${SATICI} satici, ${MERKEZ} merkez, ` +
  `rota 3 adim, dolasma ${H} adim; sayilar sorgu/kayit`);
const ad = ["P1 tekil okuma", "P2 olay yazma", "P3 donem taramasi", "P4 rota dolasmasi"];
console.log(`\n${"depo turu".padEnd(16)}${ad.map((a) => a.padStart(19)).join("")}`);
for (const [tur, o] of Object.entries(ORUNTU)) {
  const hucre = ["P1", "P2", "P3", "P4"].map((p) => {
    const d = new Depo(); o[p](d, H);
    return `${d.sorgu}/${d.kayit}`;
  });
  console.log(tur.padEnd(16) + hucre.map((h) => h.padStart(19)).join(""));
}

console.log(`\nP4 dolasma derinligi ile sorgu sayisi`);
console.log(`${"depo turu".padEnd(16)}${[1, 2, 3].map((h) => `${h} adim`.padStart(10)).join("")}`);
for (const [tur, o] of Object.entries(ORUNTU))
  console.log(tur.padEnd(16) + [1, 2, 3].map((h) => {
    const d = new Depo(); o.P4(d, h); return String(d.sorgu).padStart(10);
  }).join(""));
```

```
model: 60000 gonderi, 20 satici, 8 merkez, rota 3 adim, dolasma 3 adim; sayilar sorgu/kayit

depo turu            P1 tekil okuma      P2 olay yazma  P3 donem taramasi  P4 rota dolasmasi
anahtar-deger                   1/1                2/2          3001/3001        67503/67503
belge                           1/1                1/1             1/3000            3/67500
genis sutun                     1/3                2/2             1/3000            3/67500
cizge                           1/4                1/2             1/6001            1/67503

P4 dolasma derinligi ile sorgu sayisi
depo turu           1 adim    2 adim    3 adim
anahtar-deger        22501     45002     67503
belge                    1         2         3
genis sutun              1         2         3
cizge                    1         1         1
```

Sayılar **hesap** sınıfındadır: modelin sayaçlarından çıkarlar, belirlenimlidirler ve makineden
bağımsızdırlar. Model bir ürün değil, erişim yeteneklerinin bir soyutlamasıdır.

#### Hiçbir Sütun Baştan Sona Kazanmıyor

**P1 hiçbir türü ayırmıyor.** Dört türün de sorgu sayısı 1. Dokunulan kayıt 1, 1, 3 ve 4 olarak
farklılaşıyor, ama tek anahtarla tek kayıt getirmek her türün yaptığı iştir. Bir tasarımı yalnız
takip sorgusuna bakarak seçmek, dört seçeneği ayırt edemez.

**P2 iki türü ayırıyor.** Belge ve çizge deposu bir olayı tek istekte ekliyor. Anahtar–değer
deposu iki istek istiyor, çünkü değerin içine dokunamıyor: bütün değeri okuyup bütün değeri geri
yazıyor. Geniş sütun deposu da iki istek istiyor, ama başka bir nedenle — P3'ü karşılamak için
aynı veri ikinci bir bölüm anahtarıyla ikinci bir tabloya da yazılıyor. **İki tür aynı sayıyı iki
ayrı gerekçeyle üretiyor ve gerekçe, sayının nasıl büyüyeceğini belirliyor**: anahtar–değerin
ikinci isteği kaydın boyutuyla, geniş sütunun ikinci isteği desteklenen sorgu sayısıyla büyür.

**P3 anahtar–değer deposunu eliyor.** Belge ve geniş sütun tek istekte 3000 kayıt okuyor; çizge
tek istekte 6001 kayda dokunuyor, çünkü satıcı düğümünden her gönderiye önce bir kenar, sonra bir
düğüm üzerinden geçiliyor. Anahtar–değer 3001 istek istiyor: ikincil dizin uygulamada tutulduğu
için listede ne varsa tek tek çekiliyor. Dokunulan kayıt sayısı aynı büyüklükte, **istek sayısı üç
bin kat farklı.**

**P4 tabloyu tersine çeviriyor.** Dokunulan kayıt dört türde de aynı mertebede: 67.500 ile 67.503
arası. Ayrım sorgu sütununda. Belge ve geniş sütun adım başına bir istek harcıyor; çizge deposu
derinlik ne olursa olsun tek istekte bitiriyor; anahtar–değer 67.503 istekte. Alt tablo bunu
netleştiriyor: derinlik birden üçe çıktığında belge ve geniş sütunun istek sayısı 1, 2, 3 diye
ilerliyor, çizgeninki 1'de kalıyor. **Çizge deposunun kazandığı şey dokunulan kayıt değil, istek
sayısının dolaşma derinliğinden bağımsız olmasıdır.** Derinliği önceden bilinmeyen bir dolaşma,
başka türlerde kaç istek edeceği bilinmeyen bir işe dönüşür.

#### Hesaba Geri Dönüş

Sorgu sayıları K01'in satırlarına doğrudan çevrilir: her örüntünün istek hızı, o örüntünün gerektirdiği
sorgu sayısıyla çarpılır.

```js
// tur/hesap.mjs — olculen sorgu ve kayit sayilarinin K01 hesabindaki satirlara etkisi.
// Hepsi aritmetiktir; girdi tur/erisim.mjs kosumunun ciktisidir.
const OKUMA = 41.67, YAZMA = 97.22, DEPO = 138.89, ORAN = 2.33;   // K01 hesaplari
const TARAMA = 833.33, SATICI = 4000, PENCERE = 4 * 3600;         // K01 hesaplari ve V10
const OLCUM = {                                    // tur/erisim.mjs: [sorgu, kayit]
  "anahtar-deger": { P1: [1, 1], P2: [2, 2], P3: [3001, 3001] },
  "belge": { P1: [1, 1], P2: [1, 1], P3: [1, 3000] },
  "genis sutun": { P1: [1, 3], P2: [2, 2], P3: [1, 3000] },
  "cizge": { P1: [1, 4], P2: [1, 2], P3: [1, 6001] },
};

console.log(`${"depo turu".padEnd(16)}${"depoya ulasan/s".padStart(16)}${"kat".padStart(7)}` +
  `${"yazma/okuma".padStart(13)}${"toplu istek/s".padStart(15)}${"toplu kayit/s".padStart(15)}` +
  `${"kat".padStart(7)}`);
for (const [tur, o] of Object.entries(OLCUM)) {
  const okuma = OKUMA * o.P1[0], yazma = YAZMA * o.P2[0];
  const istek = (o.P3[0] * SATICI) / PENCERE, kayit = (o.P3[1] * SATICI) / PENCERE;
  console.log(tur.padEnd(16) + (okuma + yazma).toFixed(2).padStart(16) +
    ((okuma + yazma) / DEPO).toFixed(2).padStart(7) + (yazma / okuma).toFixed(2).padStart(13) +
    istek.toFixed(2).padStart(15) + kayit.toFixed(2).padStart(15) +
    (kayit / TARAMA).toFixed(2).padStart(7));
}
console.log(`K01 tabani: depoya ulasan ${DEPO} istek/s, yazma/okuma ${ORAN}, ` +
  `toplu tarama ${TARAMA} kayit/s (toplu istek/s tek depoda ${(SATICI / PENCERE).toFixed(2)})`);
```

```
depo turu        depoya ulasan/s    kat  yazma/okuma  toplu istek/s  toplu kayit/s    kat
anahtar-deger             236.11   1.70         4.67         833.61         833.61   1.00
belge                     138.89   1.00         2.33           0.28         833.33   1.00
genis sutun               236.11   1.70         4.67           0.28         833.33   1.00
cizge                     138.89   1.00         2.33           0.28        1666.94   2.00
K01 tabani: depoya ulasan 138.89 istek/s, yazma/okuma 2.33, toplu tarama 833.33 kayit/s (toplu istek/s tek depoda 0.28)
```

İki tür `depoya ulasan istek/s` satırını 138,89'dan 236,11'e, 1,70 kat büyütüyor ve
`depoda yazma/okuma orani`nı 2,33'ten 4,67'ye çıkarıyor. İkisinde de neden aynı: P2 iki istek
istiyor. Aynı aritmetiği bu konunun ilk dersi de üretmişti — okuma yolunu ayırmak için yazma
yolunu iki hedefe uzatmak aynı 236,11'i veriyordu. **Depo türü değişse de sayı aynı yerden
geliyor: yazma yolunun kaç hedefe dokunduğundan.**

Toplu iş sütunu asıl uyarıyı taşıyor. Belge ve geniş sütun deposunda gün sonu işi dört saatlik
pencerede saniyede 0,28 istek üretiyor ve K01'in hesapladığı 833,33 kayıt/s taramayı birebir
tekrarlıyor. Anahtar–değer deposunda aynı iş saniyede **833,61 istek** üretiyor: dört saatlik bir
pencerede depo, bütün günün adım adım kurulan 138,89 istek/s'sinin altı katını görüyor. Bu, tek
bir mimari kararın K01'in en büyük kalemini hangi sütuna taşıdığını gösteriyor — kayıt sayısı
değişmedi, istek sayısı değişti.

Çizge sütunu bunun aynadaki görüntüsüdür. İstek sayısı en iyi türlerle aynı, ama toplu iş kayıt
hızı 833,33'ten 1666,94'e, iki katına çıkıyor: her gönderiye bir kenar üzerinden ulaşılıyor.
Dolaşmayı ucuzlatan yapı, toplamayı pahalılaştırıyor.

Dört sütun birlikte okunduğunda sonuç tek bir cümledir: **hiçbir tür dört örüntüyü birden
ucuzlatmaz.** Seçim en pahalı örüntüye göre yapılır ve ilk dersin kuralı sürer — kayıt sistemi tek
kalır, öteki türler ondan türetilen ikincil depolardır.

#### Özet

- Bir depo türünü yerel olarak yanıtlayabildiği erişim biçimi tanımlar; yanıtlayamadığı her biçim
  uygulamaya düşer ve uygulamaya düşen her adım ayrı bir depo isteğidir.
- Tekil okuma dört türü ayırt etmiyor: hepsi tek istek, 1 ile 4 arası kayıt.
- Anahtar–değer deposu dönem taramasında 3001 istek istiyor, belge ile geniş sütun 1; dokunulan
  kayıt aynı mertebede, istek sayısı üç bin kat farklı.
- Çizge deposunun kazancı dokunulan kayıtta değil istek sayısındadır: üç adımlık dolaşmada belge
  ve geniş sütun 3 istek harcarken çizge 1 istekte kalıyor ve derinlik arttıkça bu sayı değişmiyor.
- K01'e dönüş: anahtar–değer ve geniş sütun `depoya ulasan istek/s`'yi 138,89'dan 236,11'e (1,70
  kat) ve `depoda yazma/okuma orani`nı 2,33'ten 4,67'ye çıkarıyor.
- Gün sonu işi anahtar–değer deposunda saniyede 833,61 istek üretiyor, çizge deposunda tarama
  hızını 833,33'ten 1666,94 kayıt/s'ye çıkarıyor; hiçbir tür dört örüntüyü birden ucuzlatmıyor.

#### Sonraki Adım

Sekiz ders verinin **nerede** ve **hangi biçimde** durduğunu tasarladı: tür erişim örüntüsüne
göre seçildi, veri çoğaltıldı, federe edildi ve parçalandı; parçalar üç ayrı kuralla
yerleştirildi; okuma yolu denormalizasyonla kısaldı ve tekrarlanan toplam önceden hesaplandı.
Kazanç gerçek: dönem taraması 833,33 kayıt/s'den 8,33'e, bir okumanın çektiği ikincil kayıt
9000'den 40'a indi. Kazanmayan tek sayı `depoya ulasan istek/s`'nin okuma tarafıdır. Bu dersin
en iyi sütununda bile takip sorgusu saniyede 41,67 istekle deponun kapısını çalıyor, çünkü sekiz
dersin hiçbiri okumanın **depoya gitmesini** engellemedi; hepsi vardığında ne bulacağını
düzenledi. Sonraki konu okumanın deponun **önünde** karşılanmasını ele alır ve ilk sorusu kopyanın
hangi katmanda duracağıdır.

## Önbellek Mimarisi

Önbelleğin hangi katmanda hangi stratejiyle duracağı: istemci, kenar, web, uygulama ve veritabanı katmanlarının yerleşim kararı, uygulama denetimli yanında okuma, yazma yolunun önbelleğe bağlandığı içinden yazma ile arkadan yazma, süre dolmadan yenileyen önden tazeleme, önbelleğin gerçekten gerekmediği durumlar ve kabul edilebilir bayatlık penceresinin belirlenmesi.

### Önbellek Yerleşimi

Source: https://academia.sh/tr/kurslar/veri-olcekleme/onbellek-yerlesimi

Kopyanın hangi katmanda duracağının bir tasarım kararı olarak ele alınması: istemci, kenar, web, uygulama ve veritabanı katmanlarının kapsam ve kapasiteyle ayrılması, dört katmanlı bir zincirin katman başına isabetinin ve ıska cezasının ölçülmesi, giriş kursundaki 0,90 önbellek isabeti varsayımının tasarımın sonucu hâline getirilmesi ve tek bir doğru boyutlu önbelleğin aynı oranı daha az dokunuşla verdiğinin gösterilmesi.

Önceki konu verinin nerede ve hangi biçimde durduğunu tasarladı: depo türü erişim örüntüsüne
göre seçildi, veri çoğaltıldı, parçalandı ve okuma yolu denormalizasyonla kısaltıldı. Bütün
bunların sonunda okuma yolu gerçekten kısaldı, ama bir şey değişmedi: her okuma hâlâ bir depoya
gidiyor. Sıradaki soru okumanın deponun **önünde** karşılanmasıdır ve ilki şudur: kopya hangi
katmanda durmalı.

Bu ders o soruyu bir yerleşim kararına çevirir. Önbellekleme, Kuyruklar ve Eşzamansız İşleme
kursu önbellek katmanlarını, stratejileri ve geçersizleştirmeyi kurdu ve ölçtü; burada hiçbiri
yeniden anlatılmaz. Değişen şey ölçeğin kendisidir. Sistem Tasarımına Giriş kursunun kabaca
büyüklük hesabında önbellek bir strateji değil tek bir sayıydı: V9, takip sorgusunun
önbellekten karşılanma oranı, 0,90 alınmıştı ve `onbellek arkasi okuma/s` satırını 41,67'ye,
`depoya ulasan istek/s` satırını 138,89'a indiriyordu. O sayı bir **varsayım**dı. Bu dersin işi
onu bir **sonuç** hâline getirmektir: hangi yerleşim 0,90'ı üretir, üretirken ne öder.

#### Katmanı Ne Ayırır

Bir önbellek katmanını üç şey tanımlar.

**Kapsam**, aynı kopyanın kaç isteği karşılayabileceğidir. İstemci önbelleği tek bir kullanıcıya
hizmet eder; kenar önbelleği bir bölgedeki bütün kullanıcılara; web katmanındaki paylaşılan yanıt
önbelleği bütün isteklere; uygulama içindeki süreç içi önbellek yalnız kendi örneğine düşen
isteklere. Kapsam büyüdükçe bir kopyanın ısıtılması daha çok isteğe yarar.

**Kapasite**, katmanın kaç girdi tutabildiğidir. Kapasite çalışma kümesinin altındaysa katman
kendi girdilerini tahliye eder ve artığı bir sonraki katmana sızdırır.

**Geri alınabilirlik**, konan bir kopyanın kaldırılıp kaldırılamayacağıdır ve kapsamla ters
gider. Uygulama içindeki girdi kodla anında silinir. Kenar katmanındaki için ayrı bir işlem
gerekir. İstemcideki kopya için hiçbir yol yoktur: konarken verilen süre dolana kadar orada
kalır. Bu üçüncü ölçüt bu dersin sayılarında görünmez ama son bölümde kararı belirler.

Beşinci bir katman daha vardır ve ötekilerden farklıdır: **veritabanı önbelleği**, kaynağın
kendi içindedir. Bir sorgunun maliyetini düşürür, sorgu **sayısını** düşürmez; hesabın hiçbir
satırı ona bağlı değildir. Bu ayrım Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda
ölçülerek kuruldu ve burada tekrarlanmaz. Aşağıdaki modelde bu yüzden dört katman vardır.

#### Zincir Modeli

Model süreç içidir: katmanlar birer nesnedir, sayaçlar açıktır, kapasite ile erişim örüntüsü
parametredir. Gerçek bir önbellek ürünü ya da bulut ortamı kurulmaz; ölçülen şey süre değil,
**sayım**dır ve bu yüzden koşumdan koşuma değişmez.

Erişim örüntüsü kursun takip sorgusudur: takip numarasına göre tekil okuma. Bir gönderinin
sorguları bir arada gelir — kullanıcı sayfayı açar, yeniler, bir süre sonra tekrar bakar. Bu
öbeklenme bu dersin varsayımıdır ve K01'in tablosuna eklenmez.

**B1 (bu dersin varsayımı): bir gönderinin sorguları 10 uzunluğunda öbekler hâlinde gelir ve
aynı anda 1000 gönderi sorgulanmaktadır.** Gerekçe: Trafik Katmanı kursunun İçerik Dağıtım
Ağları dersi aynı öbek uzunluğunu kullanmış ve kenar isabetini 0,8998 ölçmüştü; buradaki ek,
eşzamanlı gönderi sayısının bir **çalışma kümesi** tanımlaması ve kapasiteyi anlamlı
kılmasıdır. Duyarlılık: öbek 5'e inerse tekil anahtar sayısı iki katına çıkar ve bütün
katmanların isabeti düşer; çalışma kümesi 2000'e çıkarsa kapasitesi sabit kalan katmanlar
artıklarını bir sonrakine sızdırır.

```js
// onbellek/katman.mjs — katman zincirinin surec ici modeli. Katmanlar birer nesnedir,
// sayaclar aciktir. Kapasite, obek uzunlugu ve calisma kumesi model parametreleridir.
export function onbellek(kapasite) {
  const kutu = new Map();
  const o = {
    tahliye: 0,
    al(a) { const d = kutu.get(a); if (d !== undefined) { kutu.delete(a); kutu.set(a, d); } return d; },
    koy(a, d) {
      kutu.delete(a); kutu.set(a, d);
      if (kutu.size > kapasite) { kutu.delete(kutu.keys().next().value); o.tahliye += 1; }
    },
  };
  return o;
}

// Erisim oruntusu: takip numarasina gore tekil okuma. Bir gonderinin sorgulari `obek`
// uzunlugunda gelir; ayni anda `calismaKumesi` kadar gonderi sorgulanir. Sorgularin
// `sahipPayi` kadari gonderiyi baslatan kullanicidan, kalani baska bir kullanicidan gelir.
export function akis({ istek, calismaKumesi, obek, kullanici, sahipPayi = 0.7 }) {
  let tohum = 20260730;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  const yeni = (no) => ({ no, kalan: obek, sahip: Math.floor(rast() * kullanici) + 1 });
  let sonraki = 1;
  const etkin = Array.from({ length: calismaKumesi }, () => yeni(sonraki++));
  const cikti = [];
  for (let i = 0; i < istek; i++) {
    const j = Math.floor(rast() * etkin.length);
    const g = etkin[j];
    const k = rast() < sahipPayi ? g.sahip : Math.floor(rast() * kullanici) + 1;
    cikti.push([k, `takip:${g.no}`]);
    if ((g.kalan -= 1) === 0) etkin[j] = yeni(sonraki++);
  }
  return cikti;
}

// Zincir: her istek katmanlari sirayla yoklar. Iska her katmanda bir yoklama, depoda bir
// erisim, geri donuste her katmana bir yazma dokunusu sayar.
export function zincir(istekler, katmanlar) {
  const kutular = katmanlar.map(() => new Map());
  const sayac = katmanlar.map(() => ({ isabet: 0, tahliye: 0 }));
  const s = { depo: 0, dokunus: 0, iska: 0 };
  const kutuAl = (i, kullanici, anahtar) => {
    const kapsam = katmanlar[i].kapsam;
    const kimlik = kapsam === "kullanici" ? kullanici
      : kapsam === "ornek" ? (anahtar.length + Number(anahtar.slice(6))) % katmanlar[i].ornek : 0;
    let c = kutular[i].get(kimlik);
    if (c === undefined) { c = onbellek(katmanlar[i].kapasite); kutular[i].set(kimlik, c); }
    return c;
  };
  for (const [kullanici, anahtar] of istekler) {
    let bulundu = -1;
    for (let i = 0; i < katmanlar.length; i++) {
      s.dokunus += 1;
      if (kutuAl(i, kullanici, anahtar).al(anahtar) !== undefined) { bulundu = i; break; }
    }
    if (bulundu === -1) { s.depo += 1; s.iska += 1; s.dokunus += 1; }
    else sayac[bulundu].isabet += 1;
    const son = bulundu === -1 ? katmanlar.length - 1 : bulundu - 1;
    for (let i = 0; i <= son; i++) { s.dokunus += 1; kutuAl(i, kullanici, anahtar).koy(anahtar, anahtar); }
  }
  for (let i = 0; i < katmanlar.length; i++)
    for (const c of kutular[i].values()) sayac[i].tahliye += c.tahliye;
  return { sayac, ...s };
}

// M19/K01 kabaca buyukluk hesabinin girdileri (V1-V4, V8): tepe okuma ve tepe yazma.
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,
  tepeYazma: (400_000 * 7 / 86_400) * 3,
};
```

Pencere 480 saniyedir; K01'in tepe okuma hızı 416,67 istek/s olduğuna göre bu pencereye 200.000
takip sorgusu düşer. **Dokunuş**, modelin tek maliyet birimidir: bir katman yoklaması, bir depo
erişimi ya da bir geri yazma. Dokunuşlar ağırlıklandırılmaz — bir depo erişimiyle bir bellek
yoklaması aynı sayılır. Ağırlığı veren şey zaten yerleşim kararının kendisidir; sayım yalnız
kaç iş yapıldığını söyler.

#### Ölçüm

```js
// onbellek/yerlesim.mjs — dort katmanli zincirin olculmesi ve K01 hesabina donus
import { akis, zincir, K01 } from "./katman.mjs";

const ISTEK = 200_000, OBEK = 10, CALISMA = 1000, KULLANICI = 40_000;
const istekler = akis({ istek: ISTEK, calismaKumesi: CALISMA, obek: OBEK, kullanici: KULLANICI });
const KATMAN = [
  { ad: "istemci", kapsam: "kullanici", kapasite: 4 },
  { ad: "kenar", kapsam: "paylasilan", kapasite: 250 },
  { ad: "web", kapsam: "paylasilan", kapasite: 600 },
  { ad: "uygulama", kapsam: "ornek", kapasite: 400, ornek: 4 },
];

const tekil = new Set(istekler.map(([, a]) => a)).size;
const r = zincir(istekler, KATMAN);
console.log(`istek ${ISTEK} (480 s pencere), tekil takip numarasi ${tekil}, obek ${OBEK}, es zamanli gonderi ${CALISMA}`);
console.log();
console.log("katman     kapsam       kapasite   isabet  isabet payi  tahliye");
console.log("---------- ----------- --------- -------- ------------ --------");
for (let i = 0; i < KATMAN.length; i++) {
  const k = KATMAN[i], c = r.sayac[i];
  const kap = k.kapsam === "ornek" ? `${k.kapasite}x${k.ornek}` : String(k.kapasite);
  console.log(`${k.ad.padEnd(10)} ${k.kapsam.padEnd(11)} ${kap.padStart(9)} ` +
    `${String(c.isabet).padStart(8)} ${(c.isabet / ISTEK).toFixed(4).padStart(12)} ${String(c.tahliye).padStart(8)}`);
}
console.log(`${"depo".padEnd(10)} ${"-".padEnd(11)} ${"-".padStart(9)} ${String(r.depo).padStart(8)} ` +
  `${(r.depo / ISTEK).toFixed(4).padStart(12)} ${"-".padStart(8)}`);

const h = 1 - r.depo / ISTEK;
const arka = K01.tepeOkuma * (1 - h);
console.log();
console.log(`birlesik isabet      = ${h.toFixed(4)}  (V9 varsayimi 0,90)`);
console.log(`bir iskanin dokunusu = ${2 * KATMAN.length + 1} (${KATMAN.length} yoklama + 1 depo + ${KATMAN.length} geri yazma)`);
console.log(`dokunus / istek      = ${(r.dokunus / ISTEK).toFixed(3)}`);
console.log();
console.log(`K01'e donus (tepe okuma ${K01.tepeOkuma.toFixed(2)}, tepe yazma ${K01.tepeYazma.toFixed(2)} istek/s)`);
console.log(`aga hic cikmayan okuma/s = ${(K01.tepeOkuma * r.sayac[0].isabet / ISTEK).toFixed(2)}`);
console.log(`uygulamaya varan okuma/s = ${(K01.tepeOkuma * (r.sayac[3].isabet + r.depo) / ISTEK).toFixed(2)}`);
console.log(`onbellek arkasi okuma/s  = ${arka.toFixed(2)}  (V9 ile 41,67)`);
console.log(`depoya ulasan istek/s    = ${(arka + K01.tepeYazma).toFixed(2)}  (V9 ile 138,89)`);
console.log(`depoda yazma/okuma orani = ${(K01.tepeYazma / arka).toFixed(2)}  (V9 ile 2,33)`);
```

```
istek 200000 (480 s pencere), tekil takip numarasi 20454, obek 10, es zamanli gonderi 1000

katman     kapsam       kapasite   isabet  isabet payi  tahliye
---------- ----------- --------- -------- ------------ --------
istemci    kullanici           4   140045       0.7002    34810
kenar      paylasilan        250    16520       0.0826    43185
web        paylasilan        600    14390       0.0720    28445
uygulama   ornek           400x4     8394       0.0420    19051
depo       -                   -    20651       0.1033        -

birlesik isabet      = 0.8967  (V9 varsayimi 0,90)
bir iskanin dokunusu = 9 (4 yoklama + 1 depo + 4 geri yazma)
dokunus / istek      = 2.531

K01'e donus (tepe okuma 416.67, tepe yazma 97.22 istek/s)
aga hic cikmayan okuma/s = 291.76
uygulamaya varan okuma/s = 60.51
onbellek arkasi okuma/s  = 43.02  (V9 ile 41,67)
depoya ulasan istek/s    = 140.25  (V9 ile 138,89)
depoda yazma/okuma orani = 2.26  (V9 ile 2,33)
```

Bu sayıların hepsi **hesap** sınıfındadır: belirlenimli bir akış üzerinde sayılmışlardır, süre
ölçülmemiştir, koşumdan koşuma değişmezler.

Birinci sonuç V9 hakkındadır. Dört katmanın birleşik isabeti 0,8967 çıktı; varsayılan 0,90'ın
biraz altında. Fark küçük görünüyor ama hesapta görünür: `onbellek arkasi okuma/s` 41,67 yerine
43,02, `depoya ulasan istek/s` 138,89 yerine 140,25, `depoda yazma/okuma orani` 2,33 yerine
2,26. Varsayım artık savunulabilir bir sayıdır çünkü onu üreten yerleşim yazılmıştır. Kapasite
ya da çalışma kümesi değiştiğinde bu satırlar yeniden hesaplanır.

İkinci sonuç isabetin **dağılımı** hakkındadır. İsteklerin 0,7002'si istemci katmanında,
yalnız dört girdilik bir önbellekte durdu — çünkü öbek içindeki sorguların çoğu aynı
kullanıcıdan geliyor. Bu, saniyede 291,76 okumanın ağa hiç çıkmaması demektir. Kalan üç katman
toplam 0,1966 durdurdu ve uygulamaya varan okuma 416,67'den 60,51 istek/s'ye indi. Trafik
Katmanı kursunun İçerik Dağıtım Ağları dersi aynı satırı 41,76 olarak ölçmüştü; oradaki
uygulama önbelleği pencerenin bütün anahtarlarını tutabiliyordu, buradaki katmanların kapasitesi
çalışma kümesinin altındadır ve tahliye sütunu bunu gösteriyor.

Üçüncü sonuç bedelin nerede olduğudur: **ıska cezası**. Bir ıska dokuz dokunuş yapar: dört
yoklama, bir depo erişimi ve dönüşte dört geri yazma. Ortalama 2,531 dokunuş/istek. Zincir
uzadıkça isabet artar ama **her** ıska daha uzun bir yol yürür.

#### Katman Eklemenin Sınırı

Bir katmanın değeri, durdurduğu istek sayısı ile eklediği dokunuşun oranıdır. Aşağıdaki koşum
zinciri baştan kurarak her katmanın kendi payını ayırır ve aynı isabeti tek bir önbellekle
elde etmenin ne istediğini gösterir.

```js
// onbellek/kac-katman.mjs — zincire katman eklendikce ne kazanilir, ne odenir
import { akis, zincir, K01 } from "./katman.mjs";

const ISTEK = 200_000;
const istekler = akis({ istek: ISTEK, calismaKumesi: 1000, obek: 10, kullanici: 40_000 });
const TAM = [
  { ad: "istemci", kapsam: "kullanici", kapasite: 4 },
  { ad: "kenar", kapsam: "paylasilan", kapasite: 250 },
  { ad: "web", kapsam: "paylasilan", kapasite: 600 },
  { ad: "uygulama", kapsam: "ornek", kapasite: 400, ornek: 4 },
];

console.log("zincir                       isabet  depo/s  dokunus/istek  durdurulan  eklenen dokunus  dokunus/durdurulan");
console.log("---------------------------- ------ ------- -------------- ----------- ---------------- ------------------");
let onceki = { dokunus: ISTEK, depo: ISTEK };   // onbelleksiz: istek basina bir depo erisimi
for (let n = 0; n <= TAM.length; n++) {
  const r = n === 0 ? { depo: ISTEK, dokunus: ISTEK } : zincir(istekler, TAM.slice(0, n));
  const h = 1 - r.depo / ISTEK;
  const depoS = K01.tepeOkuma * (1 - h) + K01.tepeYazma;
  const durdurulan = onceki.depo - r.depo;
  const eklenen = r.dokunus - onceki.dokunus;
  const ad = n === 0 ? "(onbelleksiz)" : "+" + TAM[n - 1].ad;
  console.log(`${ad.padEnd(28)} ${h.toFixed(4).padStart(6)} ${depoS.toFixed(2).padStart(7)} ` +
    `${(r.dokunus / ISTEK).toFixed(3).padStart(14)} ${(n === 0 ? "-" : String(durdurulan)).padStart(11)} ` +
    `${(n === 0 ? "-" : String(eklenen)).padStart(16)} ${(n === 0 ? "-" : (eklenen / durdurulan).toFixed(2)).padStart(18)}`);
  onceki = r;
}

const tam = zincir(istekler, TAM);
const h = 1 - tam.depo / ISTEK;
console.log();
console.log(`dort katmanin birlesik isabeti = ${h.toFixed(4)}`);
console.log(`ayni isabet tek katmanla istenirse gereken kapasite:`);
for (const kap of [250, 600, 1000, 1200, 2000]) {
  const t = zincir(istekler, [{ ad: "tek", kapsam: "paylasilan", kapasite: kap }]);
  const th = 1 - t.depo / ISTEK;
  console.log(`  kapasite ${String(kap).padStart(4)} -> isabet ${th.toFixed(4)}  depo/s ${(K01.tepeOkuma * (1 - th) + K01.tepeYazma).toFixed(2).padStart(6)}  dokunus/istek ${(t.dokunus / ISTEK).toFixed(3)}`);
}
```

```
zincir                       isabet  depo/s  dokunus/istek  durdurulan  eklenen dokunus  dokunus/durdurulan
---------------------------- ------ ------- -------------- ----------- ---------------- ------------------
(onbelleksiz)                0.0000  513.89          1.000           -                -                  -
+istemci                     0.7002  222.13          1.600      140045           119910               0.86
+kenar                       0.7828  187.71          2.034       16520            86870               5.26
+web                         0.8548  157.73          2.324       14390            58090               4.04
+uygulama                    0.8967  140.25          2.531        8394            41302               4.92

dort katmanin birlesik isabeti = 0.8967
ayni isabet tek katmanla istenirse gereken kapasite:
  kapasite  250 -> isabet 0.2321  depo/s 417.20  dokunus/istek 2.536
  kapasite  600 -> isabet 0.5236  depo/s 295.71  dokunus/istek 1.953
  kapasite 1000 -> isabet 0.7915  depo/s 184.11  dokunus/istek 1.417
  kapasite 1200 -> isabet 0.8646  depo/s 153.63  dokunus/istek 1.271
  kapasite 2000 -> isabet 0.8975  depo/s 139.95  dokunus/istek 1.205
```

Birinci tablo azalan getiriyi sayıyla veriyor. Önbelleksiz tasarımda depoya ulaşan istek 513,89
istek/s'dir — K01'in uçtaki tepe yükünün tamamı. İlk katman bunu 222,13'e indiriyor ve
durdurduğu her istek başına yalnız 0,86 dokunuş ekliyor, çünkü durdurduğu istek zaten bir depo
erişimini iptal ediyor. Sonraki üç katmanın oranı 5,26, 4,04 ve 4,92: her biri, kurtardığı her
istek için beşe yakın ek dokunuş ödetiyor. Dördüncü katman 8394 isteği durdurup 41.302 dokunuş
ekliyor ve depoya ulaşan yükü yalnız 17,49 istek/s düşürüyor.

İkinci tablo daha rahatsız edici. Kapasitesi 2000 olan **tek** bir paylaşılan önbellek 0,8975
isabet veriyor — dört katmanlı zincirin 0,8967'sinin üstünde — ve bunu istek başına 1,205
dokunuşla yapıyor, 2,531 yerine. Zincirin gerekçesi birleşik isabet oranı değildir: doğru
boyutlandırılmış tek bir katman aynı oranı daha ucuza verir.

Zincirin gerekçesi isabetin **nerede** karşılandığıdır. Tek paylaşılan önbellekte isabetlerin
tamamı bir ağ turuna mal olur ve hepsi tek bir bileşenden geçer; dört katmanlı yerleşimde
saniyede 291,76 okuma hiç ağa çıkmaz. Buna karşılık o 291,76 okuma, geri alınabilirliği en
düşük katmanda karşılanır: istemcideki kopya konduktan sonra silinemez. Yerleşim kararı bu iki
cümlenin arasında verilir ve ikisi de aynı isabet oranıyla uyumludur.

#### Özet

- Bir önbellek katmanını kapsam, kapasite ve geri alınabilirlik tanımlar; veritabanı önbelleği
  bu sıralamanın dışındadır çünkü sorgunun maliyetini düşürür, sayısını düşürmez.
- Dört katmanlı zincirin birleşik isabeti 0,8967 ölçüldü. K01'in V9 = 0,90 varsayımı böylece
  tasarımın sonucu oldu: `onbellek arkasi okuma/s` 41,67 yerine 43,02, `depoya ulasan istek/s`
  138,89 yerine 140,25, `depoda yazma/okuma orani` 2,33 yerine 2,26.
- İsabetin 0,7002'si dört girdilik istemci önbelleğinde durdu; saniyede 291,76 okuma ağa hiç
  çıkmadı ve uygulamaya varan okuma 416,67'den 60,51 istek/s'ye indi.
- Zincir uzadıkça her ıska daha uzun yol yürür: dört katmanda bir ıska dokuz dokunuş, ortalama
  2,531 dokunuş/istek. Katmanların durdurdukları istek başına ek dokunuşu sırasıyla 0,86, 5,26,
  4,04 ve 4,92.
- Kapasitesi 2000 olan tek bir paylaşılan önbellek 0,8975 isabeti 1,205 dokunuşla veriyor;
  zincirin gerekçesi isabet oranı değil, isabetin hangi katmanda karşılandığıdır.

#### Sonraki Adım

Bu derste her katman aynı biçimde çalıştı: bak, yoksa bir alttakine sor, dönüşte koy. Bu davranış
kodda tek bir döngüdür ve bir adı vardır; üstelik tek seçenek de değildir. Katmanın isabeti
yalnız kapasiteye değil, girdinin **ne zaman düştüğüne** de bağlıdır ve girdiyi düşüren şey
çoğunlukla bir yazmadır. K01'in hesabı deponun gördüğü yerde yazma ağırlıklı bir sistem
tarif ediyordu: yazma/okuma oranı 2,33. Sıradaki ders uygulamanın kendi denetlediği bu
yerleştirmeyi adlandırır ve isabet oranını sabit bir sayı olmaktan çıkarıp yazma sıklığının bir
işlevi hâline getirir; o işlev 0,90'ın nerede tutulabildiğini ve nerede tutulamadığını söyler.

### Yanında Okuma

Source: https://academia.sh/tr/kurslar/veri-olcekleme/yaninda-okuma

Uygulama denetimli önbelleğin ölçekleme aracı olarak ele alınması: isabet oranının yazma/okuma oranının bir işlevi hâline getirilmesi, giriş kursunun 0,2333'lük oranında 0,90 varsayımının 0,7287'ye düşmesi, her geçersizleştirmenin bir depo okuması doğurması ve önbellek katmanı kısmen bozulduğunda uygulamanın depoya kendi gidebilmesinin ölçülmesi.

Önceki ders dört katmanı bir zincir olarak ölçtü ve hepsi aynı biçimde davrandı: bak, yoksa bir
alttakine sor, dönüşte koy. O davranışın adı **yanında okuma** (cache-aside) ve önbellek
katmanının isabeti yalnız kapasiteye bağlı değil; girdiyi düşüren ikinci bir kuvvet var ve o
kuvvet yazmadır. Kabaca büyüklük hesabı deponun gördüğü yerde yazma ağırlıklı bir sistem tarif
ediyordu: `depoda yazma/okuma orani` 2,33. Bu ders isabet oranını sabit bir sayı olmaktan
çıkarıp yazma sıklığının bir işlevi hâline getirir.

Yanında okumanın mekaniği Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda kuruldu ve
ölçüldü; burada yeniden anlatılmaz. Buradaki iki soru başkadır: bu strateji hangi katmana ait
ve K01'in yazma hızında ne veriyor.

#### Uygulama Denetimli Olmak Ne Demek

Yanında okumanın tanımlayıcı özelliği, önbelleğin okuma yolunun **yanında** durmasıdır: veriyi
getirme sorumluluğu önbellekte değil, uygulamadadır. Aynı okuma yolunun iki gerçekleştirimi
vardır — uygulama kodunun elle baktığı biçim ve okumanın önbellek katmanının üzerinden geçtiği
biçim — ve ikisi de bu adla anılır. Fark mekanikte değil, sorumluluğun nerede durduğundadır ve
son bölümde ölçülen tek şey odur.

Bu strateji, önbelleğin **yazma yoluna bağlanamadığı** her yerde tek seçenektir. Gönderi takip
servisinde durum olayları uygulama katmanının okuma yolundan gelmez: taşıyıcıdan gelir, Uygulama
Katmanı ve Servis Etkileşimi kursunda tasarlanan kuyruk üzerinden işlenir ve depoya oradan
yazılır. Okuma yolundaki önbellek o yazmanın olduğu yerde değildir; yazıldığını ancak bir
geçersizleştirme iletisiyle öğrenir. Bu yüzden okuma yolu önbelleğinin yapabileceği tek şey
girdiyi **silmek**tir, güncellemek değil. Yazmanın önbelleğe bağlanabildiği durum sonraki dersin
konusudur.

Katman seçimi de buradan çıkar. Önceki dersin ölçümünde uygulama katmanındaki paylaşılan
önbellek isabetin yalnız küçük bir payını durduruyordu, ama geri alınabilirliği en yüksek katman
oydu: bir silme iletisi ancak orada anında uygulanabilir. Yanında okuma bu yüzden uygulama
katmanına konur; kenar ve istemci katmanları aynı stratejiyi çalıştıramaz çünkü onlarda silme
yoktur, yalnız süre vardır.

#### Yazmanın İsabete Etkisi

Model süreç içidir ve tek katmanlıdır: önceki dersin ölçümü kapasitesi 2000 olan tek bir
paylaşılan önbelleğin çalışma kümesini karşıladığını gösterdiği için burada kapasite bağlayıcı
kısıt olmaktan çıkarılır ve tek değişken yazma oranı olur.

**B1 (bu dersin varsayımı): durum olayları, o anda sorgulanan gönderilere düşer.** Gerekçe: bir
gönderi tam da yolda olduğu için hem olay üretir hem sorgulanır. Bu, geçersizleştirmenin en güçlü
olduğu yöndür; duyarlılık: olayların yarısı sorgulanmayan gönderilere düşerse silme sayısı ve
doğurduğu ek okuma yarıya iner. Varsayım K01'in tablosuna eklenmez.

```js
// onbellek/yaninda.mjs — yaninda okumanin surec ici modeli. Onbellek, depo ve olay akisi
// birer modul; sayaclar aciktir. Yazma/okuma orani, kapasite ve ariza payi parametredir.
export function kutuAc(kapasite) {
  const m = new Map();
  const o = {
    tahliye: 0,
    var: (a) => m.has(a),
    al(a) { const d = m.get(a); if (d !== undefined) { m.delete(a); m.set(a, d); } return d; },
    koy(a, d) { m.delete(a); m.set(a, d); if (m.size > kapasite) { m.delete(m.keys().next().value); o.tahliye += 1; } },
    sil: (a) => m.delete(a),
  };
  return o;
}

// Akis: okuma ve yazma ayni etkin gonderi kumesine duser (B1). `oran` yazma/okuma oranidir.
export function akis({ okuma, oran, calismaKumesi, obek }) {
  let tohum = 20260730;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  let sonraki = 1;
  const etkin = Array.from({ length: calismaKumesi }, () => ({ no: sonraki++, kalan: obek }));
  const cikti = [];
  let borc = 0;
  for (let i = 0; i < okuma; i++) {
    const j = Math.floor(rast() * etkin.length);
    const g = etkin[j];
    cikti.push(["oku", `takip:${g.no}`]);
    if ((g.kalan -= 1) === 0) etkin[j] = { no: sonraki++, kalan: obek };
    for (borc += oran; borc >= 1; borc -= 1)
      cikti.push(["yaz", `takip:${etkin[Math.floor(rast() * etkin.length)].no}`]);
  }
  return cikti;
}

// Yaninda okuma: okumada bak-getir-koy, yazmada depoya yaz ve girdiyi sil.
// gecersizlestir=false: yazma girdiyi silmez. ariza: onbellek isleminin basarisiz olma payi.
// atlayabilir=false: okuma onbellegin uzerinden gecer, uygulama depoya kendi gidemez.
export function kosum(islemler, { kapasite, gecersizlestir = true, ariza = 0, atlayabilir = true }) {
  const c = kutuAc(kapasite);
  let tohum = 991;
  const bozuk = () => ariza > 0 && ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648) < ariza;
  const s = { isabet: 0, iska: 0, depoOkuma: 0, depoYazma: 0, silme: 0, bayat: 0, dokunus: 0, karsilanmayan: 0 };
  const dogru = new Map();
  let surum = 0;
  for (const [tur, anahtar] of islemler) {
    if (!dogru.has(anahtar)) dogru.set(anahtar, 0);
    if (tur === "oku") {
      s.dokunus += 1;
      if (bozuk()) {
        if (!atlayabilir) { s.karsilanmayan += 1; continue; }
        s.iska += 1; s.depoOkuma += 1; s.dokunus += 1;
        continue;
      }
      if (c.var(anahtar)) {
        s.isabet += 1;
        if (c.al(anahtar) !== dogru.get(anahtar)) s.bayat += 1;
        continue;
      }
      s.iska += 1; s.depoOkuma += 1; s.dokunus += 2;
      c.koy(anahtar, dogru.get(anahtar));
    } else {
      dogru.set(anahtar, (surum += 1));
      s.depoYazma += 1; s.dokunus += 1;
      if (!gecersizlestir) continue;
      s.dokunus += 1;
      if (bozuk()) continue;                       // silme kaybolur: girdi bayat kalir
      if (c.sil(anahtar)) s.silme += 1;
    }
  }
  return { ...s, tahliye: c.tahliye };
}

// M19/K01 kabaca buyukluk hesabinin girdileri (V1-V4, V8).
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,
  tepeYazma: (400_000 * 7 / 86_400) * 3,
};
```

```js
// onbellek/oran.mjs — isabet oraninin yazma/okuma oraninin islevi olarak olculmesi
import { akis, kosum, K01 } from "./yaninda.mjs";

const OKUMA = 200_000, KAPASITE = 2000, CALISMA = 1000, OBEK = 10;
const K01_ORAN = K01.tepeYazma / K01.tepeOkuma;

console.log(`okuma ${OKUMA}, kapasite ${KAPASITE}, es zamanli gonderi ${CALISMA}, obek ${OBEK}`);
console.log(`K01 yazma/okuma orani = ${K01_ORAN.toFixed(4)} (tepe yazma ${K01.tepeYazma.toFixed(2)} / tepe okuma ${K01.tepeOkuma.toFixed(2)})`);
console.log();
console.log("yazma/okuma   yazma   isabet  depo okuma  onbellek arkasi/s  depoya ulasan/s  depoda yazma/okuma");
console.log("----------- ------- -------- ----------- ------------------ ---------------- ------------------");
for (const oran of [0, 0.05, 0.1167, K01_ORAN, 0.4667, 1]) {
  const islemler = akis({ okuma: OKUMA, oran, calismaKumesi: CALISMA, obek: OBEK });
  const r = kosum(islemler, { kapasite: KAPASITE });
  const h = r.isabet / OKUMA;
  const arka = K01.tepeOkuma * (1 - h);
  const etiket = Math.abs(oran - K01_ORAN) < 1e-9 ? `${oran.toFixed(4)}*` : oran.toFixed(4);
  console.log(`${etiket.padStart(11)} ${String(r.depoYazma).padStart(7)} ${h.toFixed(4).padStart(8)} ` +
    `${String(r.depoOkuma).padStart(11)} ${arka.toFixed(2).padStart(18)} ` +
    `${(arka + K01.tepeYazma).toFixed(2).padStart(16)} ${(K01.tepeYazma / arka).toFixed(2).padStart(18)}`);
}
console.log("* K01'in tepe hizlarindan cikan oran");

console.log();
const islemler = akis({ okuma: OKUMA, oran: K01_ORAN, calismaKumesi: CALISMA, obek: OBEK });
const ile = kosum(islemler, { kapasite: KAPASITE });
const olmadan = kosum(islemler, { kapasite: KAPASITE, gecersizlestir: false });
console.log(`K01 oraninda gecersizlestirme ile   : isabet ${(ile.isabet / OKUMA).toFixed(4)}  depo okuma ${ile.depoOkuma}  silme ${ile.silme}  bayat okuma ${ile.bayat}`);
console.log(`K01 oraninda gecersizlestirme olmadan: isabet ${(olmadan.isabet / OKUMA).toFixed(4)}  depo okuma ${olmadan.depoOkuma}  silme ${olmadan.silme}  bayat okuma ${olmadan.bayat}`);
console.log(`silme basina dogan ek depo okumasi  = ${((ile.depoOkuma - olmadan.depoOkuma) / ile.silme).toFixed(2)}`);
console.log(`yazma basina ek islem               = ${((ile.dokunus - olmadan.dokunus) / ile.depoYazma).toFixed(2)} dokunus + ${((ile.depoOkuma - olmadan.depoOkuma) / ile.depoYazma).toFixed(2)} depo okumasi`);
```

```
okuma 200000, kapasite 2000, es zamanli gonderi 1000, obek 10
K01 yazma/okuma orani = 0.2333 (tepe yazma 97.22 / tepe okuma 416.67)

yazma/okuma   yazma   isabet  depo okuma  onbellek arkasi/s  depoya ulasan/s  depoda yazma/okuma
----------- ------- -------- ----------- ------------------ ---------------- ------------------
     0.0000       0   0.8976       20483              42.67           139.90               2.28
     0.0500   10000   0.8551       28976              60.37           157.59               1.61
     0.1167   23340   0.8039       39212              81.69           178.91               1.19
    0.2333*   46666   0.7287       54257             113.04           210.26               0.86
     0.4667   93340   0.6134       77312             161.07           258.29               0.60
     1.0000  200000   0.4523      109549             228.23           325.45               0.43
* K01'in tepe hizlarindan cikan oran

K01 oraninda gecersizlestirme ile   : isabet 0.7287  depo okuma 54257  silme 33957  bayat okuma 0
K01 oraninda gecersizlestirme olmadan: isabet 0.8977  depo okuma 20451  silme 0  bayat okuma 105794
silme basina dogan ek depo okumasi  = 1.00
yazma basina ek islem               = 2.45 dokunus + 0.72 depo okumasi
```

Bütün sayılar **hesap** sınıfındadır; belirlenimli bir akış üzerinde sayılmışlardır ve süre
ölçülmemiştir.

Birinci satır yazma yokken önceki dersin sonucunu tekrarlıyor: isabet 0,8976, `depoya ulasan
istek/s` 139,90. K01'in varsayımı burada tutuyor. K01'in kendi yazma hızında ise tutmuyor.
Yazma/okuma oranı 0,2333 olduğunda isabet **0,7287**'ye düşüyor, `onbellek arkasi okuma/s`
41,67'den 113,04'e, `depoya ulasan istek/s` 138,89'dan **210,26**'ya çıkıyor. V9 = 0,90
varsayımı örtük olarak "önbelleğe hiç yazılmıyor" demekti; sistem kendi yazma hızını
gördüğünde varsayım yüzde on dokuz eksiliyor.

`depoda yazma/okuma orani` sütunu daha da ilginç. K01 bu oranı 2,33 hesaplamış ve "okuma
ağırlıklı diye tarif edilen sistem, deponun gördüğü yerde yazma ağırlıklıdır" demişti. Yanında
okumanın gerçek isabetiyle oran **0,86**'ya iniyor: geçersizleştirme deponun okuma yükünü öyle
büyütüyor ki depo yeniden okuma ağırlıklı hâle geliyor. Yazma sayısı hiç değişmedi; değişen,
her yazmanın doğurduğu okumadır.

Son üç satır o doğurmayı sayıyor. K01 oranında 33.957 girdi silindi ve depo okuması 20.451'den
54.257'ye çıktı: **silme başına 1,00 ek depo okuması**. Silinen girdi neredeyse her zaman yeniden
okunuyor, çünkü silinme nedeni tam da o gönderinin etkin olmasıdır. Bir yazmanın önbelleğe
maliyeti böylece "bir silme" değil, "bir silme artı 0,72 depo okuması"dır.

Geçersizleştirme kapatıldığında isabet 0,8977'ye geri dönüyor ve fatura başka bir sütunda
görünüyor: 105.794 bayat okuma, yani okumaların yarıdan fazlası. Yanında okumanın ödünleşimi bu
iki sütun arasındadır ve ikisinin ortasındaki bölge — kabul edilebilir bayatlık penceresi — bu
konunun son dersinin konusudur.

#### Uygulamanın Depoya Kendi Gidebilmesi

İki gerçekleştirim biçimi arasındaki fark, önbellek katmanı bozulduğunda ölçülebilir hâle gelir.
Model, önbellek işlemlerinin bir payını başarısız kılar; uygulama denetimli biçim bunu ıska
sayıp depoya gider, üzerinden geçen biçim ise isteği karşılayamaz.

```js
// onbellek/ariza.mjs — onbellek katmani kismen bozuldugunda iki bicimin davranisi
import { akis, kosum, K01 } from "./yaninda.mjs";

const OKUMA = 200_000, KAPASITE = 2000;
const ORAN = K01.tepeYazma / K01.tepeOkuma;
const islemler = akis({ okuma: OKUMA, oran: ORAN, calismaKumesi: 1000, obek: 10 });

console.log("ariza  bicim               karsilanan  isabet  depo okuma  depoya ulasan/s  bayat okuma");
console.log("------ ------------------- ---------- ------- ----------- ---------------- -----------");
for (const ariza of [0, 0.1, 0.5, 1]) {
  for (const [ad, atlayabilir] of [["uygulama denetimli", true], ["uzerinden gecen", false]]) {
    const r = kosum(islemler, { kapasite: KAPASITE, ariza, atlayabilir });
    const karsilanan = OKUMA - r.karsilanmayan;
    const h = r.isabet / OKUMA;
    const depoS = K01.tepeOkuma * (r.depoOkuma / OKUMA) + K01.tepeYazma;
    console.log(`${ariza.toFixed(2).padStart(6)} ${ad.padEnd(19)} ${(karsilanan / OKUMA).toFixed(4).padStart(10)} ` +
      `${h.toFixed(4).padStart(7)} ${String(r.depoOkuma).padStart(11)} ${depoS.toFixed(2).padStart(16)} ${String(r.bayat).padStart(11)}`);
  }
}
```

```
ariza  bicim               karsilanan  isabet  depo okuma  depoya ulasan/s  bayat okuma
------ ------------------- ---------- ------- ----------- ---------------- -----------
  0.00 uygulama denetimli      1.0000  0.7287       54257           210.26           0
  0.00 uzerinden gecen         1.0000  0.7287       54257           210.26           0
  0.10 uygulama denetimli      1.0000  0.6468       70650           244.41        7473
  0.10 uzerinden gecen         0.8974  0.6468       50126           201.65        7473
  0.50 uygulama denetimli      1.0000  0.3209      135818           380.18       18780
  0.50 uzerinden gecen         0.4876  0.3209       33334           166.67       18780
  1.00 uygulama denetimli      1.0000  0.0000      200000           513.89           0
  1.00 uzerinden gecen         0.0000  0.0000           0            97.22           0
```

Sağlıklı durumda iki biçim aynı satırı üretiyor; fark yalnız arıza altında var. Önbellek
tamamen çöktüğünde uygulama denetimli biçim isteklerin tamamını karşılamayı sürdürüyor ve depoya
ulaşan istek 513,89 istek/s'ye çıkıyor — K01'in uçtaki tepe yükünün tamamı. Bu sayı bir tasarım
şartıdır: yanında okumayı seçmek, deponun bu yükü kısa süre taşıyabilmesini şart koşar. Üzerinden
geçen biçimde ise karşılanan istek oranı sıfıra iniyor; servisin hizmet erişilebilirliği
önbellek katmanının ayakta kalmasına bağlanmış olur.

Depoya ulaşan istek sütunu bu satırlarda yanıltıcıdır ve bilerek yan yana konmuştur: yüzde elli
arızada üzerinden geçen biçimin deposu 166,67 istek/s ile sağlıklı durumdakinden **daha az** yük
görüyor. Depo yükü düşük görünürken isteklerin yarısı hiç yanıtlanmıyor. Bir ölçütün iyileşmesi
tek başına okunamaz.

Üçüncü sütun beklenmedik bir sonucu taşıyor: yüzde on arızada 7473 bayat okuma var, oysa sağlıklı
koşumda hiç yoktu. Kaybolan şey okumalar değil **silmelerdir**; başarısız bir silme, hiçbir
sayaçta hata olarak görünmeden bayat bir girdi bırakır. Yanında okumanın doğruluğu, en gürültüsüz
biçimde başarısız olan işleme bağlıdır.

#### Özet

- Yanında okuma, veriyi getirme sorumluluğunun önbellekte değil uygulamada olduğu okuma yolu
  önbelleğidir; yazmanın önbelleğe bağlanamadığı yerde tek seçenektir ve yapabildiği tek şey
  girdiyi silmektir.
- İsabet oranı yazma/okuma oranının işlevidir: yazma yokken 0,8976, K01'in 0,2333'lük oranında
  **0,7287**. V9 = 0,90 varsayımı örtük olarak yazmasız bir sistem varsayıyordu.
- K01'e dönüş: `onbellek arkasi okuma/s` 41,67'den 113,04'e, `depoya ulasan istek/s` 138,89'dan
  210,26'ya çıkıyor; `depoda yazma/okuma orani` 2,33'ten 0,86'ya iniyor, yani depo yeniden okuma
  ağırlıklı hâle geliyor.
- Silme başına 1,00 ek depo okuması doğuyor; bir yazmanın önbelleğe maliyeti 2,45 dokunuş ve 0,72
  depo okumasıdır. Geçersizleştirme kapatılırsa isabet 0,8977'ye döner ama 105.794 bayat okuma
  oluşur.
- Önbellek tamamen çöktüğünde uygulama denetimli biçim isteklerin tamamını karşılar ve depoya
  513,89 istek/s biner; üzerinden geçen biçimde karşılanan istek sıfırlanır. Kısmi arızada
  kaybolan silmeler 7473 bayat okuma bırakır.

#### Sonraki Adım

Bu dersin bütün maliyeti tek bir kısıttan doğdu: önbellek yazmanın olduğu yerde değildi, bu
yüzden yeni değeri koyamadı, yalnız eskisini sildi ve her silme bir depo okuması doğurdu. Kısıt
her zaman geçerli değildir. Gönderi durumunu depoya yazan yol uygulamanın kendi içinden geçiyorsa
önbelleğe yeni değer yazmanın önünde bir engel kalmaz; yazma iki yere birden gider ve silme
hiç yapılmaz. Aynı bağ bir adım daha ileri götürülüp yazma önce önbelleğe, depoya sonra
toplu olarak da gönderilebilir — bu kez değişen sayı okuma tarafında değil, `depoda yazma/okuma
orani` satırının payındadır. Sonraki ders yazma yolunun önbelleğe bağlandığı bu iki düzeni
ölçer: yazma yolunun kaç yere uzadığını, yazma başına ne kadar ek iş çıktığını ve süreç
durduğunda kaç olayın bellekte kaldığını.

### İçinden Yazma ve Arkadan Yazma

Source: https://academia.sh/tr/kurslar/veri-olcekleme/icinden-yazma-ve-arkadan-yazma

Yazma yolunun önbelleğe bağlanmasının iki düzeni: içinden yazmanın silmeyi kaldırıp isabeti 0,7285'ten 0,9170'e çıkarması, denormalizasyonun eklediği ikinci yazma hedefinin yolu iki depoya uzatması, arkadan yazmanın toplu aktarımla yazma başına depo yazmasını 2,00'dan 1,20'ye indirmesi ve bunun karşılığında bellekte bekleyen 200 olayın dayanıklılık penceresi olarak ölçülmesi.

Önceki dersin bütün maliyeti tek bir kısıttan doğdu: önbellek yazmanın olduğu yerde değildi, yeni
değeri koyamadı, yalnız eskisini sildi ve her silme bir depo okuması doğurdu. Gönderi durumunu
depoya yazan yol uygulamanın kendi içinden geçtiğinde bu kısıt kalkar. Yazma iki yere birden
gider ve silme hiç yapılmaz; ya da bir adım daha ileri gidilip yazma önce önbelleğe, depoya sonra
toplu olarak gönderilir. Birincisine **içinden yazma** (write-through), ikincisine **arkadan
yazma** (write-behind) denir. Bu ders iki düzeni ölçer.

İki stratejinin mekaniği Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda kuruldu ve
ölçüldü; burada yeniden anlatılmaz. Buradaki soru ölçekleme sorusudur: yazma yolu kaç yere
uzuyor, olay başına ne kadar ek iş çıkıyor ve K01'in hangi satırı oynuyor.

#### İkinci Yazma Hedefi

Bu konunun kurduğu bir sayı bu derste değişir. Önceki konu okuma yolunu kısaltmak için
denormalizasyon uygulamıştı: bölge başına dağıtımdaki gönderi sayısı, sorgu anında sayılmak
yerine ayrı bir sayaçta tutulur. O karar okuma tarafında kazanç sağladı, ama yazma tarafında
sessiz bir borç bıraktı — her durum olayı artık **iki** yeri günceller: gönderinin takip kaydı ve
gönderinin bulunduğu bölgenin sayacı.

K01'in tablosunda yalnız bir yazma vardı; `tepe yazma istek/s` 97,22, bir olayın bir kayda
yazıldığı varsayımıyla hesaplanmıştı. Yazma hedefi ikiye çıktığında olay hızı değişmez ama depo
yazma hızı değişir. Bu dersin ilk ölçüsü bu yüzden **yazma yolunun uzunluğu**dur: bir olayın
kaç yere dokunduğu.

**B1 (bu dersin varsayımı): taşıyıcı ağı 40 dağıtım bölgesine ayrılmıştır.** Gerekçe: rota
adımları bölge düzeyinde raporlanır ve V5'teki takip yanıtı bölgeyi bir alan olarak taşır.
Duyarlılık son ölçümde yazılıdır: bölge sayısı büyüdükçe toplu aktarımdaki birleşme oranı düşer.
Varsayım K01'in tablosuna eklenmez.

```js
// onbellek/yazma.mjs — yazma yolunun onbellege baglandigi duzenlerin surec ici modeli.
// Onbellek, depo ve olay akisi birer modul; sayaclar aciktir. Toplu pencere ve bolge
// sayisi parametredir. Sureler olculmez, islem sayilir.
export function kutuAc(kapasite) {
  const m = new Map();
  const o = {
    tahliye: 0,
    var: (a) => m.has(a),
    al: (a) => { const d = m.get(a); if (d !== undefined) { m.delete(a); m.set(a, d); } return d; },
    koy: (a, d) => { m.delete(a); m.set(a, d); if (m.size > kapasite) { m.delete(m.keys().next().value); o.tahliye += 1; } },
    sil: (a) => m.delete(a),
  };
  return o;
}

// Her durum olayi iki yeri gunceller: gonderinin takip kaydi ve bolgenin dagitim sayaci.
export function akis({ okuma, oran, calismaKumesi, obek, bolge }) {
  let tohum = 20260730;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  let sonraki = 1;
  const etkin = Array.from({ length: calismaKumesi }, () => ({ no: sonraki++, kalan: obek }));
  const cikti = [];
  let borc = 0;
  for (let i = 0; i < okuma; i++) {
    const j = Math.floor(rast() * etkin.length);
    const g = etkin[j];
    cikti.push(["oku", `takip:${g.no}`]);
    if ((g.kalan -= 1) === 0) etkin[j] = { no: sonraki++, kalan: obek };
    for (borc += oran; borc >= 1; borc -= 1) {
      const h = etkin[Math.floor(rast() * etkin.length)].no;
      cikti.push(["yaz", `takip:${h}`, `bolge:${Math.floor(rast() * bolge) + 1}`]);
    }
  }
  return cikti;
}

// takipYazma: "sil" (yaninda okuma) ya da "guncelle" (icinden yazma).
// bolgeYazma: "icinden" ya da "arkadan"; arkadan yazmada toplu pencere `topluPencere` yazmadir.
export function kosum(islemler, { kapasite, takipYazma, bolgeYazma, topluPencere = 200 }) {
  const c = kutuAc(kapasite);
  const bolgeC = new Map();
  const bekleyen = new Map();                 // arkadan yazmada depoya gitmemis bolge sayaci
  const s = { isabet: 0, depoOkuma: 0, depoYazma: 0, dokunus: 0, yazmaDokunus: 0, bayat: 0, bekleyenOlay: 0 };
  const dogru = new Map();
  let sayim = 0, olayBorcu = 0, enCokBekleyenOlay = 0;
  for (const [tur, anahtar, bolge] of islemler) {
    if (!dogru.has(anahtar)) dogru.set(anahtar, 0);
    if (tur === "oku") {
      s.dokunus += 1;
      if (c.var(anahtar)) { s.isabet += 1; if (c.al(anahtar) !== dogru.get(anahtar)) s.bayat += 1; continue; }
      s.depoOkuma += 1; s.dokunus += 2;
      c.koy(anahtar, dogru.get(anahtar));
      continue;
    }
    const deger = dogru.get(anahtar) + 1;
    dogru.set(anahtar, deger);
    s.depoYazma += 1; s.yazmaDokunus += 1;                  // takip kaydi her zaman depoya gider
    s.yazmaDokunus += 1;
    if (takipYazma === "sil") c.sil(anahtar); else c.koy(anahtar, deger);
    bolgeC.set(bolge, (bolgeC.get(bolge) ?? 0) + 1);        // bolge sayaci once onbellekte
    s.yazmaDokunus += 1;
    if (bolgeYazma === "icinden") { s.depoYazma += 1; s.yazmaDokunus += 1; continue; }
    bekleyen.set(bolge, bolgeC.get(bolge));
    olayBorcu += 1;
    enCokBekleyenOlay = Math.max(enCokBekleyenOlay, olayBorcu);
    if ((sayim += 1) % topluPencere === 0) {
      s.depoYazma += bekleyen.size; s.yazmaDokunus += bekleyen.size;
      bekleyen.clear(); olayBorcu = 0;
    }
  }
  s.bekleyenOlay = olayBorcu;
  return { ...s, bekleyenGirdi: bekleyen.size, enCokBekleyenOlay };
}

// M19/K01 kabaca buyukluk hesabinin girdileri (V1-V4, V8).
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,
  tepeYazma: (400_000 * 7 / 86_400) * 3,
};
```

Model süreç içidir. Zaman ölçülmez, işlem sayılır; toplu aktarım bir süreyle değil bir yazma
sayısıyla tetiklenir, çünkü süre bu makineye bağlı olurdu ve sayım değildir.

#### Üç Düzen

```js
// onbellek/duzen.mjs — uc yazma duzeninin ayni akista karsilastirilmasi
import { akis, kosum, K01 } from "./yazma.mjs";

const OKUMA = 200_000, KAPASITE = 2000, BOLGE = 40, PENCERE = 200;
const ORAN = K01.tepeYazma / K01.tepeOkuma;
const islemler = akis({ okuma: OKUMA, oran: ORAN, calismaKumesi: 1000, obek: 10, bolge: BOLGE });
const OLAY = islemler.filter(([t]) => t === "yaz").length;

console.log(`okuma ${OKUMA}, durum olayi ${OLAY}, bolge ${BOLGE}, toplu pencere ${PENCERE} yazma`);
console.log(`K01: tepe okuma ${K01.tepeOkuma.toFixed(2)}/s, tepe yazma ${K01.tepeYazma.toFixed(2)}/s\n`);

const DUZEN = [
  ["yaninda okuma + bolge icinden", { takipYazma: "sil", bolgeYazma: "icinden" }],
  ["icinden yazma + bolge icinden", { takipYazma: "guncelle", bolgeYazma: "icinden" }],
  ["icinden yazma + bolge arkadan", { takipYazma: "guncelle", bolgeYazma: "arkadan" }],
];

console.log("duzen                          isabet  depo okuma  depo yazma  yazma/olay  yol uzunlugu  depoya ulasan/s  depoda yazma/okuma  bekleyen olay");
console.log("------------------------------ ------ ----------- ----------- ----------- ------------- ---------------- ------------------- -------------");
for (const [ad, secim] of DUZEN) {
  const r = kosum(islemler, { kapasite: KAPASITE, topluPencere: PENCERE, ...secim });
  const h = r.isabet / OKUMA;
  const arka = K01.tepeOkuma * (1 - h);
  const yazmaS = K01.tepeYazma * (r.depoYazma / OLAY);
  console.log(`${ad.padEnd(30)} ${h.toFixed(4).padStart(6)} ${String(r.depoOkuma).padStart(11)} ` +
    `${String(r.depoYazma).padStart(11)} ${(r.depoYazma / OLAY).toFixed(2).padStart(11)} ` +
    `${(r.yazmaDokunus / OLAY).toFixed(2).padStart(13)} ` +
    `${(arka + yazmaS).toFixed(2).padStart(16)} ${(yazmaS / arka).toFixed(2).padStart(19)} ` +
    `${String(r.enCokBekleyenOlay).padStart(13)}`);
}

const arkadan = kosum(islemler, { kapasite: KAPASITE, topluPencere: PENCERE, takipYazma: "guncelle", bolgeYazma: "arkadan" });
console.log(`\nayni kural takip kaydina da uygulansaydi kayip penceresi:`);
console.log(`  bellekte bekleyen olay = ${arkadan.enCokBekleyenOlay}`);
console.log(`  olay kaydi 220 bayt (V6) -> ${arkadan.enCokBekleyenOlay * 220} bayt yazilmamis`);
console.log(`  tepe yazma ${K01.tepeYazma.toFixed(2)}/s -> ${(arkadan.enCokBekleyenOlay / K01.tepeYazma).toFixed(2)} saniyelik olay akisi`);

console.log("\ntoplu pencerede ayni anahtara dusen yazma sayisi birlesme oranidir:");
console.log("bolge sayisi  depo yazma  yazma/olay  depoya ulasan/s");
for (const bolge of [40, 200, 1000]) {
  const a = akis({ okuma: OKUMA, oran: ORAN, calismaKumesi: 1000, obek: 10, bolge });
  const r = kosum(a, { kapasite: KAPASITE, topluPencere: PENCERE, takipYazma: "guncelle", bolgeYazma: "arkadan" });
  const arka = K01.tepeOkuma * (1 - r.isabet / OKUMA);
  const yazmaS = K01.tepeYazma * (r.depoYazma / OLAY);
  console.log(`${String(bolge).padStart(12)} ${String(r.depoYazma).padStart(11)} ` +
    `${(r.depoYazma / OLAY).toFixed(2).padStart(11)} ${(arka + yazmaS).toFixed(2).padStart(16)}`);
}
```

```
okuma 200000, durum olayi 46666, bolge 40, toplu pencere 200 yazma
K01: tepe okuma 416.67/s, tepe yazma 97.22/s

duzen                          isabet  depo okuma  depo yazma  yazma/olay  yol uzunlugu  depoya ulasan/s  depoda yazma/okuma  bekleyen olay
------------------------------ ------ ----------- ----------- ----------- ------------- ---------------- ------------------- -------------
yaninda okuma + bolge icinden  0.7285       54308       93332        2.00          4.00           307.59                1.72             0
icinden yazma + bolge icinden  0.9170       16592       93332        2.00          4.00           229.01                5.63             0
icinden yazma + bolge arkadan  0.9170       16592       55908        1.20          3.20           151.04                3.37           200

ayni kural takip kaydina da uygulansaydi kayip penceresi:
  bellekte bekleyen olay = 200
  olay kaydi 220 bayt (V6) -> 44000 bayt yazilmamis
  tepe yazma 97.22/s -> 2.06 saniyelik olay akisi

toplu pencerede ayni anahtara dusen yazma sayisi birlesme oranidir:
bolge sayisi  depo yazma  yazma/olay  depoya ulasan/s
          40       55908        1.20           151.04
         200       75983        1.63           192.87
        1000       88862        1.90           219.70
```

Bütün sayılar **hesap** sınıfındadır: belirlenimli bir akış üzerinde sayılmışlardır ve süre
ölçülmemiştir.

#### Sayıların Okunması

**İçinden yazma okuma tarafını düzeltiyor.** Silme kalkınca isabet 0,7285'ten 0,9170'e çıktı ve
depo okuması 54.308'den 16.592'ye indi. Dikkat çeken şey, 0,9170'in yazmasız koşumun 0,8976'sının
da **üstünde** olmasıdır: yazma yalnız girdiyi düşürmemekle kalmıyor, onu ısıtıyor da. Bir gönderi
olay ürettiği anda önbelleğe giriyor ve sonraki ilk sorgu ıska almıyor. V9'un 0,90'ı burada ilk
kez aşılıyor ve nedeni bir varsayım değil, yazma yolunun bağlanmasıdır.

**Yazma yolu iki depoya uzamış durumda.** İlk iki düzende olay başına depo yazması 2,00; yol
uzunluğu 4,00 dokunuş (iki depo yazması, bir takip önbelleği güncellemesi, bir bölge sayacı
güncellemesi). Bu, K01'in görmediği bir maliyettir: tablo bir olayın bir kayda yazıldığını
varsayıyordu. İkinci yazma hedefi denormalizasyon kararının bedelidir ve `depoya ulasan istek/s`
satırını yukarı çeker.

**Arkadan yazma yazma tarafını düzeltiyor.** Bölge sayacı önce önbellekte tutulup her 200
yazmada bir toplu aktarıldığında olay başına depo yazması 2,00'dan **1,20**'ye iniyor. Aynı
bölgeye düşen ardışık artışlar tek bir güncellemede birleşiyor. `depoya ulasan istek/s` 229,01'den
**151,04**'e düşüyor ve `depoda yazma/okuma orani` 5,63'ten 3,37'ye iniyor.

Üç düzenin `depoya ulasan istek/s` sütunu birlikte okunmalıdır: 307,59 → 229,01 → 151,04. K01'in
hesabındaki 138,89 bunların hiçbirine eşit değil, çünkü o hesap ne geçersizleştirmeyi ne ikinci
yazma hedefini biliyordu. 151,04, tasarım bittikten sonra o satırın gerçek değeridir.

**Birleşme oranı anahtar uzayına bağlı.** Son tablo kuralı veriyor: toplu pencerede aynı anahtara
kaç yazma düşerse o kadar kazanılır. Kırk bölgede 200 yazmalık pencere olay başına 1,20 depo
yazması bırakıyor; bölge sayısı 1000'e çıktığında pencere içinde neredeyse her yazma ayrı bir
anahtara düşüyor ve oran 1,90'a, `depoya ulasan istek/s` 219,70'e yükseliyor. Arkadan yazma
küçük ve sıcak bir anahtar uzayında kazandırır; geniş bir uzayda yalnız gecikme ekler.

#### Hangi Veri Hangi Düzene

Arkadan yazmanın bedeli son üç satırda yazılı. Toplu pencere 200 yazma olduğuna göre bellekte
her an 200 olaya kadar yazılmamış değişiklik bulunur; tepe yazma hızında bu 2,06 saniyelik olay
akışıdır. Süreç o anda dursa bu değişiklikler kaybolur.

Bölge sayacı için kayıp katlanılabilir, çünkü sayaç **türetilmiş** bir değerdir: olay kayıtları
depodadır ve sayaç onlardan yeniden hesaplanabilir. Takip kaydı ve olay kaydının kendisi için
katlanılamaz. V6'ya göre bir olay kaydı 220 bayttır ve V12'ye göre 730 gün saklanır; saklama
süresinin gerekçesi sözleşme uyuşmazlıklarıdır. Kaybolan 200 olay 44.000 bayttır ve o baytların
hiçbir yerde ikinci bir kopyası yoktur.

Kural bu yüzden veriye göre yazılır, katmana göre değil: **kaynağı başka bir yerde duran veri**
arkadan yazmayla yazılabilir, **kendisi kaynak olan veri** yazılamaz. Aynı önbellek katmanında
iki anahtar iki ayrı düzenle çalışır ve bu bir tutarsızlık değil, bir yerleşim kararıdır.

#### Özet

- Denormalizasyonun eklediği bölge sayacı yazma yolunu iki depoya uzattı: olay başına depo
  yazması 2,00, yol uzunluğu 4,00 dokunuş. K01'in tablosu bir olayın tek bir kayda yazıldığını
  varsayıyordu.
- İçinden yazma silmeyi kaldırınca isabet 0,7285'ten 0,9170'e çıktı — V9'un 0,90'ının üstüne —
  çünkü yazma girdiyi düşürmek yerine ısıtıyor; depo okuması 54.308'den 16.592'ye indi.
- Arkadan yazma bölge sayacında olay başına depo yazmasını 1,20'ye indirdi; `depoya ulasan
  istek/s` 229,01'den 151,04'e, `depoda yazma/okuma orani` 5,63'ten 3,37'ye indi.
- Birleşme oranı toplu pencerede aynı anahtara düşen yazma sayısıdır: 40 bölgede olay başına
  1,20 depo yazması, 1000 bölgede 1,90 ve `depoya ulasan istek/s` 219,70.
- Arkadan yazmanın kayıp penceresi 200 olay, 44.000 bayt ve 2,06 saniyelik akıştır; kaynağı
  başka yerde duran türetilmiş veride kabul edilir, kendisi kaynak olan olay kaydında edilmez.

#### Sonraki Adım

Bu derste yazma, önbellekteki girdiyi ayakta tuttu; ama yazma gelmeyen bir anahtarın girdisi
yalnız bir nedenle düşer: süresi dolar. Süre dolduğu anda o anahtarı soran istek ıska alır,
depoya gider ve bekler — üstelik anahtar sıcaksa aynı anda birçok istek aynı şeyi yapar. Süre
dolmasını beklemek zorunlu değildir: girdi hâlâ geçerliyken, ömrünün sonuna yaklaştığında arka
planda yenilenebilir ve okuyucu hiç ıska görmez. Bu yenileme bedava değildir; yenilenen girdilerin
bir kısmı bir daha hiç okunmaz ve o tazelemeler doğrudan depoya yazılmış boş yüktür. Sonraki ders
eşiği bir parametre olarak oynatır ve iki sayıyı karşı karşıya koyar: kurtarılan ıska ile boşa
giden tazeleme.

### Önden Tazeleme

Source: https://academia.sh/tr/kurslar/veri-olcekleme/onden-tazeleme

Girdinin süresi dolmadan arka planda yenilenmesinin ölçülmesi: yaşam süresinin isabeti 0,9170'ten 0,7109'a indirmesi, eşik oranı büyüdükçe bekleyen isteğin azalıp boşa tazelemenin artması, eşik 1 alındığında isabet 0,8286 iken depo yükünün önbelleksiz tasarımın 513,89 istek/s'sine eşitlenmesi ve asıl kaldıracın eşik değil yaşam süresi olduğunun gösterilmesi.

Önceki derste yazma, önbellekteki girdiyi ayakta tuttu. Yazma gelmeyen bir anahtarın girdisi ise
yalnız bir nedenle düşer: süresi dolar. Yaşam süresi bu tasarımda isteğe bağlı bir ayar değil,
bir güvenlik ağıdır — bu konunun ikinci dersi, kaybolan bir geçersizleştirme iletisinin hiçbir
sayaçta görünmeden bayat bir girdi bıraktığını ölçmüştü. Girdiye bir üst sınır süresi konmasının
nedeni budur.

O sınırın bir bedeli var ve bu dersin ilk sayısı odur. Süresi dolduğu anda o anahtarı soran
istek ıska alır, depoya gider ve **bekler**. Beklemeyi ortadan kaldırmanın bir yolu vardır:
girdi hâlâ geçerliyken, ömrünün sonuna yaklaştığında arka planda yenilenir. Buna **önden
tazeleme** (refresh-ahead) denir. Bu ders eşiği bir parametre olarak oynatır ve iki sayıyı karşı
karşıya koyar: kurtarılan bekleme ile boşa giden tazeleme.

#### Saat ve Ömür

Model süreç içidir ve zamanı bir sayaçla temsil eder: her okuma saati bir tik ilerletir. Tikin
saniyeye çevrilmesi K01'in tepe okuma hızıyla yapılır — 416,67 istek/s olduğuna göre bir saniye
416,67 tiktir. Gerçek süre ölçülmez; ölçülen her şey sayımdır ve koşumdan koşuma değişmez.

Yaşam süresi bu derste 5 saniye alınmıştır. **B1 (bu dersin varsayımı): takip kaydının önbellek
girdisine verilen üst sınır süresi 5 saniyedir.** Gerekçe: süre, kaybolan bir geçersizleştirmenin
en fazla ne kadar sürebileceğini sınırlar ve bu sürenin ne olması gerektiği bu konunun son
dersinin konusudur; burada sabit tutulur ki tek değişken eşik olsun. Duyarlılık son ölçümde
yazılıdır. Varsayım K01'in tablosuna eklenmez.

```js
// onbellek/tazele.mjs — onden tazelemenin surec ici modeli. Saat bir sayactir: her okuma
// bir tik ilerletir ve K01'in tepe okuma hizina gore saniyeye cevrilir. Yasam suresi, esik
// orani ve secim kurali parametredir; sure olculmez, islem sayilir.
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,
  tepeYazma: (400_000 * 7 / 86_400) * 3,
};

export function akis({ okuma, oran, calismaKumesi, obek }) {
  let tohum = 20260730;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  let sonraki = 1;
  const etkin = Array.from({ length: calismaKumesi }, () => ({ no: sonraki++, kalan: obek }));
  const cikti = [];
  let borc = 0;
  for (let i = 0; i < okuma; i++) {
    const j = Math.floor(rast() * etkin.length);
    const g = etkin[j];
    cikti.push(["oku", `takip:${g.no}`]);
    if ((g.kalan -= 1) === 0) etkin[j] = { no: sonraki++, kalan: obek };
    for (borc += oran; borc >= 1; borc -= 1)
      cikti.push(["yaz", `takip:${etkin[Math.floor(rast() * etkin.length)].no}`]);
  }
  return cikti;
}

// esik: girdinin kalan omru yasam suresinin bu orani altina duserse okuma sirasinda
// arkadan tazelenir. esik = 0 onden tazeleme yok demektir.
// enAzGorulme: yalniz bu kadar kez okunmus anahtarlar tazelenir (secici tazeleme).
export function kosum(islemler, { kapasite, omur, esik, enAzGorulme = 0 }) {
  const kutu = new Map();                 // anahtar -> { biter, tazelendi }
  const s = { isabet: 0, bekleyen: 0, tazeleme: 0, bosaTazeleme: 0, depoOkuma: 0, depoYazma: 0 };
  const gorulme = new Map();
  let saat = 0;
  const dus = (anahtar) => {
    const g = kutu.get(anahtar);
    if (g && g.tazelendi) s.bosaTazeleme += 1;
    kutu.delete(anahtar);
  };
  const koy = (anahtar, tazelendi) => {
    kutu.delete(anahtar);
    kutu.set(anahtar, { biter: saat + omur, tazelendi });
    if (kutu.size > kapasite) dus(kutu.keys().next().value);
  };
  for (const [tur, anahtar] of islemler) {
    if (tur === "yaz") { s.depoYazma += 1; if (kutu.has(anahtar)) koy(anahtar, false); continue; }
    saat += 1;
    gorulme.set(anahtar, (gorulme.get(anahtar) ?? 0) + 1);
    const g = kutu.get(anahtar);
    if (g === undefined || g.biter <= saat) {
      if (g !== undefined) dus(anahtar);            // suresi dolmus girdi: tazelendiyse bosa gitti
      s.bekleyen += 1; s.depoOkuma += 1;
      koy(anahtar, false);
      continue;
    }
    s.isabet += 1;
    if (g.tazelendi) g.tazelendi = false;           // tazelenen deger okundu: bosa gitmedi
    if (esik > 0 && g.biter - saat <= omur * esik && gorulme.get(anahtar) >= enAzGorulme) {
      s.tazeleme += 1; s.depoOkuma += 1;
      koy(anahtar, true);
    } else {
      kutu.delete(anahtar); kutu.set(anahtar, g);   // en son kullanilan sona alinir
    }
  }
  for (const anahtar of [...kutu.keys()]) dus(anahtar);
  return s;
}
```

**Boşa tazeleme**, yenilenen bir girdinin bir daha okunmadan düşmesidir. Model bunu bir bayrakla
sayar: tazelenen girdi okunursa bayrak silinir, girdi okunmadan süresi dolar ya da tahliye
edilirse boşa tazeleme sayacı artar. Yazma yolu önceki dersin kararını sürdürür — içinden yazma —
bu yüzden yazma girdiyi düşürmez, ömrünü yeniden başlatır.

#### Eşiğin Oynatılması

```js
// onbellek/esik.mjs — esik orani oynatilinca kurtarilan bekleme ile bosa giden tazeleme
import { akis, kosum, K01 } from "./tazele.mjs";

const OKUMA = 200_000, KAPASITE = 2000;
const ORAN = K01.tepeYazma / K01.tepeOkuma;
const OMUR_SN = 5, OMUR = Math.round(OMUR_SN * K01.tepeOkuma);   // tik cinsinden yasam suresi
const islemler = akis({ okuma: OKUMA, oran: ORAN, calismaKumesi: 1000, obek: 10 });

console.log(`okuma ${OKUMA}, kapasite ${KAPASITE}, yasam suresi ${OMUR_SN} s = ${OMUR} okuma tiki`);
console.log(`bir tik = 1/${K01.tepeOkuma.toFixed(2)} s (K01 tepe okuma hizi)\n`);
console.log("esik  isabet  bekleyen  tazeleme  bosa tazeleme  bosa orani  depo okuma  onbellek arkasi/s  depoya ulasan/s");
console.log("----- ------ --------- --------- -------------- ----------- ----------- ------------------ ---------------");
for (const esik of [0, 0.1, 0.25, 0.5, 1]) {
  const r = kosum(islemler, { kapasite: KAPASITE, omur: OMUR, esik });
  const arka = K01.tepeOkuma * (r.depoOkuma / OKUMA);
  console.log(`${esik.toFixed(2).padStart(5)} ${(r.isabet / OKUMA).toFixed(4).padStart(6)} ` +
    `${String(r.bekleyen).padStart(9)} ${String(r.tazeleme).padStart(9)} ${String(r.bosaTazeleme).padStart(14)} ` +
    `${(r.tazeleme === 0 ? 0 : r.bosaTazeleme / r.tazeleme).toFixed(4).padStart(11)} ` +
    `${String(r.depoOkuma).padStart(11)} ${arka.toFixed(2).padStart(18)} ${(arka + K01.tepeYazma).toFixed(2).padStart(15)}`);
}

console.log("\nbosa tazeleme cok okunan anahtari elemekle azalmiyor (esik 0,25):");
console.log("en az gorulme  tazeleme  bosa tazeleme  bosa orani");
for (const enAz of [0, 3, 5, 8]) {
  const r = kosum(islemler, { kapasite: KAPASITE, omur: OMUR, esik: 0.25, enAzGorulme: enAz });
  console.log(`${String(enAz).padStart(13)} ${String(r.tazeleme).padStart(9)} ${String(r.bosaTazeleme).padStart(14)} ` +
    `${(r.bosaTazeleme / r.tazeleme).toFixed(4).padStart(11)}`);
}

console.log("\nasil kaldirac yasam suresidir (esik 0 ile 0,25 yan yana):");
console.log("omur (s)  esik 0 isabet  esik 0 depo  esik 0,25 isabet  esik 0,25 depo  esik 0,25 bekleyen  depoya ulasan/s");
for (const sn of [2, 5, 15, 60]) {
  const o = Math.round(sn * K01.tepeOkuma);
  const a = kosum(islemler, { kapasite: KAPASITE, omur: o, esik: 0 });
  const b = kosum(islemler, { kapasite: KAPASITE, omur: o, esik: 0.25 });
  const arka = K01.tepeOkuma * (b.depoOkuma / OKUMA);
  console.log(`${String(sn).padStart(8)} ${(a.isabet / OKUMA).toFixed(4).padStart(14)} ${String(a.depoOkuma).padStart(12)} ` +
    `${(b.isabet / OKUMA).toFixed(4).padStart(17)} ${String(b.depoOkuma).padStart(15)} ${String(b.bekleyen).padStart(19)} ` +
    `${(arka + K01.tepeYazma).toFixed(2).padStart(16)}`);
}
```

```
okuma 200000, kapasite 2000, yasam suresi 5 s = 2083 okuma tiki
bir tik = 1/416.67 s (K01 tepe okuma hizi)

esik  isabet  bekleyen  tazeleme  bosa tazeleme  bosa orani  depo okuma  onbellek arkasi/s  depoya ulasan/s
----- ------ --------- --------- -------------- ----------- ----------- ------------------ ---------------
 0.00 0.7109     57815         0              0      0.0000       57815             120.45          217.67
 0.10 0.7363     52731      9780           1862      0.1904       62511             130.23          227.45
 0.25 0.7660     46806     23922           4120      0.1722       70728             147.35          244.57
 0.50 0.7994     40128     50540           8728      0.1727       90668             188.89          286.11
 1.00 0.8286     34270    165730          27825      0.1679      200000             416.67          513.89

bosa tazeleme cok okunan anahtari elemekle azalmiyor (esik 0,25):
en az gorulme  tazeleme  bosa tazeleme  bosa orani
            0     23922           4120      0.1722
            3     22693           4035      0.1778
            5     17116           3662      0.2140
            8      8524           3075      0.3607

asil kaldirac yasam suresidir (esik 0 ile 0,25 yan yana):
omur (s)  esik 0 isabet  esik 0 depo  esik 0,25 isabet  esik 0,25 depo  esik 0,25 bekleyen  depoya ulasan/s
       2         0.4884       102320            0.5263          116205               94735           339.32
       5         0.7109        57815            0.7660           70728               46806           244.57
      15         0.8686        26288            0.8884           31562               22315           162.98
      60         0.8976        20482            0.8977           20510               20461           139.95
```

Bütün sayılar **hesap** sınıfındadır: belirlenimli bir akış üzerinde sayılmışlardır.

#### Sayıların Okunması

İlk satır güvenlik ağının faturasını veriyor. Önceki ders içinden yazmayla 0,9170 isabet
ölçmüştü; oraya beş saniyelik bir üst sınır süresi konunca isabet **0,7109**'a düşüyor ve
`depoya ulasan istek/s` 151,04'ten 217,67'ye çıkıyor. Kaybolan geçersizleştirmelere karşı
konan sınır, isabetin beşte birine mal oluyor.

Eşik büyüdükçe iki sütun ters yönlere gidiyor. Bekleyen istek 57.815'ten 34.270'e iniyor; buna
karşılık tazeleme sayısı sıfırdan 165.730'a çıkıyor ve bunun 27.825'i boşa gidiyor. **Boşa oranı
eşikten neredeyse bağımsız**: beş satırın dördünde 0,17 ile 0,19 arasında. Bunun nedeni yapısaldır
— bir tazelemenin boşa gitmesi, o anahtarın bir daha okunmamasıdır ve bu, öbeğin son sorgusunda
kaçınılmaz olarak olur. Okunan hiçbir sayaç bir sorgunun sonuncu olduğunu önceden söyleyemez.

İkinci tablo bunu doğruluyor. "Az okunan anahtarları tazeleme" kuralı sezgisel olarak boşa
tazelemeyi azaltmalıydı; ölçüm tersini söylüyor. Eşik sekize çekildiğinde tazeleme sayısı
23.922'den 8524'e iniyor ama boşa oranı 0,1722'den **0,3607**'ye çıkıyor. Elenen tazelemeler
zaten işe yarayanlardı: çok okunmuş bir anahtar öbeğinin sonuna daha yakındır. İsraf, erişimin
başında değil sonundadır ve görülme sayısıyla ayrılamaz.

Son satır bu dersin en sert sayısını taşıyor. Eşik 1 alındığında — yani her okuma bir tazeleme
tetiklediğinde — isabet oranı **0,8286** gibi saygıdeğer bir yerde duruyor, ama depo okuması
200.000, yani istek sayısının tamamı. `depoya ulasan istek/s` **513,89**: bu, bu konunun ilk
dersinde önbelleksiz tasarım için hesaplanan sayının aynısıdır. Önbellek çalışıyor, isabet
sayacı doluyor, gösterge iyi görünüyor ve depo tam yükü taşıyor. **Yüksek isabet oranı, depo
yükü hakkında tek başına hiçbir şey söylemez.**

#### Asıl Kaldıraç

Üçüncü tablo eşiği ömürle yan yana koyuyor ve sıralamayı tersine çeviriyor. Ömür 2 saniyeden 60
saniyeye çıktığında eşiksiz isabet 0,4884'ten 0,8976'ya, `depoya ulasan istek/s` ise 339,32'den
139,95'e iniyor. Aynı aralıkta eşiğin katkısı sürekli küçülüyor: 2 saniyede isabeti 0,0379
artırıyor, 60 saniyede yalnız 0,0001. Ömür 60 saniyeyken önden tazeleme 28 fazladan depo okuması
yapıp bekleyen isteği 20.482'den 20.461'e indiriyor — ölçülebilir ama anlamsız bir kazanç.

Kural buradan çıkıyor: **önden tazeleme, yaşam süresinin erişim öbeğine göre kısa kaldığı
durumda beklemeyi azaltır.** Ömür öbekten uzunsa girdi zaten süresi dolmadan yeniden okunur ve
tazelenecek bir şey yoktur. Ömür kısaysa tazeleme işe yarar, ama kurtardığı her bekleme için
depoya fazladan okuma bindirir ve o okumaların yaklaşık altıda biri boşa gider.

Bu, önden tazelemeyi bir **gecikme** aracı yapar, bir yük azaltma aracı değil. K01'in
`depoya ulasan istek/s` satırı bu derste her seferinde **yukarı** gitti. Bir tasarım kararının
hesabı iyileştirmesi zorunlu değildir; ölçülmesi ve ödünleşiminin yazılması zorunludur.

#### Özet

- Kaybolan geçersizleştirmelere karşı konan beş saniyelik üst sınır süresi isabeti 0,9170'ten
  0,7109'a indirdi ve `depoya ulasan istek/s` satırını 151,04'ten 217,67'ye çıkardı.
- Eşik 0'dan 1'e çıktığında bekleyen istek 57.815'ten 34.270'e indi, tazeleme 165.730'a çıktı ve
  bunun 27.825'i boşa gitti; boşa oranı eşikten bağımsız biçimde 0,17 civarında kaldı.
- Az okunan anahtarları elemek boşa tazelemeyi azaltmıyor, oranı 0,1722'den 0,3607'ye çıkarıyor:
  israf erişimin sonundadır ve görülme sayısıyla ayrılamaz.
- Eşik 1 alındığında isabet 0,8286 iken depo okuması 200.000 ve `depoya ulasan istek/s` 513,89
  oluyor — önbelleksiz tasarımın sayısı. Yüksek isabet oranı depo yükü hakkında bir şey söylemez.
- Asıl kaldıraç yaşam süresidir: 2 saniyeden 60 saniyeye çıkınca isabet 0,4884'ten 0,8976'ya,
  `depoya ulasan istek/s` 339,32'den 139,95'e iniyor ve önden tazelemenin katkısı 0,0001'e düşer.

#### Sonraki Adım

Bu dersin son sayısı rahatsız edici bir soru bıraktı: isabet oranı 0,8286 iken depo, önbellek hiç
yokmuş gibi 513,89 istek/s taşıyordu. Demek ki çalışan bir önbellek, hiç önbellek olmamasıyla
aynı yükü üretebiliyor. O zaman ters yönden sorulması gereken bir soru var ve bu konuda henüz
hiç sorulmadı: önbellek gerçekten gerekli mi. Buraya kadar her ders bir önbelleğin var olduğunu
varsaydı ve yalnız onun nasıl düzenleneceğini tartıştı. Sıradaki ders varsayımı kaldırır: hangi
erişim örüntülerinde önbellek eklemek `depoya ulasan istek/s` satırını hiç değiştirmez, hangi
örüntülerde onu **yükseltir** ve önbelleksiz bir tasarımın gerçekten karşıt kalıp olduğu sınır
nerede başlar.

### Önbelleksiz Tasarım Karşıt Kalıbı

Source: https://academia.sh/tr/kurslar/veri-olcekleme/onbelleksiz-tasarim-karsit-kalibi

Önbelleğin gerekli olup olmadığının erişim örüntüsünden karara bağlanması: takip sorgusunda önbelleksiz tasarımın depoya 513,89 istek/s bindirmesi, dönem taramasında önbelleğin isabetsiz kalıp istek başına 2,00 ek dokunuş getirmesi, sıcak yazmada 5000 kat tekrarın 0,3001 isabete düşmesi ve toplu taramanın aynı önbellekten geçirilmesinin takip isabetini 0,4263 düşürüp depo erişimini 85.252 artırması.

Önceki dersin son sayısı rahatsız edici bir soru bıraktı: isabet oranı 0,8286 iken depo, önbellek
hiç yokmuş gibi 513,89 istek/s taşıyordu. Buraya kadar her ders bir önbelleğin var olduğunu
varsaydı ve yalnız onun nasıl düzenleneceğini tartıştı. Bu ders varsayımı kaldırır ve soruyu ters
yönden sorar: önbellek gerçekten gerekli mi.

Sorunun iki yanlış yanıtı vardır ve ikisi de aynı kusurdan doğar. Birincisi **önbelleksiz
tasarım karşıt kalıbı**: erişim örüntüsü tekrarla dolu olduğu hâlde her okumanın depoya
gitmesi. İkincisi bunun aynası: tekrar barındırmayan bir yola önbellek konması. İkisi de erişim
örüntüsünün ölçülmemesinin sonucudur, bu yüzden bu ders örüntüyü ölçer.

#### Dört Örüntü

Model süreç içidir: aynı önbellek dört ayrı akışla koşturulur, sayaçlar açıktır. Akışların hepsi
K01'in üç akışından türetilmiştir — takip sorgusu, durum olayı yazması ve gün sonu ücretlendirme
taraması — ve hızları K01'in hesabından gelir: tepe okuma 416,67 istek/s, toplu tarama 833,33
kayıt/s. Pencere yine 480 saniyedir.

```js
// onbellek/oruntu.mjs — ayni onbellek dort erisim oruntusuyle kosturulur. Onbellek ve
// depo birer modul; sayaclar aciktir. Oruntuler K01'in uc akisindan turetilmistir.
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,      // 416,67 istek/s
  tepeYazma: (400_000 * 7 / 86_400) * 3,        // 97,22 istek/s
  topluTarama: (30 * 400_000) / (4 * 3600),     // 833,33 kayit/s
};
export const PENCERE = 480;                      // saniye

function rastgeleUretec(tohum) {
  return () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
}

// Takip sorgusu: bir gonderinin sorgulari `obek` uzunlugunda gelir.
export function takipAkisi(istek, calismaKumesi = 1000, obek = 10) {
  const rast = rastgeleUretec(20260730);
  let sonraki = 1;
  const etkin = Array.from({ length: calismaKumesi }, () => ({ no: sonraki++, kalan: obek }));
  const cikti = [];
  for (let i = 0; i < istek; i++) {
    const j = Math.floor(rast() * etkin.length);
    cikti.push(["oku", `takip:${etkin[j].no}`]);
    if ((etkin[j].kalan -= 1) === 0) etkin[j] = { no: sonraki++, kalan: obek };
  }
  return cikti;
}

// Ucretlendirme taramasi: her satici-gun kaydi bir kez okunur, sirayla.
export function taramaAkisi(kayit) {
  return Array.from({ length: kayit }, (_, i) => ["oku", `donem:${i + 1}`]);
}

// Sicak yazma: kucuk bir anahtar uzayina okumadan cok yazma duser.
export function sicakYazmaAkisi(islem, anahtar = 40, yazmaPayi = 0.7) {
  const rast = rastgeleUretec(4242);
  return Array.from({ length: islem }, () => {
    const a = `bolge:${Math.floor(rast() * anahtar) + 1}`;
    return [rast() < yazmaPayi ? "yaz" : "oku", a];
  });
}

// Iki akisi sirayla ic ice gecirir: her `araN` takip istegi arasina `sokN` tarama istegi.
export function karistir(a, b, araN, sokN) {
  const cikti = [];
  let i = 0, j = 0;
  while (i < a.length || j < b.length) {
    for (let k = 0; k < araN && i < a.length; k++) cikti.push(a[i++]);
    for (let k = 0; k < sokN && j < b.length; k++) cikti.push(b[j++]);
  }
  return cikti;
}

// atla: bu ad alanindaki anahtarlar onbellege hic ugramaz.
export function kosum(islemler, { kapasite, atla = null }) {
  const m = new Map();
  const s = { istek: 0, okuma: 0, isabet: 0, depoOkuma: 0, depoYazma: 0, dokunus: 0, takipIstek: 0, takipIsabet: 0 };
  for (const [tur, anahtar] of islemler) {
    s.istek += 1;
    const takip = anahtar.startsWith("takip:");
    if (takip) s.takipIstek += 1;
    if (atla !== null && anahtar.startsWith(atla)) {
      if (tur === "oku") s.depoOkuma += 1; else s.depoYazma += 1;
      s.dokunus += 1;
      continue;
    }
    if (tur === "yaz") { s.depoYazma += 1; s.dokunus += 2; m.delete(anahtar); continue; }
    s.okuma += 1; s.dokunus += 1;
    if (m.has(anahtar)) {
      s.isabet += 1; if (takip) s.takipIsabet += 1;
      const d = m.get(anahtar); m.delete(anahtar); m.set(anahtar, d);
      continue;
    }
    s.depoOkuma += 1; s.dokunus += 2;
    m.set(anahtar, 1);
    if (m.size > kapasite) m.delete(m.keys().next().value);
  }
  return s;
}
```

```js
// onbellek/karar.mjs — dort oruntude onbellegin kazandirdigi ve kaybettirdigi
import { K01, PENCERE, takipAkisi, taramaAkisi, sicakYazmaAkisi, karistir, kosum } from "./oruntu.mjs";

const KAPASITE = 2000;
const OKUMA = Math.round(K01.tepeOkuma * PENCERE);
const TARAMA = Math.round(K01.topluTarama * PENCERE);

const takip = takipAkisi(OKUMA);
const tarama = taramaAkisi(TARAMA);
const sicak = sicakYazmaAkisi(OKUMA);

console.log(`pencere ${PENCERE} s: takip sorgusu ${OKUMA}, donem taramasi ${TARAMA} kayit, kapasite ${KAPASITE}`);
console.log(`(K01: tepe okuma ${K01.tepeOkuma.toFixed(2)}/s, toplu tarama ${K01.topluTarama.toFixed(2)} kayit/s)\n`);

const ORUNTU = [
  ["takip sorgusu", takip],
  ["donem taramasi", tarama],
  ["sicak yazma", sicak],
  ["takip + tarama", karistir(takip, tarama, 1, 2)],
];

console.log("oruntu           istek  okuma  tekil anahtar  tekrar  isabet  depo erisimi  onbelleksiz  fark  ek dokunus/istek");
console.log("--------------- ------ ------ -------------- ------- ------- ------------- ----------- ----- -----------------");
for (const [ad, akis] of ORUNTU) {
  const r = kosum(akis, { kapasite: KAPASITE });
  const tekil = new Set(akis.map(([, a]) => a)).size;
  const depo = r.depoOkuma + r.depoYazma;
  console.log(`${ad.padEnd(15)} ${String(r.istek).padStart(6)} ${String(r.okuma).padStart(6)} ${String(tekil).padStart(14)} ` +
    `${(r.istek / tekil).toFixed(2).padStart(7)} ${(r.isabet / r.okuma).toFixed(4).padStart(7)} ` +
    `${String(depo).padStart(13)} ${String(r.istek).padStart(11)} ${(depo - r.istek).toString().padStart(5)} ` +
    `${((r.dokunus - r.istek) / r.istek).toFixed(2).padStart(17)}`);
}

console.log("\ntoplu tarama ayni onbellekten gecerse takip sorgusuna ne olur:");
const birlikte = karistir(takip, tarama, 1, 2);
const gecen = kosum(birlikte, { kapasite: KAPASITE });
const atlayan = kosum(birlikte, { kapasite: KAPASITE, atla: "donem:" });
const yalniz = kosum(takip, { kapasite: KAPASITE });
const satir = (ad, r) => {
  const depo = r.depoOkuma + r.depoYazma;
  const takipS = K01.tepeOkuma * (1 - r.takipIsabet / r.takipIstek);
  console.log(`${ad.padEnd(28)} ${(r.takipIsabet / r.takipIstek).toFixed(4).padStart(14)} ` +
    `${String(depo).padStart(13)} ${(depo / PENCERE).toFixed(2).padStart(15)} ${(takipS + K01.tepeYazma).toFixed(2).padStart(16)}`);
};
console.log("durum                          takip isabeti  depo erisimi  depo erisimi/s  depoya ulasan/s");
console.log("---------------------------- --------------- ------------- --------------- ----------------");
satir("tarama yok", yalniz);
satir("tarama onbellekten geciyor", gecen);
satir("tarama onbellegi atliyor", atlayan);
console.log(`\ntaramanin onbellekten gecmesinin bedeli = ${(gecen.depoOkuma + gecen.depoYazma) - (atlayan.depoOkuma + atlayan.depoYazma)} ek depo erisimi`);
console.log(`takip isabetindeki kayip = ${((atlayan.takipIsabet / atlayan.takipIstek) - (gecen.takipIsabet / gecen.takipIstek)).toFixed(4)}`);
```

```
pencere 480 s: takip sorgusu 200000, donem taramasi 400000 kayit, kapasite 2000
(K01: tepe okuma 416.67/s, toplu tarama 833.33 kayit/s)

oruntu           istek  okuma  tekil anahtar  tekrar  isabet  depo erisimi  onbelleksiz  fark  ek dokunus/istek
--------------- ------ ------ -------------- ------- ------- ------------- ----------- ----- -----------------
takip sorgusu   200000 200000          20477    9.77  0.8976         20483      200000 -179517              0.20
donem taramasi  400000 400000         400000    1.00  0.0000        400000      400000     0              2.00
sicak yazma     200000  60078             40 5000.00  0.3001        181968      200000 -18032              1.12
takip + tarama  600000 600000         420477    1.43  0.1571        505735      600000 -94265              1.69

toplu tarama ayni onbellekten gecerse takip sorgusuna ne olur:
durum                          takip isabeti  depo erisimi  depo erisimi/s  depoya ulasan/s
---------------------------- --------------- ------------- --------------- ----------------
tarama yok                           0.8976         20483           42.67           139.90
tarama onbellekten geciyor           0.4713        505735         1053.61           317.50
tarama onbellegi atliyor             0.8976        420483          876.01           139.90

taramanin onbellekten gecmesinin bedeli = 85252 ek depo erisimi
takip isabetindeki kayip = 0.4263
```

Bütün sayılar **hesap** sınıfındadır: belirlenimli akışlar üzerinde sayılmışlardır.

#### Karşıt Kalıbın Bedeli

İlk satır karşıt kalıbı tanımlıyor. Takip sorgusunda tekrar oranı 9,77: her takip numarası
pencerede ortalama on kez sorulur. Önbelleksiz tasarımda depo 200.000 erişim görür, önbellekli
tasarımda 20.483 — fark 179.517 erişim, yani onda dokuzu gereksizdi. K01'in hesabında bu,
`depoya ulasan istek/s` satırının 513,89 ile 139,90 arasındaki farkıdır: **3,67 kat**.
Önbelleksiz tasarım, deponun bu katsayıyla boyutlandırılmasını gerektirir ve ondan kaçınmanın
bedeli istek başına 0,20 ek dokunuştur.

İkinci satır aynanın öteki yüzü. Dönem taraması 400.000 satıcı-gün kaydını okur ve hepsi
farklıdır; tekrar oranı tam olarak 1,00, isabet 0,0000. Önbellekli ve önbelleksiz depo erişimi
eşit — fark sıfır. Kazanılan hiçbir şey yokken istek başına **2,00 ek dokunuş** ödenir: bir
yoklama ve bir yerleştirme. Bu örüntüde önbelleksiz tasarım karşıt kalıp değil, doğru
tasarımdır.

Üçüncü satır tekrarın tek başına yetmediğini gösteriyor. Sıcak yazma örüntüsünde kırk anahtar
200.000 işlem görüyor; tekrar oranı 5000, bu tablodaki en yüksek değer. Buna karşılık isabet
yalnız **0,3001**. Neden, işlemlerin 140.000'inin yazma olmasıdır: her yazma girdiyi düşürür ve
sonraki okuma ıska alır. Yüksek tekrar oranı bir önbelleği haklı çıkarmaz; haklı çıkaran şey,
tekrarın **iki yazma arasına** sığmasıdır.

#### Bir Örüntü Başka Bir Örüntüyü Bozar

Dördüncü satır ile ikinci tablo aynı olguyu iki açıdan gösteriyor. Takip sorgusu ile dönem
taraması aynı önbellekten geçtiğinde birleşik isabet 0,1571'e, takip sorgusunun kendi isabeti
0,8976'dan **0,4713**'e iniyor. Taramanın 400.000 farklı anahtarı, kapasitesi 2000 olan
önbelleğe sürekli girip çıkıyor ve girerken takip girdilerini dışarı itiyor.

Sayı burada işareti değiştiriyor. Tarama önbelleği atladığında toplam depo erişimi 420.483,
önbellekten geçtiğinde **505.735**: aradaki 85.252 erişim, önbelleğin **eklediği** yüktür.
`depoya ulasan istek/s` 139,90'dan 317,50'ye çıkıyor. Bir önbellek, yanlış örüntüye açıldığında
koruduğu şeyi bozar.

Çözüm önbelleği kaldırmak değil, tarama akışını ondan geçirmemektir. Son satır bunu ölçüyor:
`donem:` ad alanı atlandığında takip isabeti hiç düşmeden 0,8976'da kalıyor ve `depoya ulasan
istek/s` 139,90'a geri dönüyor. Tarama yine 400.000 kayıt okuyor — o okumalar zaten kaçınılmazdı
— ama artık kimsenin çalışma kümesini tahliye etmiyor. K01'in gün sonu işini ayrı bir dört
saatlik pencereye koymasının gerekçesi bu tabloda görünür hâle geliyor.

#### Karar Kuralı

Dört ölçüm birlikte okunduğunda önbellek kararı dört koşula iner ve dördü de sayılabilir.

**Tekrar oranı birden büyük olmalı.** Pencerede istek sayısının tekil anahtar sayısına oranı 1,00
ise kazanılacak hiçbir şey yoktur; dönem taraması bu koşulda kalır.

**Tekrar iki yazma arasına sığmalı.** Sıcak yazma örüntüsünde tekrar 5000 ama okuma başına düşen
yazma oranı yüksek olduğu için isabet 0,3001'de kalıyor. Ölçülecek nicelik tekrar değil, bir
girdinin ömrü boyunca aldığı okuma sayısıdır.

**Çalışma kümesi kapasiteye sığmalı.** Bu konunun ilk dersi kapasitesi 2000 olan bir önbelleğin
1000 gönderilik çalışma kümesini karşıladığını ölçmüştü; kapasite altına inildiğinde katmanlar
artıklarını birbirine sızdırır.

**Aynı önbelleği paylaşan başka bir örüntü çalışma kümesini tahliye etmemeli.** Bu koşul
ötekilerin hepsi sağlansa bile tek başına bozulabilir ve bozulduğunda önbellek net zarara geçer:
85.252 ek depo erişimi.

Dördü sağlanmıyorsa önbelleksiz tasarım karşıt kalıp değildir; ölçülmeden verilmiş bir önbellek
kararı ise her durumda öyledir.

#### Özet

- Takip sorgusunda tekrar oranı 9,77 ve önbelleksiz tasarım depoya 200.000 erişim bindiriyor;
  önbellekli tasarımda 20.483. K01'in `depoya ulasan istek/s` satırında bu 513,89 ile 139,90
  arasındaki 3,67 katlık farktır.
- Dönem taramasında tekrar oranı 1,00, isabet 0,0000 ve depo erişimi iki tasarımda da 400.000;
  kazanç sıfırken istek başına 2,00 ek dokunuş ödenir.
- Sıcak yazma örüntüsünde tekrar oranı 5000 olduğu hâlde isabet 0,3001: yüksek tekrar bir
  önbelleği haklı çıkarmaz, tekrarın iki yazma arasına sığması çıkarır.
- Tarama aynı önbellekten geçtiğinde takip isabeti 0,8976'dan 0,4713'e iniyor ve toplam depo
  erişimi 420.483'ten 505.735'e çıkıyor; önbellek 85.252 erişim **ekliyor**.
- Karar dört koşula iner: tekrar oranı birden büyük, tekrar iki yazma arasına sığıyor, çalışma
  kümesi kapasiteye sığıyor ve başka bir örüntü onu tahliye etmiyor.

#### Sonraki Adım

Bu derste önbelleğin gerekli olduğu örüntüler ayrıldı, ama bir soru hep açık kaldı. Önbellekten
dönen değer, deponun o andaki değeriyle aynı olmak zorunda değil; ikisinin ayrıldığı süre bu
konunun ikinci dersinde bir kez ölçüldü — geçersizleştirme kapatıldığında 105.794 bayat okuma —
ve dördüncü dersinde bir parametreye bağlandı: beş saniyelik üst sınır süresi keyfi seçilmişti ve
seçimin gerekçesi yazılmamıştı. Sıradaki ders o gerekçeyi kurar. Bayatlığın ne kadarının kabul
edilebilir olduğu bir mühendislik tercihi değil, verinin kendisinden çıkan bir sayıdır: bir
gönderinin durumu ne sıklıkla değişiyorsa, o değişimden daha kısa bir pencere bayat yanıt
üretmez. Sonraki ders pencereyi bu ilişkiden hesaplar, saniyede kaç bayat yanıt kabul edildiğini
sayar ve aynı bayatlık bütçesiyle isabetin nereye kadar çıkabileceğini ölçer.

### Tutarlılık ve Bayat Veri

Source: https://academia.sh/tr/kurslar/veri-olcekleme/tutarlilik-ve-bayat-veri

Bayatlık penceresinin verinin değişme hızından hesaplanması: 48 saatlik yol süresi ve V4 ile tepe saatte 8228,57 saniyelik olay aralığının çıkarılması, pencerenin 60 saniyede doyup isabeti 0,8968'e ve depoya ulaşan isteği 140,23'e getirmesi, bayat yanıtın hep tam bir olay geride kalması ve önceki derslerin geçersizleştirme üst sınırının 0,0010'luk gerçek payla değiştirilmesi.

Önceki ders önbelleğin gerekli olduğu örüntüleri ayırdı ama bir soru açık kaldı. Önbellekten
dönen değer, deponun o andaki değeriyle aynı olmak zorunda değil. Bu konunun ikinci dersi ayrılmayı
bir kez ölçtü — geçersizleştirme kapatıldığında 105.794 bayat okuma — ve dördüncü dersi bir
parametreye bağladı: beş saniyelik üst sınır süresi keyfi seçilmişti ve gerekçesi yazılmamıştı.

Bu ders o gerekçeyi kurar. Bayatlığın ne kadarının kabul edilebilir olduğu bir tercih değil,
verinin kendisinden çıkan bir sayıdır: bir gönderinin durumu ne sıklıkla değişiyorsa, o
değişimden çok daha kısa bir pencere neredeyse hiç bayat yanıt üretmez.

#### İki Pencere, Aynı Yanıt

Sistem Tasarımına Giriş kursu **eskilik penceresi**ni tanımlamıştı: bir yazma onaylandıktan
sonra kopyaların hepsinin o değeri görmesine kadar geçen süre. Buradaki pencere başkadır ve
adı **bayatlık penceresi**dir (staleness window): önbellekteki bir kopyanın kaynaktan geri
kalabileceği en uzun süre; o süre içinde sunulan kopya **bayat veri**dir (stale data).
Kaynakları farklıdır — biri çoğaltmanın yayılımından, öteki girdiye verilen ömürden doğar —
ama ürettikleri şey aynıdır ve aynı adla anılır: **bayat yanıt**. Bir sistemde ikisi birden
bulunur ve toplanır.

Tutarlılık modelleri o kursta tanımlandı; burada tekrarlanmaz. Yalnız bir bağ kurulur:
önbelleğin **sonunda tutarlılık** güvencesi, girdiye verilen ömürdür. Ömür yoksa ve yalnız
geçersizleştirmeye güveniliyorsa yakınsama güvencesi de yoktur — bu konunun ikinci dersi
kaybolan bir silmenin hiçbir sayaçta görünmeden bayat bir girdi bıraktığını ölçmüştü. Ömür, o
girdinin er geç düzeleceğini söyleyen tek mekanizmadır ve bayatlık penceresi tam olarak o
güvencenin genişliğidir.

#### Pencere Nereden Gelir

Bir gönderi V4'e göre yedi durum olayı üretir. Bu olayların ne kadar süreye yayıldığı K01'in
tablosunda yok; oradaki tek süre 730 günlük saklamadır ve o başka bir şeydir.

**B1 (bu dersin varsayımı): bir gönderi ilk olayından teslimine kadar 48 saat yolda kalır.**
Gerekçe: V4'ün yedi durumu (kabul, aktarma, dağıtıma çıkış, teslim ve ara durumlar) bir yol
süresine yayılır ve iki günlük bir yol süresi bu durumların hepsinin gerçekleşmesine yeter.
V8'in tepe çarpanı olayları da hızlandırdığına göre tepe saatte gönderi başına olay aralığı
$48 \cdot 3600 / 7 / 3$ saniyedir. Duyarlılığı son ölçümde yazılıdır. Varsayım K01'in tablosuna
eklenmez.

```js
// onbellek/bayat.mjs — bayatlik penceresinin surec ici modeli. Saat bir sayactir; olay
// araligi, pencere ve kapasite parametredir. Sure olculmez, olay ve okuma sayilir.
export const K01 = {
  tepeOkuma: (2_000_000 * 6 / 86_400) * 3,   // 416,67 istek/s
  tepeYazma: (400_000 * 7 / 86_400) * 3,     // 97,22 istek/s
  gonderiBasinaOlay: 7,                      // V4
  tepeCarpani: 3,                            // V8
};

// B1: bir gonderi ilk olayindan teslimine kadar 48 saat yolda kalir. Tepe saatte olay
// araligi V4 ve V8 ile birlikte buradan cikar.
export const YOL_SAAT = 48;
export const olayAraligi = (yolSaat) => (yolSaat * 3600) / K01.gonderiBasinaOlay / K01.tepeCarpani;

// Okuma akisi ve olay takvimi birlikte uretilir: her gonderi, olusturuldugunda kendi olay
// zamanini alir. Saat okuma sayacidir; bir tik = 1/tepeOkuma saniye.
export function kosum({ okuma, calismaKumesi, obek, kapasite, pencereSn, yolSaat = YOL_SAAT }) {
  let tohum = 20260730;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  const pencere = pencereSn * K01.tepeOkuma;             // tik
  const aralikTik = olayAraligi(yolSaat) * K01.tepeOkuma;
  const kutu = new Map();                                // anahtar -> { surum, biter }
  const gercek = new Map();                              // anahtar -> { surum, sonrakiOlay }
  let saat = 0, sonraki = 1;
  const s = { isabet: 0, depoOkuma: 0, bayat: 0, gerilikToplam: 0, enBuyukGerilik: 0, olay: 0 };
  const yeniGonderi = () => {
    const no = sonraki++;
    gercek.set(`takip:${no}`, { surum: 0, sonrakiOlay: saat + rast() * aralikTik });
    return { no, kalan: obek };
  };
  const etkin = Array.from({ length: calismaKumesi }, () => yeniGonderi());

  for (let i = 0; i < okuma; i++) {
    saat += 1;
    const j = Math.floor(rast() * etkin.length);
    const anahtar = `takip:${etkin[j].no}`;
    if ((etkin[j].kalan -= 1) === 0) etkin[j] = yeniGonderi();

    const g = gercek.get(anahtar);                       // vadesi gelen olaylar islenir
    while (saat >= g.sonrakiOlay) { g.surum += 1; s.olay += 1; g.sonrakiOlay += aralikTik; }

    const c = kutu.get(anahtar);
    if (c !== undefined && c.biter > saat) {
      s.isabet += 1;
      const gerilik = g.surum - c.surum;
      if (gerilik > 0) {
        s.bayat += 1; s.gerilikToplam += gerilik;
        s.enBuyukGerilik = Math.max(s.enBuyukGerilik, gerilik);
      }
      continue;
    }
    s.depoOkuma += 1;
    kutu.delete(anahtar);
    kutu.set(anahtar, { surum: g.surum, biter: saat + pencere });
    if (kutu.size > kapasite) kutu.delete(kutu.keys().next().value);
  }
  return s;
}
```

**Gerilik**, bayat bir yanıtın kaç olay geride kaldığıdır. Bayat okuma sayısı pencerenin ne
sıklıkla ihlal edildiğini söyler; gerilik, ihlal edildiğinde kullanıcının ne gördüğünü söyler.
İkisi ayrı sorulardır ve tasarım kararı ikisine birden bakar.

```js
// onbellek/pencere.mjs — pencere buyudukce isabet, bayat yanit ve depo yuku
import { K01, YOL_SAAT, olayAraligi, kosum } from "./bayat.mjs";

const OKUMA = 200_000, KAPASITE = 2000, CALISMA = 1000, OBEK = 10;
const PENCERE_SN = 480;

console.log(`B1: yol suresi ${YOL_SAAT} saat, V4 = ${K01.gonderiBasinaOlay} olay, V8 = ${K01.tepeCarpani}`);
const ARALIK = olayAraligi(YOL_SAAT);
console.log(`-> tepe saatte gonderi basina olay araligi = ${ARALIK.toFixed(2)} s`);
console.log(`okuma ${OKUMA} (${PENCERE_SN} s), kapasite ${KAPASITE}, es zamanli gonderi ${CALISMA}\n`);

console.log("pencere (s)  isabet  bayat okuma  bayat orani  bayat yanit/s  ort. gerilik  en buyuk  depoya ulasan/s");
console.log("----------- ------- ------------ ------------ -------------- ------------- --------- ----------------");
for (const p of [1, 5, 30, 60, 300, 1800]) {
  const r = kosum({ okuma: OKUMA, calismaKumesi: CALISMA, obek: OBEK, kapasite: KAPASITE, pencereSn: p });
  const h = r.isabet / OKUMA;
  const bayatOran = r.bayat / OKUMA;
  const arka = K01.tepeOkuma * (1 - h);
  console.log(`${String(p).padStart(11)} ${h.toFixed(4).padStart(7)} ${String(r.bayat).padStart(12)} ` +
    `${bayatOran.toFixed(6).padStart(12)} ${(bayatOran * K01.tepeOkuma).toFixed(3).padStart(14)} ` +
    `${(r.bayat === 0 ? 0 : r.gerilikToplam / r.bayat).toFixed(2).padStart(13)} ${String(r.enBuyukGerilik).padStart(9)} ` +
    `${(arka + K01.tepeYazma).toFixed(2).padStart(16)}`);
}

const r5 = kosum({ okuma: OKUMA, calismaKumesi: CALISMA, obek: OBEK, kapasite: KAPASITE, pencereSn: 5 });
console.log(`\npencere 5 s: onbellekteyken olay alan anahtar sayisi = ${r5.olay}`);
console.log(`K01 tepe yazma ${K01.tepeYazma.toFixed(2)}/s -> pencerede toplam olay = ${Math.round(K01.tepeYazma * PENCERE_SN)}`);
console.log(`onbellekteki anahtara dusen olay payi = ${(r5.olay / (K01.tepeYazma * PENCERE_SN)).toFixed(4)}`);

console.log(`\nbayatlik butcesi -> pencere (kuram: pencere = olay araligi x butce)`);
console.log("butce (bayat yanit orani)  kuramsal pencere (s)  olculen bayat orani");
for (const butce of [0.0001, 0.001, 0.01]) {
  const p = ARALIK * butce;
  const r = kosum({ okuma: OKUMA, calismaKumesi: CALISMA, obek: OBEK, kapasite: KAPASITE, pencereSn: p });
  console.log(`${butce.toFixed(4).padStart(25)} ${p.toFixed(2).padStart(21)} ${(r.bayat / OKUMA).toFixed(6).padStart(20)}`);
}

console.log(`\nB1'in duyarliligi (pencere 60 s):`);
console.log("yol suresi (saat)  olay araligi (s)  bayat orani  bayat yanit/s  isabet");
for (const yol of [48, 12, 2]) {
  const r = kosum({ okuma: OKUMA, calismaKumesi: CALISMA, obek: OBEK, kapasite: KAPASITE, pencereSn: 60, yolSaat: yol });
  const o = r.bayat / OKUMA;
  console.log(`${String(yol).padStart(17)} ${olayAraligi(yol).toFixed(2).padStart(17)} ${o.toFixed(6).padStart(12)} ` +
    `${(o * K01.tepeOkuma).toFixed(3).padStart(14)} ${(r.isabet / OKUMA).toFixed(4).padStart(7)}`);
}
```

```
B1: yol suresi 48 saat, V4 = 7 olay, V8 = 3
-> tepe saatte gonderi basina olay araligi = 8228.57 s
okuma 200000 (480 s), kapasite 2000, es zamanli gonderi 1000

pencere (s)  isabet  bayat okuma  bayat orani  bayat yanit/s  ort. gerilik  en buyuk  depoya ulasan/s
----------- ------- ------------ ------------ -------------- ------------- --------- ----------------
          1  0.2700            3     0.000015          0.006          1.00         1           401.40
          5  0.6313           32     0.000160          0.067          1.00         1           250.85
         30  0.8866          221     0.001105          0.460          1.00         1           144.46
         60  0.8968          239     0.001195          0.498          1.00         1           140.23
        300  0.8968          239     0.001195          0.498          1.00         1           140.23
       1800  0.8968          239     0.001195          0.498          1.00         1           140.23

pencere 5 s: onbellekteyken olay alan anahtar sayisi = 46
K01 tepe yazma 97.22/s -> pencerede toplam olay = 46667
onbellekteki anahtara dusen olay payi = 0.0010

bayatlik butcesi -> pencere (kuram: pencere = olay araligi x butce)
butce (bayat yanit orani)  kuramsal pencere (s)  olculen bayat orani
                   0.0001                  0.82             0.000005
                   0.0010                  8.23             0.000290
                   0.0100                 82.29             0.001195

B1'in duyarliligi (pencere 60 s):
yol suresi (saat)  olay araligi (s)  bayat orani  bayat yanit/s  isabet
               48           8228.57     0.001195          0.498  0.8968
               12           2057.14     0.004180          1.742  0.8968
                2            342.86     0.032020         13.342  0.8968
```

Bütün sayılar **hesap** sınıfındadır: belirlenimli bir akış üzerinde sayılmışlardır.

#### Pencerenin Doyması

İlk tablo beklenmedik bir yerde duruyor. Pencere 60 saniyeden sonra hiçbir şeyi değiştirmiyor:
isabet 0,8968, bayat oranı 0,001195, `depoya ulasan istek/s` 140,23 — üç satır aynı. Nedeni,
girdinin ömrünü dolduramadan **tahliye** edilmesidir. Bir gönderinin on sorgusu yaklaşık yirmi
dört saniyeye yayılır; o bittiğinde gönderi çalışma kümesinden çıkar ve girdisi kapasite
baskısıyla düşer. Altmış saniyeden uzun bir pencere bir daha okunmayacak bir girdiye verilmiş
demektir.

Bu, dördüncü dersin beş saniyelik varsayımını kötü bir seçim yapıyor. Beş saniyede isabet 0,6313
ve `depoya ulasan istek/s` 250,85; altmış saniyede 0,8968 ve 140,23. Beş saniyelik pencere
depoya saniyede **110,62 fazladan istek** bindiriyor ve karşılığında bayat oranını 0,001195'ten
0,000160'a indiriyor: saniyede 0,43 bayat yanıt kazanmak için 110,62 istek ödeniyor.

İkinci tablo pencerenin nasıl seçileceğini veriyor. Bir **bayatlık bütçesi** — kabul edilen bayat
yanıt oranı — konur ve pencere, olay aralığı ile bütçenin çarpımı olur. Yüzde birlik bir
bütçe 82,29 saniyelik bir pencereye karşılık geliyor; ölçülen oran ise 0,001195, bütçenin sekizde
biri. Kuram üst sınırı verir, tahliye onu daha da aşağı çeker.

#### Bayatlığın Büyüklüğü

Bayat okuma sayısı tek başına yeterli değildir. Ortalama gerilik ve en büyük gerilik sütunları
altı satırın hepsinde **1,00** ve **1**: bayat bir yanıt hiçbir zaman birden fazla olay geride
kalmıyor. Kullanıcı açısından bu, gönderinin bir durum adımı önceki hâlini görmesi demektir —
"dağıtıma çıktı" yerine "aktarma merkezinde" gibi. Durum hiçbir zaman iki adım geri gitmiyor ve
sayfayı yenilediğinde doğrusunu görüyor.

Bu sayı pencereyi kabul edilebilir kılan asıl gerekçedir. Saniyede 0,498 bayat yanıt, sekiz
dakikalık pencerede 239 yanıt eder ve her biri tam bir adım geridedir. Aynı bütçe, gerilik iki
ya da üç olsaydı başka bir karar gerektirirdi.

Kabul kararının bir de katman boyutu var. Bu konunun ilk dersi isabetin 0,7002'sinin istemci
katmanında karşılandığını ölçmüştü ve o katmanda konan kopya, süresi dolana kadar geri alınamaz.
Bayat yanıtların çoğunluğu bu yüzden geri alınamaz katmandan gelir: pencere orada bir tahmin
değil, bir taahhüttür.

#### Bir Varsayımın Yerine Bir Hesap

Son iki çıktı bloğu bu konunun en önemli düzeltmesini taşıyor.

Bu konunun ikinci dersi bir üst sınır kullanmıştı: durum olaylarının tamamının o anda sorgulanan
gönderilere düştüğünü varsaydı ve bunun geçersizleştirmenin en güçlü olduğu yön olduğunu yazdı.
B1 o üst sınırın yerine bir hesap koyuyor. Pencerede 46.667 durum olayı var ve bunların yalnız
**46**'sı önbellekte duran bir anahtara düşüyor: pay **0,0010**. Olayların geri kalanı ya
sorgulanmayan gönderilere ya da o anda önbellekte bulunmayan anahtarlara gidiyor ve hiçbir
girdiyi düşürmüyor.

Bu, ikinci ve üçüncü derslerin sayılarını üst sınır hâline getirir. Yanında okumanın ölçtüğü
0,7287 isabet, olayların hepsi sıcak anahtara düşseydi geçerli olurdu; gerçek payla isabet
0,8968'de kalır ve yanında okuma ile içinden yazma arasındaki fark okuma tarafında kaybolur.
Üçüncü dersin yazma yolu çözümlemesi bundan etkilenmez: yazma yolunun uzunluğu, birleşme oranı ve
kayıp penceresi olayların nereye düştüğünden bağımsızdır.

Son tablo B1'in duyarlılığını veriyor ve kararın sınırını çiziyor. Yol süresi 48 saatten 2 saate
indiğinde olay aralığı 8228,57 saniyeden 342,86 saniyeye iner ve aynı altmış saniyelik pencere
bayat oranını 0,001195'ten **0,032020**'ye, bayat yanıtı saniyede 13,342'ye çıkarır. İsabet
değişmez. Aynı önbellek tasarımı, aynı ölçülerle, aynı gün içinde teslim edilen bir gönderi
akışında yirmi yedi kat daha fazla bayat yanıt üretir. Pencere veriye bağlıdır, sisteme değil.

#### Özet

- Bayatlık penceresi önbellekteki kopyanın kaynaktan geri kalabileceği süredir; eskilik
  penceresinden kaynağıyla ayrılır ama aynı bayat yanıtı üretir ve önbelleğin sonunda tutarlılık
  güvencesi tam olarak bu penceredir.
- Pencere verinin değişme hızından çıkar: B1 ve V4 ile tepe saatte olay aralığı 8228,57 saniye,
  pencere ise bu aralık ile bayatlık bütçesinin çarpımıdır — yüzde birlik bütçe 82,29 saniye.
- Pencere 60 saniyede doyuyor: isabet 0,8968, `depoya ulasan istek/s` 140,23 ve bayat oranı
  0,001195. Dördüncü dersin beş saniyelik penceresi saniyede 0,43 bayat yanıt kazanmak için
  depoya 110,62 fazladan istek bindiriyordu.
- Bayat yanıt hiçbir koşumda birden fazla olay geride kalmadı; ortalama ve en büyük gerilik 1.
  Kabul kararı bayat yanıt sayısına değil, bu iki sayıya birden bakar.
- Pencerede 46.667 olayın yalnız 46'sı önbellekteki bir anahtara düşüyor (pay 0,0010); bu, ikinci
  dersin geçersizleştirme varsayımını üst sınır hâline getirir. Yol süresi 2 saate inerse aynı
  pencere bayat oranını 0,032020'ye çıkarır.

#### Sonraki Adım

Bu konu okumayı deponun önünde karşıladı ve bayatlığı bir pencereyle sınırladı. `depoya ulasan
istek/s` satırı 513,89'dan 140,23'e indi ve V9'un 0,90'ı bir varsayım olmaktan çıkıp bir
yerleşim, bir strateji ve bir pencere kararının sonucu oldu. Ama bütün bu tasarım tek bir şeyi
hiç sorgulamadı: önbelleğe konan değerin **biçimini**. Önbellek okuma yolunu hafifletti; okuduğu
şey hâlâ yazma için tasarlanmış modeldir. Gönderi kaydı, taşıyıcının durum olayını yazabilmesi
için düzenlenmiştir ve takip sorgusu o modeli okumak zorunda kaldığı için önce birleştirir,
sonra biçimlendirir, sonra saklar. Önbellek bu ödüncü ortadan kaldırmaz, yalnız ıskalanmayan
isteklerden gizler; ıskalanan her istek yine aynı modele gider. Sıradaki konu ödüncü kaldırır ve
okuma ile yazma yollarını **kendi modelleriyle** ölçekler. İlk sorusu şudur: aynı veriyi iki ayrı
yapıda tutmak okuma ve yazma yollarının ayrı ölçeklenmesini sağlar mı, sağlıyorsa hangi orandan
sonra.

## Okuma–Yazma Ayrımı

Okuma ve yazma yollarının ayrı ölçeklenmesi: farklı modellerle ölçekleme kararı, değişimin kayıt olarak saklanması, okuma modellerinin yansıtmayla türetilmesi ve yeniden oluşturulması, sonunda tutarlılığın kullanıcı arayüzünde bekleme ve çakışma olarak görünmesi.

### Komut ve Sorgu Ayrımı

Source: https://academia.sh/tr/kurslar/veri-olcekleme/komut-ve-sorgu-ayrimi

Okuma ve yazma yollarının ayrı modellerle ölçeklenmesi: takip sorgusunun tek modelde sekiz, izdüşümde bir kayıt dokunması, komutun üç dokunuştan beşe çıkması, iki yerleşimin düğüm sayısının okuma ve yazma çarpanları altında karşılaştırılması ve okuma deposunun K01'in yazma/okuma oranından kaçamaması.

Önceki konu okuma yolunu bir katman öne çekti: önbellek isteklerin çoğunu depoya hiç
ulaştırmadı — `depoya ulasan istek/s` satırı 140,23'e indi — ve ulaşanların gördüğü bayatlığı
bir pencereyle sınırladı. Ama önbelleğin sakladığı
şey, deponun verdiği yanıtın kopyasıdır; ıskalanan her istek yine **aynı modele** gider. O model
gönderi kaydını, durum olaylarını ve aralarındaki bağı yazma tarafının değişmezlerini koruyacak
biçimde düzenler. Okuma yolu, yazma için tasarlanmış bir modeli ödünç alır ve her ıskalamada
onun bedelini öder.

Bu konu ödünç almayı bırakır. Sorusu şudur: aynı veriyi iki ayrı yapıda tutmak, okuma ve yazma
yollarının **ayrı ölçeklenmesini** sağlar mı, sağlıyorsa hangi orandan sonra.

#### İki Ayrı Ayrım

Adları benzeyen iki kavram var ve birbirinin yerine geçmezler. Programlama Temelleri kursunda
tanıtılan **komut–sorgu ayrımı** bir yöntemin imzasıyla ilgilidir: bir yöntem ya durumu değiştirir
ya değer döndürür, ikisini birden yapmaz. **Komut ve sorgu sorumluluğu ayrımı** (command query
responsibility segregation) ise yöntemi değil **modeli** ayırır; aynı veri iki ayrı yapıda
tutulur.

İkinci kalıp Alan Odaklı Tasarım kursunda kuruldu ve orada ölçüldü: pano sorgusunun yazma modeli
üzerinden gezdiği nesne sayısı veri boyuyla büyüyordu, okuma modeli üzerinden sabit kalıyordu.
O ders bir sınır da koymuştu — tek bir kaydı kimliğiyle okuyan bir sorgu yazma modelinde de tek
nesne gezer, ayrım ona hiçbir şey kazandırmaz. Takip sorgusu tam olarak böyle görünür: tek bir
takip numarası verilir. Ölçüm bu benzerliğin yanıltıcı olup olmadığını sınayarak başlar.

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

Karar bir örüntüyle birlikte verilir. Bu konuda örüntü üçtür: takip numarasına göre tekil okuma,
taşıyıcıdan gelen durum olayının yazılması ve satıcıya göre dönemsel toplu tarama. İlk ikisi bu
dersin konusudur.

Takip yanıtının gövdesi K01'de tanımlıydı (V5, 480 bayt): durum, bölge, güncellenme zamanı ve
**son üç rota adımı**. Son üç adım gönderi kaydında değil, olay kayıtlarındadır. Yazma modeli
üzerinden yanıt derlemek bu yüzden gönderi kaydını ve o gönderinin bütün durum olaylarını okumayı
gerektirir; olay sayısı K01'in V4 varsayımıyla 7'dir.

Düzenek süreç içi bir depo modelidir. Gerçek bir veritabanı, disk ya da ağ yoktur; kayıtlar
bellekte durur ve her erişim dokunduğu kayıt sayısını arttırır. Ölçü süre değil **kayıt
dokunuşudur**, çünkü süre makineye bağlıdır, dokunuş sayısı değildir.

```js
// ayrim/depo.mjs — surec ici depo modeli. Gercek bir veritabani yoktur: kayitlar bellekte
// durur ve her erisim dokundugu kayit sayisini sayar. Olculen sey sure degil, dokunustur.
const NOKTA = ["34", "06", "35", "01", "16"];

export function depoKur() {
  return { gonderi: new Map(), olay: new Map(), izdusum: new Map(), sayac: { okuma: 0, yazma: 0 } };
}

function yanitKur(g, dizi) {
  return { id: g.id, durum: g.durum, bolge: g.bolge, an: g.an, sonAdim: dizi.slice(-3).map((o) => o.nokta) };
}

export function tohumla(d, sayi, olayBasina) {
  let x = 7;
  const sonraki = () => (x = (x * 48271 + 11) % 2147483647);
  for (let i = 1; i <= sayi; i += 1) {
    const id = `G${i}`;
    const g = { id, bolge: NOKTA[sonraki() % NOKTA.length], durum: "yolda", an: olayBasina };
    const dizi = Array.from({ length: olayBasina }, (_, k) => ({ id, sira: k + 1, nokta: NOKTA[sonraki() % NOKTA.length] }));
    d.gonderi.set(id, g);
    d.olay.set(id, dizi);
    d.izdusum.set(id, yanitKur(g, dizi));
  }
  return d.gonderi.size;
}

// Tek model: takip yaniti gonderi kaydindan ve o gonderinin olay kayitlarindan derlenir.
export function takipTekModel(d, id) {
  const g = d.gonderi.get(id);
  d.sayac.okuma += 1;
  const dizi = d.olay.get(id);
  d.sayac.okuma += dizi.length;
  return yanitKur(g, dizi);
}

// Ayrik model: takip yaniti hazir bekleyen tek izdusum satirindan okunur.
export function takipIzdusumden(d, id) {
  d.sayac.okuma += 1;
  return d.izdusum.get(id);
}

// Komut: olay eklenir, gonderi kaydi oku-degistir-yaz ile guncellenir; ayrik yerlesimde
// ayrica izdusum satiri oku-degistir-yaz ile tazelenir.
export function komut(d, id, nokta, izdusumeYaz) {
  const dizi = d.olay.get(id);
  dizi.push({ id, sira: dizi.length + 1, nokta });
  d.sayac.yazma += 1;
  const g = d.gonderi.get(id);
  d.sayac.okuma += 1;
  g.an = dizi.length;
  d.sayac.yazma += 1;
  if (izdusumeYaz !== true) return;
  const eski = d.izdusum.get(id);
  d.sayac.okuma += 1;
  d.izdusum.set(id, { ...eski, an: g.an, sonAdim: dizi.slice(-3).map((o) => o.nokta) });
  d.sayac.yazma += 1;
}
```

**İzdüşüm** (projection), Alan Odaklı Tasarım kursunda tanımlandığı biçimiyle, yazma tarafının
ürettiği değişimlerden türetilen okuma modelidir. Burada izdüşüm satırı takip yanıtının kendisidir.

#### Ölçüm ve Hesap

Ölçüm iki adımdır. Önce model koşturulur ve sorgu ile komut başına dokunuş sayısı sayılır; bu
sayılar belirlenimlidir. Sonra sayılar K01'in hızlarına uygulanır ve düğüm sayısı çıkarılır.
Düğüm sayısı için bir kapasite gerekiyor.

**OY1 — bir depo düğümünün saniyede sürdürdüğü kayıt dokunuşu: 100.** Bu konunun kendi
varsayımıdır ve K01'in tablosuna eklenmez. Gerekçesi, bölme yapılabilmesi için bir birim
gerekmesidir; sayının mutlak değeri bir ölçüm değildir. Duyarlılığı bu yüzden özel bir biçim
alır: 200 alınırsa bütün düğüm sayıları yarılanır ama iki yerleşimin **oranı** değişmez, çünkü
kapasite iki tarafta da aynı bölendir.

```js
// ayrim/olc.mjs — iki yerlesimin sorgu ve komut basina dokunusu modelden olculur, sonra
// K01'in hizlarina uygulanir. Butun sayilar belirlenimlidir; sinif olarak hesaptir.
import { depoKur, tohumla, takipTekModel, takipIzdusumden, komut } from "./depo.mjs";

const V4 = 7;          // K01 varsayimi: gonderi basina durum olayi
const OKUMA = 41.67;   // K01 hesabi: onbellek arkasi okuma/s
const YAZMA = 97.22;   // K01 hesabi: tepe yazma istek/s
const OY1 = 100;       // bu konunun varsayimi: bir dugumun surdurdugu kayit dokunusu/s

const N = 200;
const d = depoKur();
tohumla(d, N, V4);

const kimlik = [...d.gonderi.keys()];
d.sayac.okuma = 0; d.sayac.yazma = 0;
const tekYanit = kimlik.map((id) => takipTekModel(d, id));
const tekSorgu = d.sayac.okuma / N;

d.sayac.okuma = 0; d.sayac.yazma = 0;
const izYanit = kimlik.map((id) => takipIzdusumden(d, id));
const izSorgu = d.sayac.okuma / N;
const esit = JSON.stringify(tekYanit) === JSON.stringify(izYanit);

d.sayac.okuma = 0; d.sayac.yazma = 0;
for (const id of kimlik) komut(d, id, "16", false);
const tekKomut = { okuma: d.sayac.okuma / N, yazma: d.sayac.yazma / N };

d.sayac.okuma = 0; d.sayac.yazma = 0;
for (const id of kimlik) komut(d, id, "01", true);
const ayrikKomut = { okuma: d.sayac.okuma / N, yazma: d.sayac.yazma / N };

const b = (x, n = 2) => x.toFixed(n);
console.log(`model: ${N} gonderi, gonderi basina ${V4} olay (K01 V4)`);
console.log(`takip sorgusu / tek model    = ${tekSorgu} kayit dokunusu`);
console.log(`takip sorgusu / izdusumden   = ${izSorgu} kayit dokunusu, iki yanit esit mi = ${esit}`);
console.log(`komut / tek model            = ${tekKomut.okuma + tekKomut.yazma} dokunus ` +
  `(${tekKomut.okuma} okuma, ${tekKomut.yazma} yazma)`);
console.log(`komut / ayrik model          = ${ayrikKomut.okuma + ayrikKomut.yazma} dokunus ` +
  `(${ayrikKomut.okuma} okuma, ${ayrikKomut.yazma} yazma)`);

const tek = (r, w) => OKUMA * r * tekSorgu + YAZMA * w * (tekKomut.okuma + tekKomut.yazma);
const ayrikYazma = (w) => YAZMA * w * (tekKomut.okuma + tekKomut.yazma);
const ayrikOkuma = (r, w) => OKUMA * r * izSorgu + YAZMA * w * (ayrikKomut.okuma + ayrikKomut.yazma - tekKomut.okuma - tekKomut.yazma);
const dugum = (yuk) => Math.ceil(yuk / OY1);

console.log(`\nK01: onbellek arkasi okuma ${OKUMA}/s, tepe yazma ${YAZMA}/s, oran ${b(YAZMA / OKUMA)}`);
console.log(`dugum kapasitesi OY1 = ${OY1} dokunus/s (bu konunun varsayimi)\n`);
console.log(`${"okuma x".padStart(8)}${"yazma x".padStart(8)}${"tek dokunus/s".padStart(15)}` +
  `${"tek dugum".padStart(11)}${"ayrik y+o dokunus/s".padStart(21)}${"ayrik dugum".padStart(13)}`);
for (const [r, w] of [[1, 1], [2, 1], [4, 1], [1, 2], [1, 4], [4, 4]]) {
  const t = tek(r, w), ay = ayrikYazma(w), ao = ayrikOkuma(r, w);
  console.log(`${String(r).padStart(8)}${String(w).padStart(8)}${b(t).padStart(15)}` +
    `${String(dugum(t)).padStart(11)}${`${b(ay)} + ${b(ao)}`.padStart(21)}` +
    `${`${dugum(ay)} + ${dugum(ao)} = ${dugum(ay) + dugum(ao)}`.padStart(13)}`);
}

const kazanc = (r, w) => dugum(tek(r, w)) - dugum(ayrikYazma(w)) - dugum(ayrikOkuma(r, w));
let ilkKayip = null;
for (let w = 1; w <= 20; w += 1) if (kazanc(1, w) < 0) { ilkKayip = w; break; }
console.log(`\nayrimin dugum kazanci: taban ${kazanc(1, 1)}, okuma x4'te ${kazanc(4, 1)}, ` +
  `yazma x4'te ${kazanc(1, 4)}`);
console.log(`ayrimin dugum sayisini artirdigi en kucuk yazma carpani = ${ilkKayip}`);

const izGuncelleme = ayrikKomut.okuma + ayrikKomut.yazma - tekKomut.okuma - tekKomut.yazma;
console.log(`\nokuma deposunun gordugu istek: sorgu ${b(OKUMA)}/s, izdusum guncellemesi ` +
  `${b(YAZMA)}/s -> yazma/okuma orani ${b(YAZMA / OKUMA)} (K01'in orani)`);
console.log(`okuma deposunun gordugu dokunus: ${b(OKUMA * izSorgu)} + ${b(YAZMA * izGuncelleme)} ` +
  `-> oran ${b((YAZMA * izGuncelleme) / (OKUMA * izSorgu))}`);
```

```
model: 200 gonderi, gonderi basina 7 olay (K01 V4)
takip sorgusu / tek model    = 8 kayit dokunusu
takip sorgusu / izdusumden   = 1 kayit dokunusu, iki yanit esit mi = true
komut / tek model            = 3 dokunus (1 okuma, 2 yazma)
komut / ayrik model          = 5 dokunus (2 okuma, 3 yazma)

K01: onbellek arkasi okuma 41.67/s, tepe yazma 97.22/s, oran 2.33
dugum kapasitesi OY1 = 100 dokunus/s (bu konunun varsayimi)

 okuma x yazma x  tek dokunus/s  tek dugum  ayrik y+o dokunus/s  ayrik dugum
       1       1         625.02          7      291.66 + 236.11    3 + 3 = 6
       2       1         958.38         10      291.66 + 277.78    3 + 3 = 6
       4       1        1625.10         17      291.66 + 361.12    3 + 4 = 7
       1       2         916.68         10      583.32 + 430.55   6 + 5 = 11
       1       4        1500.00         15     1166.64 + 819.43  12 + 9 = 21
       4       4        2500.08         26     1166.64 + 944.44 12 + 10 = 22

ayrimin dugum kazanci: taban 1, okuma x4'te 10, yazma x4'te -6
ayrimin dugum sayisini artirdigi en kucuk yazma carpani = 2

okuma deposunun gordugu istek: sorgu 41.67/s, izdusum guncellemesi 97.22/s -> yazma/okuma orani 2.33 (K01'in orani)
okuma deposunun gordugu dokunus: 41.67 + 194.44 -> oran 4.67
```

#### Sayıların Okunması

İlk üç satır benzerliğin yanıltıcı olduğunu gösteriyor. Takip sorgusu tek bir kimlikle geliyor
ama yazma modeli üzerinden **8 kayda** dokunuyor: bir gönderi kaydı ve yedi olay kaydı. İzdüşüm
üzerinden 1 kayda dokunuyor ve iki yolun ürettiği yanıtlar eşit çıkıyor. Ayrımın kazanç
getirmediği durum, sorgunun tek kimlikle gelmesi değil, **yanıtın tek kayıttan çıkmasıdır**;
takip yanıtı çıkmıyor.

Komut satırları bedeli veriyor. Tek modelde bir durum olayı 3 dokunuş ediyor: olay eklenir,
gönderi kaydı okunur ve yazılır. Ayrık yerleşimde izdüşüm satırının da okunup yazılması gerekiyor
ve komut 5 dokunuşa çıkıyor. Yazma yolu **1,67 katına** çıkıyor.

Düğüm tablosu iki yolu K01'in hızlarında karşılaştırıyor. Tabanda tek model 625,02 dokunuş/s ile
7 düğüm istiyor, ayrık yerleşim 3 yazma ve 3 okuma düğümüyle 6 düğüm istiyor. Kazanç bir düğüm;
bu, ayrımı tek başına haklı çıkaracak bir fark değildir.

Asıl fark büyüme yönünde. Okuma dört katına çıktığında tek model 17 düğüme tırmanıyor, ayrık
yerleşim 7'de kalıyor: kazanç 10 düğüm. Çünkü ayrık yerleşimde okuma büyümesi yalnız izdüşüm
sorgularını büyütüyor ve her sorgu 1 dokunuş. Yazma dört katına çıktığında tablo tersine dönüyor:
tek model 15, ayrık yerleşim 21 düğüm istiyor. Kayıp 6 düğüm ve eşik erken geliyor — yazma
çarpanı **2** olduğunda ayrım zaten pahalıya düşüyor. Ayrım bir bahistir: okumanın yazmadan hızlı
büyüyeceği bahsi.

#### Oran Kaybolmuyor, Taşınıyor

Son iki satır kalıbın en kolay yanlış anlaşılan yerini ölçüyor. K01 depoda yazma/okuma oranını
2,33 bulmuştu; okuma ağırlıklı görünen sistem, deponun gördüğü yerde yazma ağırlıklıydı. Ayrım
bu orandan kaçmıyor. Okuma deposu saniyede 41,67 sorgu ve 97,22 izdüşüm güncellemesi görüyor;
istek cinsinden oranı yine **2,33**. Dokunuş cinsinden bakıldığında durum daha da belirgin:
41,67 dokunuşa karşı 194,44 dokunuş, oran **4,67**. "Okuma modeli" adı taşıyan depo, özgün
depodan iki kat daha yazma ağırlıklıdır.

Ayrımın verdiği şey oranı düzeltmek değil, **dokunuş başına bedeli** ayırmaktır. Okuma yolunun
dokunuşu 8'den 1'e iniyor; yazma yolunun dokunuşu 3'ten 5'e çıkıyor. K01'in `depoya ulasan
istek/s` satırındaki 138,89 istek ortadan kalkmıyor, iki depoya bölünüyor: 97,22 yazma
deposuna, 41,67 okuma deposuna. Toplam dokunuş 625,02'den 527,77'ye iniyor, yani 1,18 kat.
Bu sayıların hepsi hesap sınıfındadır; tek varsayım OY1'dir ve o da yalnız bölmeyi yapıyor.

#### Ayrımın Getirdiği Yeni Soru

Ayrık yerleşimde izdüşüm satırı komutla birlikte güncellendi ve bu yüzden hep taze kaldı. Bu,
modeli gereğinden iyi gösteriyor: iki depoya aynı anda yazmak, iki deponun tek bir tutarlılık
sınırında olmasını gerektirir. Ayrı ölçeklenmeleri isteniyorsa aynı sınırda olamazlar; izdüşüm
komuttan **sonra** güncellenir ve arada bir pencere açılır. O pencerenin ne kadar açık kalacağı
ve okuma deposunun neden yeniden kurulabilir olması gerektiği, izdüşümün neyden türetildiğine
bağlıdır.

#### Özet

- Komut–sorgu ayrımı bir yöntemin imzasıyla, komut ve sorgu sorumluluğu ayrımı modelle ilgilidir;
  ikincisi Alan Odaklı Tasarım kursunda kuruldu, burada ölçekleme aracı olarak ele alınıyor.
- Takip sorgusu tek kimlikle gelse de yanıtı tek kayıttan çıkmıyor: yazma modeli üzerinden 8,
  izdüşüm üzerinden 1 kayıt dokunuşu, iki yanıt eşit.
- Bedel yazma yolundadır: komut 3 dokunuştan 5 dokunuşa, yani 1,67 katına çıkıyor.
- OY1 = 100 dokunuş/s kapasiteyle tabanda düğüm 7'den 6'ya iniyor; okuma dört katına çıkarsa
  17'ye karşı 7, yazma dört katına çıkarsa 15'e karşı 21. Ayrım yazma çarpanı 2'de zarara geçiyor.
- K01'in 2,33'ü kaybolmuyor, taşınıyor: okuma deposunun istek oranı da 2,33, dokunuş oranı 4,67.
  Ayrım oranı değil, dokunuş başına bedeli değiştirir.

#### Sonraki Adım

İzdüşümün komutla aynı anda güncellenmesi bu dersin ölçümünü kolaylaştırdı ama ayrımın amacını
yok ediyor: aynı tutarlılık sınırında duran iki depo ayrı ölçeklenemez. İzdüşüm komuttan sonra,
kendi hızıyla güncellenecekse neyden türetileceği belirlenmelidir. Gönderi kaydı yalnız son
durumu tuttuğu için ondan türetilemez — bir izdüşüm satırı, kaydın hangi değişimle o hâle
geldiğini bilmek zorundadır. Sonraki ders değişimin kendisini kayıt olarak saklamayı ele alır:
eklemeli bir kütüğün yazma yolundan hangi okumayı kaldırdığını, K01'in bir yazma isteğinin depoda
kaç işleme dönüştüğünü hiç sormadığını ve olayın kaynak kayda dönüşmesinin günlük veri artışı ile
saklanan veri satırlarına ne eklediğini ölçer.

### Olay Kaynaklı Tasarım

Source: https://academia.sh/tr/kurslar/veri-olcekleme/olay-kaynakli-tasarim

Değişimin kayıt olarak saklanmasının ölçekleme bedeli: eklemeli kütüğün yazma yolundan oku-değiştir-yaz okumasını kaldırması, istek başına depo işleminin 4,50'den 3,10'a inmesi, kaynak olayın ek alanlarının günlük artışı 976'dan 1168 MB'a ve saklanan veriyi 712,48'den 852,64 GB'a çıkarması, dönemsel taramanın 10,80'den 35,04 GB'a tırmanması.

Önceki ders izdüşümü komutla aynı anda güncelledi ve ayrımın amacını yarısından etti: aynı
tutarlılık sınırında duran iki depo ayrı ölçeklenemez. İzdüşüm kendi hızıyla güncellenecekse
neyden türetileceği belirlenmelidir ve gönderi kaydı buna elverişli değildir — yalnız son durumu
tutar, kaydın o hâle hangi değişimle geldiğini söylemez. İzdüşümü tazelemek için değişimin
kendisi gerekir.

**Olay kaynaklı kalıcılık** (event sourcing) tam olarak bunu yapar: saklanan asıl şey durum
değil, durumu üreten olayların dizisidir. Kalıp Alan Odaklı Tasarım kursunda kuruldu ve orada
yanıtlanabilirlik ölçüsüyle savunuldu — durum tablosunun şemasında olmayan soruların olay
dizisinden çıkması. Bu dersin sorusu başkadır: kalıp veri katmanının **ölçekleme sayılarına** ne
yapıyor. Kütük yazma yolunu ucuzlatıyor mu, saklanan veriyi ne kadar büyütüyor ve hangi erişim
örüntüsünü pahalıya düşürüyor.

#### Yazma Yolunda Gizlenen Okuma

K01 depoya ulaşan istek hızını 138,89 bulmuştu: saniyede 41,67 okuma isteği ve 97,22 yazma
isteği, oran 2,33. Bu sayı **istek** sayar. Bir yazma isteğinin depoda kaç işleme dönüştüğü
sorulmamıştı.

Durumu yerinde güncelleyen bir kalıcılıkta bir durum olayı üç işlem eder: olay denetim için
yazılır, gönderi kaydı okunur, güncellenmiş kayıt geri yazılır. Ortadaki okuma bir istek değildir,
bu yüzden K01'in tablosunda görünmez; ama depoya gerçekten ulaşır. Eklemeli bir kütükte o okuma
yoktur: yazma yalnız dizinin sonuna ekler ve önceki durumu bilmek zorunda değildir.

Kaynak kayıt olmanın bir bedeli var. K01'in olay kaydı (V6, 220 bayt) bir denetim satırıdır:
takip numarası, durum, rota adımı, zaman damgası. Aynı kayıt tek doğruluk kaynağı olduğunda
fazladan alan taşır — olay türü, dizideki sıra numarası, şema sürümü ve tekillik anahtarı. Sıra
numarası olmadan katlama sırası belirsizdir, şema sürümü olmadan eski biçimli bir olay okunamaz,
tekillik anahtarı olmadan yeniden gönderilen bir olay ikinci kez uygulanır.

**OY2 — kaynak olay kaydının ek yükü: 60 bayt.** Bu konunun varsayımıdır, K01'in tablosuna
eklenmez. Gerekçesi yukarıdaki dört alandır. Duyarlılığı ölçümün son satırlarında veriliyor:
ek yük 0 alınırsa K01'in sayıları aynen çıkar, 120 bayt alınırsa saklanan veri bir kez daha
büyür.

#### Düzenek

İki kalıcılık aynı arayüzü sunar ve aynı olayları görür. Süreç içi bir modeldir; disk, ağ ve
süre yoktur. Sayılan iki şey vardır: her erişimin ürettiği **depo işlemi** ve her kaydın
eklediği **saklanan bayt**. Bayt sayısı JSON boyundan değil, K01'in V6 ve V7 varsayımlarından
gelir; amaç modelin kendi biçimini değil, K01'in hesabını sürdürmektir.

```js
// olay/kalicilik.mjs — iki kalicilik biciminin surec ici modeli. Ayni arayuz, ayni olaylar;
// her erisim depo islemi sayar, her kayit K01'in bayt varsayimlarindan saklanan bayt ekler.
export const V4 = 7, V6 = 220, V7 = 900;   // K01 varsayimlari
export const OY2 = 60;                     // bu konunun varsayimi: kaynak olayin ek alanlari

function sayacKur() {
  return { okuma: 0, yazma: 0, bayt: 0 };
}

// A: durumu yerinde guncelleyen kalicilik; olaylar ayrica denetim icin yazilir.
export function durumGunceller() {
  const kayit = new Map(), denetim = new Map();
  const s = sayacKur();
  return {
    ad: "durum gunceller", sayac: s,
    olustur(id, bolge) {
      kayit.set(id, { id, bolge, durum: "kabul-edildi", sonAdim: [] });
      denetim.set(id, []);
      s.yazma += 1; s.bayt += V7;
    },
    yaz(id, nokta) {
      denetim.get(id).push({ id, nokta });
      s.yazma += 1; s.bayt += V6;
      const k = kayit.get(id);
      s.okuma += 1;                       // oku-degistir-yaz: mevcut satir once okunur
      k.durum = "aktarmada";
      s.yazma += 1;                       // guncellenmis satir geri yazilir
    },
    oku(id) {                             // takip yaniti: satir + son uc rota adimi icin olaylar
      const k = kayit.get(id);
      s.okuma += 1;
      const d = denetim.get(id);
      s.okuma += d.length;
      return { id: k.id, bolge: k.bolge, durum: k.durum, sonAdim: d.slice(-3).map((o) => o.nokta) };
    },
    tara(idler) {                         // donemsel toplu tarama: yalniz gonderi satiri okunur
      let bayt = 0;
      for (const id of idler) { kayit.get(id); s.okuma += 1; bayt += V7; }
      return bayt;
    },
  };
}

// B: yalniz eklemeli olay kutugu; durum katlamayla turetilir, yerinde guncelleme yoktur.
export function olayKutugu() {
  const kutuk = new Map();
  const s = sayacKur();
  const katla = (dizi) => {
    const bas = dizi[0];
    let durum = "kabul-edildi";
    const adim = [];
    for (const o of dizi.slice(1)) { durum = "aktarmada"; adim.push(o.nokta); }
    return { id: bas.id, bolge: bas.bolge, durum, sonAdim: adim.slice(-3) };
  };
  return {
    ad: "olay kutugu", sayac: s,
    olustur(id, bolge) {
      kutuk.set(id, [{ tur: "gonderi-olusturuldu", id, bolge }]);
      s.yazma += 1; s.bayt += V7 + OY2;
    },
    yaz(id, nokta) {
      kutuk.get(id).push({ tur: "aktarmaya-girdi", id, nokta });
      s.yazma += 1; s.bayt += V6 + OY2;   // yazma yolunda okuma yok
    },
    oku(id) {
      const dizi = kutuk.get(id);
      s.okuma += dizi.length;
      return katla(dizi);
    },
    tara(idler) {
      let bayt = 0;
      for (const id of idler) {
        const dizi = kutuk.get(id);
        s.okuma += dizi.length;
        bayt += V7 + OY2 + (dizi.length - 1) * (V6 + OY2);
      }
      return bayt;
    },
  };
}
```

Katlama işlevi son üç rota adımını dizinin kendisinden çıkarır; durumu güncelleyen kalıcılık ise
aynı üç adımı denetim olaylarından okur. İki yolun aynı yanıtı ürettiği ölçümde sınanıyor.

#### Ölçüm

```js
// olay/olc.mjs — ayni olaylar iki kaliciliga da yazilir; istek basina depo islemi ve gonderi
// basina saklanan bayt olculur, sonra K01'in hizlarina ve hacimlerine uygulanir.
import { durumGunceller, olayKutugu, V4, V6, V7, OY2 } from "./kalicilik.mjs";

const NOKTA = ["34", "06", "35", "01", "16"];
const N = 200;

function kosum(depo) {
  let x = 7;
  const sonraki = () => (x = (x * 48271 + 11) % 2147483647);
  const idler = Array.from({ length: N }, (_, i) => `G${i + 1}`);
  for (const id of idler) {
    depo.olustur(id, NOKTA[sonraki() % NOKTA.length]);
    for (let k = 0; k < V4; k += 1) depo.yaz(id, NOKTA[sonraki() % NOKTA.length]);
  }
  const kurulus = { ...depo.sayac };
  depo.sayac.okuma = 0; depo.sayac.yazma = 0;
  const yanit = idler.map((id) => depo.oku(id));
  const sorgu = depo.sayac.okuma / N;
  depo.sayac.okuma = 0;
  const taramaBayt = depo.tara(idler) / N;
  const tarama = { islem: depo.sayac.okuma / N, bayt: taramaBayt };
  depo.sayac.okuma = 0; depo.sayac.yazma = 0;
  for (const id of idler) depo.yaz(id, "16");
  const komut = { okuma: depo.sayac.okuma / N, yazma: depo.sayac.yazma / N };
  return { ad: depo.ad, komut, sorgu, tarama, gonderiBayt: kurulus.bayt / N, yanit };
}

const A = kosum(durumGunceller()), B = kosum(olayKutugu());
const b = (x, n = 2) => x.toFixed(n);
const p = (x, n) => String(x).padStart(n);

console.log(`model: ${N} gonderi, gonderi basina ${V4} durum olayi (K01 V4)`);
console.log(`iki kaliciligin urettigi takip yaniti ayni mi = ${JSON.stringify(A.yanit) === JSON.stringify(B.yanit)}\n`);
console.log(`${"kalicilik".padEnd(16)}${"komut islemi".padStart(14)}${"sorgu islemi".padStart(14)}` +
  `${"tarama islemi".padStart(15)}${"gonderi bayt".padStart(14)}`);
for (const r of [A, B])
  console.log(`${r.ad.padEnd(16)}${`${r.komut.okuma}o + ${r.komut.yazma}y`.padStart(14)}` +
    `${p(r.sorgu, 14)}${p(r.tarama.islem, 15)}${p(r.gonderiBayt, 14)}`);

// K01 hesaplari ve varsayimlari
const OKUMA = 41.67, YAZMA = 97.22, G_GONDERI = 400_000, SAKLAMA = 730, TOPLU_GUN = 30, PENCERE = 4 * 3600;
console.log(`\nK01: depoya ulasan istek/s = ${b(OKUMA + YAZMA)} (${OKUMA} okuma + ${YAZMA} yazma)` +
  `, oran ${b(YAZMA / OKUMA)}`);
console.log(`${"kalicilik".padEnd(16)}${"okuma islemi/s".padStart(16)}${"yazma islemi/s".padStart(16)}` +
  `${"toplam islem/s".padStart(16)}${"islem/istek".padStart(13)}`);
for (const r of [A, B]) {
  const o = OKUMA * r.sorgu + YAZMA * r.komut.okuma, y = YAZMA * r.komut.yazma;
  console.log(`${r.ad.padEnd(16)}${b(o).padStart(16)}${b(y).padStart(16)}${b(o + y).padStart(16)}` +
    `${b((o + y) / (OKUMA + YAZMA)).padStart(13)}`);
}

console.log(`\n${"kalicilik".padEnd(16)}${"gunluk artis MB".padStart(17)}${"saklanan GB".padStart(13)}` +
  `${"toplu okunan GB".padStart(17)}${"toplu Mbit/s".padStart(14)}`);
for (const r of [A, B]) {
  const gunluk = (G_GONDERI * r.gonderiBayt) / 1e6;
  const toplu = (TOPLU_GUN * G_GONDERI * r.tarama.bayt) / 1e9;
  console.log(`${r.ad.padEnd(16)}${b(gunluk).padStart(17)}${b((gunluk * SAKLAMA) / 1000).padStart(13)}` +
    `${b(toplu).padStart(17)}${b((toplu * 1e9 * 8) / (PENCERE * 1e6)).padStart(14)}`);
}

const kat = (f) => f(B) / f(A);
console.log(`\nkutugun katsayilari: gunluk artis x${b(kat((r) => r.gonderiBayt))}` +
  `, toplu okuma x${b(kat((r) => r.tarama.bayt))}, komut islemi x${b(kat((r) => r.komut.okuma + r.komut.yazma))}`);
for (const ek of [0, OY2, 2 * OY2]) {
  const gunluk = (G_GONDERI * (V7 + ek + V4 * (V6 + ek))) / 1e6;
  console.log(`OY2 = ${p(ek, 3)} bayt -> gunluk artis ${b(gunluk)} MB` +
    `, saklanan ${b((gunluk * SAKLAMA) / 1000)} GB`);
}
```

```
model: 200 gonderi, gonderi basina 7 durum olayi (K01 V4)
iki kaliciligin urettigi takip yaniti ayni mi = true

kalicilik         komut islemi  sorgu islemi  tarama islemi  gonderi bayt
durum gunceller        1o + 2y             8              1          2440
olay kutugu            0o + 1y             8              8          2920

K01: depoya ulasan istek/s = 138.89 (41.67 okuma + 97.22 yazma), oran 2.33
kalicilik         okuma islemi/s  yazma islemi/s  toplam islem/s  islem/istek
durum gunceller           430.58          194.44          625.02         4.50
olay kutugu               333.36           97.22          430.58         3.10

kalicilik         gunluk artis MB  saklanan GB  toplu okunan GB  toplu Mbit/s
durum gunceller            976.00       712.48            10.80          6.00
olay kutugu               1168.00       852.64            35.04         19.47

kutugun katsayilari: gunluk artis x1.20, toplu okuma x3.24, komut islemi x0.33
OY2 =   0 bayt -> gunluk artis 976.00 MB, saklanan 712.48 GB
OY2 =  60 bayt -> gunluk artis 1168.00 MB, saklanan 852.64 GB
OY2 = 120 bayt -> gunluk artis 1360.00 MB, saklanan 992.80 GB
```

#### Sayıların Okunması

İlk denetim `durum gunceller` satırının K01'i birebir yeniden ürettiğidir: gönderi başına 2440
bayt, günde 976 MB, 730 günde 712,48 GB, dönemsel taramada 10,80 GB ve 6,00 Mbit/s. K01'in
hesabı bu kalıcılık biçimini varsaymıştı; model o varsayımı yeniden üretmeseydi karşılaştırma
geçersiz olurdu. İkinci denetim iki kalıcılığın aynı takip yanıtını vermesidir.

Yazma yolu kütüğün kazandığı yerdir. Bir durum olayı `durum gunceller` yerleşiminde 3 işlem eder
(1 okuma, 2 yazma), kütükte 1 işlem eder. Kütük komut maliyetini **0,33 katına** indiriyor.
K01'in hızlarında bu, saniyede 194,44 yazma işleminin 97,22'ye ve tablonun hiç saymadığı 97,22
gizli okumanın sıfıra inmesi demektir. İstek başına depo işlemi 4,50'den 3,10'a düşüyor: K01'in
138,89 isteği aslında 625,02 işlem üretiyordu, kütükte 430,58 üretiyor.

Saklama yolu kütüğün kaybettiği yerdir. Gönderi başına 2440 bayt 2920 bayta çıkıyor; günlük
artış 976 MB'dan **1168 MB**'a, saklanan veri 712,48 GB'dan **852,64 GB**'a. Artış 1,20 kat ve
tamamı OY2'den geliyor — duyarlılık satırları bunu doğruluyor: ek yük sıfır alındığında kütüğün
saklama sayıları K01'inkilerle aynı, 120 bayt alındığında saklanan veri 992,80 GB. Buradaki
asıl bulgu şudur: **olay dizisi K01'in tablosunda zaten vardı.** Günlük 976 MB'ın 616 MB'ı olay
kaydıdır; kütük saklanan veriyi katlamıyor, yalnız kaynak olmanın ek alanlarını ekliyor.

Tarama yolu kütüğün kırıldığı yerdir. Dönemsel toplu tarama satıcının otuz gününü gezer ve
`durum gunceller` yerleşiminde gönderi başına tek satır okur. Kütükte aynı bilgi için sekiz olay
okunması ve katlanması gerekir: taranan bayt 10,80 GB'dan **35,04 GB**'a, dört saatlik pencerenin
bant genişliği 6,00 Mbit/s'den **19,47 Mbit/s**'e çıkıyor. K01 bu satırı zaten en büyük kaynak
kalemi olarak işaretlemişti — tepe okuma çıkışının 3,75 katıydı; kütükle birlikte 12,17 katına
çıkıyor. Bir erişim örüntüsü için verilen karar başka bir örüntüyü üç kattan fazla pahalıya
düşürebilir.

#### Anlık Görüntünün İşe Yaramadığı Yer

Alan Odaklı Tasarım kursu kütüğün okuma maliyetine bir çare koymuştu: anlık görüntü, yeniden
kurmayı dizinin başından değil bir noktadan başlatır. O ders kuralı da yazmıştı — görüntü aralığı
ortalama geçmiş uzunluğunun altına inmedikçe okunan olay sayısı düşmez.

Bu sistemde ortalama geçmiş uzunluğu 8'dir (bir oluşturma olayı ve V4 durum olayı). Kural
uygulandığında görüntü aralığının 8'in altında olması gerekir; aralık 1 olduğunda ise saklanan
şey her olaydan sonra güncellenen bir durum satırıdır, yani `durum gunceller` yerleşiminin
kendisi. Kısa geçmişli bir alanda anlık görüntü ayrı bir seçenek üretmez; iki uç arasında
yalnız birkaç adım vardır. Bu kursun sorusu bu yüzden görüntüde değil izdüşümdedir: kütüğü
okuyan sorgu değil, kütükten türetilmiş ayrı bir okuma modeli.

Kütüğün kendi sınırı da burada söylenmeli. Yazılmış bir olay değişmediği için bir kaydın tamamen
silinmesi gerektiğinde dizinin eklemeli olması bir engeldir; K01'in 730 günlük saklama
varsayımı (V12) bu yüzden bir sözleşme kararıdır, teknik bir varsayım değil.

#### Özet

- Kütük yazma yolundaki gizli okumayı kaldırır: durum olayı 3 işlemden 1 işleme, istek başına
  depo işlemi 4,50'den 3,10'a iner; K01'in 138,89 isteğinin işlem karşılığı 625,02'den 430,58'e.
- Kaynak olmanın bedeli OY2 = 60 bayttır: günlük artış 976 → 1168 MB, saklanan veri 712,48 →
  852,64 GB, yani 1,20 kat. Ek yük 0 alındığında K01'in sayıları aynen çıkıyor.
- Olay dizisi K01'in tablosunda zaten vardı — günlük 976 MB'ın 616 MB'ı olay kaydıdır; kütük
  saklamayı katlamaz, yalnız sıra, tür, şema sürümü ve tekillik alanlarını ekler.
- Kırılan örüntü dönemsel taramadır: gönderi başına 1 okuma yerine 8 olay okunur, taranan veri
  10,80 → 35,04 GB, pencere bant genişliği 6,00 → 19,47 Mbit/s.
- Ortalama geçmiş uzunluğu 8 olduğu için anlık görüntü ayrı bir seçenek üretmiyor; aralık 1
  olduğunda görüntü zaten durum satırının kendisidir.

#### Sonraki Adım

Kütük yazma yolunu ucuzlattı ve okuma yolunu pahalıya düşürdü; iki sayı arasındaki fark ancak
okuma yolu kütüğün dışına çıkarılırsa kapanır. Önceki dersin izdüşümü bunu yapıyordu ama komutla
aynı anda güncelleniyordu ve bu yüzden ayrı ölçeklenemiyordu. Kütük artık türetmenin girdisini
sağlıyor: izdüşüm kütüğü kendi hızıyla okuyabilir, geride kalabilir, sıfırdan yeniden
kurulabilir. Sonraki ders bu türetmeyi ele alır — izdüşümün sakladığı ek veriyi, yeniden
üretim için okunan olay sayısını, yeniden üretim sırasında akan yeni olayların yakalanma
koşulunu ve şema değiştiğinde yeniden üretimin ne kadar sürdüğünü ölçer.

### Okuma Modellerinin Türetilmesi

Source: https://academia.sh/tr/kurslar/veri-olcekleme/okuma-modellerinin-turetilmesi

İzdüşümün olay kütüğünden yansıtmayla kurulması ve sıfırdan yeniden üretilmesi: artımlı bakımla yeniden üretimin aynı satırları vermesi, izdüşümün günlük artışa 192 MB ve saklanan veriye 140,16 GB eklemesi, açık pencerenin tam saklamaya göre 146 kat az olay okuması ve yeniden üretimin 2,11 saatlik gerilik penceresi ile yanında üretimin 140,16 GB'lık geçici yer bedeli.

Önceki ders kütüğü yazma yolunun kaynağı yaptı ve bunun bedelini iki yerde gösterdi: saklanan
veri 1,20 katına çıktı, dönemsel tarama 3,24 katına. İkinci sayı kütüğün asıl sınırıdır — bir
sorgunun yanıtı sekiz olayın katlanmasından çıkıyorsa okuma yolu kütüğün uzunluğuna bağlıdır.
Çözüm ilk derste görülmüştü: sorgunun istediği satırı hazır tutmak. Şimdi o satırın nereden
türetildiği belli: kütükten.

Bu ders türetmenin kendisini ele alır. **Yansıtma** kütükteki olayların bir okuma modeline
uygulanmasıdır; ürünü **izdüşümdür** (projection). Ölçülecek üç şey var: izdüşümün ne kadar yer
tuttuğu, sıfırdan yeniden üretilmesinin kaç olay okuduğu ve yeniden üretim sürerken akan yeni
olayların yakalanıp yakalanamadığı.

#### Bir Kütük, Birden Çok İzdüşüm

İzdüşüm bir katlama işlevidir: olay dizisini alır, satır üretir. Katlama saf olduğu için aynı
kütük her zaman aynı satırları verir ve bu, izdüşümün **atılabilir** olmasını sağlar. Atılabilir
olması ölçekleme açısından belirleyicidir — bir okuma modeli bozulduğunda, şeması değiştiğinde
ya da yeni bir düğüme kopyalanması gerektiğinde yedekten değil kütükten kurulur.

Aynı kütükten birden çok izdüşüm türetilebilir ve her biri kendi erişim örüntüsüne göre
biçimlenir. Bu konuda ikisi var: takip numarasına göre tekil okumayı karşılayan **takip
izdüşümü** ve aktarma noktasına göre bekleyen gönderi sayısını tutan **nokta sayacı**. Birincinin
satır sayısı gönderi sayısına, ikincinin satır sayısı aktarma noktası sayısına bağlıdır; ikisi
aynı olaylardan beslenir. Önceden hesaplanmış sonuçların depo içinde tutulması bu kursun Veri
Dağıtımı konusunda ayrıca ele alınıyor; buradaki fark, izdüşümün deponun değil kütüğün türevi
olmasıdır.

```js
// turetme/izdusum.mjs — olay kutugunden turetilen iki okuma modeli. Her izdusum bir katlama
// islevidir; sayaclar okunan olayi ve yazilan satiri ayri tutar. Surec ici modeldir.
export function takipIzdusumu(surum) {
  const satir = new Map();
  const s = { olay: 0, yazma: 0 };
  return {
    ad: `takip v${surum}`, satir, sayac: s,
    uygula(o) {
      s.olay += 1;
      if (o.tur === "gonderi-olusturuldu") {
        const r = { id: o.id, bolge: o.bolge, durum: "kabul-edildi", sonAdim: [] };
        if (surum === 2) r.aktarma = 0;
        satir.set(o.id, r);
        s.yazma += 1;
        return;
      }
      const r = satir.get(o.id);
      if (o.tur === "teslim-edildi") { r.durum = "teslim-edildi"; s.yazma += 1; return; }
      r.durum = "aktarmada";
      r.sonAdim = [...r.sonAdim, o.nokta].slice(-3);
      if (surum === 2) r.aktarma += 1;
      s.yazma += 1;
    },
    goruntu: () => JSON.stringify([...satir.entries()].sort()),
  };
}

// Ikinci okuma modeli: aktarma noktasina gore bekleyen gonderi sayaci. Ayni kutukten turetilir,
// satir sayisi gonderi sayisina degil nokta sayisina baglidir.
export function noktaIzdusumu() {
  const satir = new Map(), yer = new Map();
  const s = { olay: 0, yazma: 0 };
  return {
    ad: "nokta sayaci", satir, sayac: s,
    uygula(o) {
      s.olay += 1;
      if (o.tur === "gonderi-olusturuldu") return;
      const onceki = yer.get(o.id);
      if (onceki !== undefined) { satir.set(onceki, satir.get(onceki) - 1); s.yazma += 1; }
      if (o.tur === "teslim-edildi") { yer.delete(o.id); return; }
      satir.set(o.nokta, (satir.get(o.nokta) ?? 0) + 1);
      s.yazma += 1;
      yer.set(o.id, o.nokta);
    },
    goruntu: () => JSON.stringify([...satir.entries()].sort()),
  };
}

// Kutuk ureteci: her gonderi bir olusturma ve V4 aktarma olayi alir; kapali gonderiler
// ayrica teslim olayi tasir. Uretec sabittir, ayni girdi ayni kutugu verir.
export function kutukUret(sayi, olayBasina) {
  const NOKTA = ["34", "06", "35", "01", "16"];
  let x = 7;
  const sonraki = () => (x = (x * 48271 + 11) % 2147483647);
  const kutuk = [], acik = new Set();
  for (let i = 1; i <= sayi; i += 1) {
    const id = `G${i}`;
    kutuk.push({ tur: "gonderi-olusturuldu", id, bolge: NOKTA[sonraki() % NOKTA.length] });
    for (let k = 0; k < olayBasina; k += 1)
      kutuk.push({ tur: "aktarmaya-girdi", id, nokta: NOKTA[sonraki() % NOKTA.length] });
    if (i % 5 === 0) acik.add(id);
    else kutuk.push({ tur: "teslim-edildi", id });
  }
  return { kutuk, acik };
}
```

`surum` parametresi şema değişikliğini modelliyor: ikinci sürüm satıra bir aktarma sayacı ekler.
Yeni alanın eski satırlara işlenmesinin tek yolu izdüşümü kütükten yeniden üretmektir.

#### Ölçüm

Ölçüm iki katmanlıdır. Model katmanı türetmenin doğruluğunu ve olay başına yazma sayısını
verir; bunlar belirlenimli sayılardır. Hesap katmanı bu sayıları K01'in hacimlerine uygular ve
iki yeni varsayım gerektirir.

**OY3 — bir gönderinin açık kalma süresi: 5 gün.** Gerekçesi, K01'in 730 günlük saklama
varsayımının (V12) sözleşme uyuşmazlıkları için olması, gönderinin akışı için olmamasıdır;
kabulden teslime kadar geçen süre bundan kısadır. Duyarlılığı: 10 gün alınırsa açık pencerenin
olay sayısı ve yeniden üretim süresi iki katına çıkar.

**OY4 — yeniden üretimin olay tüketme hızının canlı akışa oranı: 20.** Gerekçesi, yeniden
üretimin toplu okuma yapması, istek başına ağ turu ve doğrulama taşımamasıdır. Oran seçilir,
mutlak hız değil; bu yüzden sayı bir ölçüm değildir. Duyarlılığı tabloda üç değerle veriliyor
ve bir eşik gösteriyor.

```js
// turetme/olc.mjs — izdusum artimli bakimla ve sifirdan yeniden uretimle kurulur, ikisi
// karsilastirilir; sonra K01'in hacimlerine ve bu konunun iki varsayimina uygulanir.
import { takipIzdusumu, noktaIzdusumu, kutukUret } from "./izdusum.mjs";

const V4 = 7, V5 = 480, V12 = 730, G_GONDERI = 400_000, V8 = 3, GUN = 86_400;
const OY3 = 5, OY4 = 20;   // bu konunun varsayimlari: acik kalma gunu, yeniden uretim kat sayisi
const N = 200;

const { kutuk, acik } = kutukUret(N, V4);
const artimli = takipIzdusumu(1);
for (const o of kutuk) artimli.uygula(o);

const tam = takipIzdusumu(1);
for (const o of kutuk) tam.uygula(o);

const aktifKutuk = kutuk.filter((o) => acik.has(o.id));
const aktif = takipIzdusumu(1);
for (const o of aktifKutuk) aktif.uygula(o);

const v2 = takipIzdusumu(2);
for (const o of kutuk) v2.uygula(o);
const nokta = noktaIzdusumu();
for (const o of kutuk) nokta.uygula(o);

const b = (x, n = 2) => x.toFixed(n);
const p = (x, n) => String(x).padStart(n);
console.log(`kutuk: ${N} gonderi, ${kutuk.length} olay, acik gonderi ${acik.size}` +
  `, aktif kutuk ${aktifKutuk.length} olay`);
console.log(`artimli bakim ile sifirdan yeniden uretim ayni mi = ${artimli.goruntu() === tam.goruntu()}`);
console.log(`aktif yeniden uretim acik gonderileri ayni kuruyor mu = ` +
  `${[...acik].every((id) => JSON.stringify(aktif.satir.get(id)) === JSON.stringify(tam.satir.get(id)))}`);
console.log(`v2 satirinda yeni alan var mi = ${"aktarma" in v2.satir.get("G5")}` +
  `, v1 satirinda = ${"aktarma" in tam.satir.get("G5")}\n`);

console.log(`${"izdusum".padEnd(14)}${"okunan olay".padStart(13)}${"yazilan satir".padStart(15)}` +
  `${"satir sayisi".padStart(14)}${"olay basina yazma".padStart(19)}`);
for (const [ad, r] of [["takip v1 tam", tam], ["takip v1 aktif", aktif],
  ["takip v2 tam", v2], ["nokta sayaci", nokta]])
  console.log(`${ad.padEnd(14)}${p(r.sayac.olay, 13)}${p(r.sayac.yazma, 15)}` +
    `${p(r.satir.size, 14)}${b(r.sayac.yazma / r.sayac.olay).padStart(19)}`);

// K01'e donus: izdusumun sakladigi veri
const gunlukIzdusum = (G_GONDERI * V5) / 1e6;
console.log(`\nizdusum satiri ${V5} bayt (K01 V5), gunde ${G_GONDERI} satir`);
console.log(`izdusumun ekledigi gunluk artis = ${b(gunlukIzdusum)} MB` +
  `, ${V12} gunde ${b((gunlukIzdusum * V12) / 1000)} GB`);
for (const [ad, gun, sak] of [["K01", 976, 712.48], ["olay kutugu (onceki ders)", 1168, 852.64]])
  console.log(`${ad.padEnd(26)} ${b(gun)} MB/gun, ${b(sak)} GB -> izdusumle ` +
    `${b(gun + gunlukIzdusum)} MB/gun, ${b(sak + (gunlukIzdusum * V12) / 1000)} GB`);

// Yeniden uretim: okunan olay ve yakalama
const gunlukOlay = G_GONDERI * (1 + V4);
const tepeAkis = (gunlukOlay / GUN) * V8;
const tamOlay = gunlukOlay * V12, aktifOlay = gunlukOlay * OY3;
console.log(`\ntepe olay akisi = ${b(tepeAkis)} olay/s (K01: gunde ${gunlukOlay} olay, tepe carpani ${V8})`);
console.log(`${"pencere".padEnd(22)}${"okunan olay".padStart(15)}${"kat".padStart(8)}`);
console.log(`${`tam saklama (${V12} gun)`.padEnd(22)}${p(tamOlay, 15)}${b(tamOlay / aktifOlay).padStart(8)}`);
console.log(`${`acik pencere (OY3=${OY3})`.padEnd(22)}${p(aktifOlay, 15)}${"1.00".padStart(8)}`);

console.log(`\n${"OY4".padStart(5)}${"tuketme olay/s".padStart(16)}${"net olay/s".padStart(12)}` +
  `${"acik pencere saat".padStart(19)}${"tam pencere gun".padStart(17)}`);
for (const k of [OY4, OY4 / 2, 1.5, 1]) {
  const tuket = k * tepeAkis, net = tuket - tepeAkis;
  const saat = net <= 0 ? null : aktifOlay / net / 3600;
  console.log(`${b(k, 1).padStart(5)}${b(tuket).padStart(16)}${b(net).padStart(12)}` +
    `${(saat === null ? "yakalayamaz" : b(saat)).padStart(19)}` +
    `${(saat === null ? "yakalayamaz" : b(tamOlay / net / 86400)).padStart(17)}`);
}
console.log(`\nyerinde yeniden uretimde gerilik penceresi = ` +
  `${b(aktifOlay / ((OY4 - 1) * tepeAkis) / 3600)} saat; yaninda uretimde 0`);
console.log(`yaninda uretimin gecici yer bedeli = ${b((gunlukIzdusum * V12) / 1000)} GB` +
  `, tepe saklama ${b(852.64 + 2 * (gunlukIzdusum * V12) / 1000)} GB`);
```

```
kutuk: 200 gonderi, 1760 olay, acik gonderi 40, aktif kutuk 320 olay
artimli bakim ile sifirdan yeniden uretim ayni mi = true
aktif yeniden uretim acik gonderileri ayni kuruyor mu = true
v2 satirinda yeni alan var mi = true, v1 satirinda = false

izdusum         okunan olay  yazilan satir  satir sayisi  olay basina yazma
takip v1 tam           1760           1760           200               1.00
takip v1 aktif          320            320            40               1.00
takip v2 tam           1760           1760           200               1.00
nokta sayaci           1760           2760             5               1.57

izdusum satiri 480 bayt (K01 V5), gunde 400000 satir
izdusumun ekledigi gunluk artis = 192.00 MB, 730 gunde 140.16 GB
K01                        976.00 MB/gun, 712.48 GB -> izdusumle 1168.00 MB/gun, 852.64 GB
olay kutugu (onceki ders)  1168.00 MB/gun, 852.64 GB -> izdusumle 1360.00 MB/gun, 992.80 GB

tepe olay akisi = 111.11 olay/s (K01: gunde 3200000 olay, tepe carpani 3)
pencere                   okunan olay     kat
tam saklama (730 gun)      2336000000  146.00
acik pencere (OY3=5)         16000000    1.00

  OY4  tuketme olay/s  net olay/s  acik pencere saat  tam pencere gun
 20.0         2222.22     2111.11               2.11            12.81
 10.0         1111.11     1000.00               4.44            27.04
  1.5          166.67       55.56              80.00           486.67
  1.0          111.11        0.00        yakalayamaz      yakalayamaz

yerinde yeniden uretimde gerilik penceresi = 2.11 saat; yaninda uretimde 0
yaninda uretimin gecici yer bedeli = 140.16 GB, tepe saklama 1132.96 GB
```

#### Türetmenin Doğruluğu

İlk üç satır türetmenin taşıdığı üç sözü sınıyor. Artımlı bakımla kurulan izdüşüm ile sıfırdan
yeniden üretilen izdüşüm aynı çıktı: yansıtma, olayların hangi hızla uygulandığından bağımsızdır.
Yalnız açık gönderilerin olaylarıyla yapılan kısmi üretim, o gönderilerin satırlarını tam
üretimle aynı kurdu: kapanmış gönderilerin olayları açık gönderilerin satırını etkilemiyor.
Üçüncüsü, ikinci sürüm satırında aktarma sayacı var, birincide yok — şema değişikliği izdüşümün
kendi işidir, kütüğe dokunmaz.

Olay başına yazma sütunu izdüşümler arasındaki farkı gösteriyor. Takip izdüşümü her olayda tek
satır yazar (1,00), çünkü olayın etkilediği satır bellidir. Nokta sayacı 1,57 yazar, çünkü bir
gönderinin bir noktadan ötekine geçmesi iki satırı birden değiştirir. Satır sayısı sütunu ikinci
farkı veriyor: takip izdüşümü 200 satır, nokta sayacı 5 satır. Bir izdüşümün maliyeti kütüğün
uzunluğuna değil, olayın kaç satırı oynattığına ve satırların ne kadar yer tuttuğuna bağlıdır.

#### Saklama Bedeli

İzdüşüm satırı takip yanıtının gövdesidir ve boyu K01'de tanımlıdır (V5, 480 bayt). Günde
400.000 gönderi için 192 MB, 730 günlük saklama penceresinde 140,16 GB eder.

Bu, önceki dersin sayılarına eklenir: günlük artış 1168 MB'dan **1360 MB**'a, saklanan veri
852,64 GB'dan **992,80 GB**'a çıkar. K01'in 976 MB ve 712,48 GB'ıyla karşılaştırıldığında iki
kararın toplam bedeli günde 384 MB ve 280,32 GB, yani 1,39 kat.

Tablodaki bir çakışma dikkat çekiyor: izdüşüm K01'in kalıcılığına eklendiğinde de 1168 MB/gün
ve 852,64 GB çıkıyor — kaynak olayın ek alanlarının bedeliyle aynı. Bu bir bağıntı değil,
seçilen varsayımların ürettiği bir denk gelmedir; 400.000 × 480 ile 3.200.000 × 60 aynı sayıdır.
K01 aynı türde bir çakışmayı kendi tablosunda da işaretlemişti; ezberlenmez, yeniden hesaplanır.

#### Yeniden Üretim ve Yakalama

Yeniden üretimin ölçüsü süre değil **okunan olay sayısıdır**; süre ortama bağlıdır, okunan olay
değildir. Tam saklama penceresi 2.336.000.000 olay tutuyor. Açık pencere (OY3 = 5 gün) 16.000.000
olay tutuyor: **146 kat** az. Bu oran izdüşüm tasarımının en önemli kararını belirliyor — takip
izdüşümü yalnız açık gönderiler için gerekiyorsa yeniden üretim açık pencereyle sınırlanabilir;
kapanmış gönderilerin satırına da ihtiyaç varsa sınırlanamaz.

Süreye çevirmek OY4'ü gerektiriyor ve tablo bir eşik gösteriyor. Yeniden üretim canlı akışın 20
katı hızla tükettiğinde net ilerleme saniyede 2111,11 olaydır ve açık pencere 2,11 saatte
kapanır; tam pencere 12,81 gün sürer. Oran 10'a inerse süreler 4,44 saat ve 27,04 güne çıkar.
Oran 1,5 iken açık pencere 80 saat, tam pencere 486,67 gün sürer — yani yeniden üretim saklama
penceresinden uzun olur ve hiçbir zaman bitmez. Oran 1 olduğunda tüketme hızı akış hızına eşittir
ve üretim **yakalayamaz**. Kural tek cümleyle yazılır: yeniden üretimin süresi tüketme hızıyla
değil, tüketme hızı ile akış hızı arasındaki **farkla** belirlenir.

#### Yerinde mi, Yanında mı

Yeniden üretim canlı izdüşümün üzerine yapılırsa okuma modeli üretim boyunca eksiktir; gerilik
penceresi 2,11 saattir ve bu sürede takip sorguları eksik satır görür. Alternatif, yeni izdüşümü
eskisinin yanında kurmak ve bittiğinde okuma yolunu ona çevirmektir. Gerilik penceresi bu durumda
0 olur, çünkü eski izdüşüm bütün üretim boyunca çalışmaya devam eder.

Bedel yerdir: üretim boyunca iki izdüşüm birden durur ve 140,16 GB'lık ikinci bir kopya taşınır;
saklanan veri tepede 1132,96 GB'a çıkar. Bu, kursun her dersinde tekrarlanan takasın bir başka
biçimidir — gerilik penceresi yer karşılığında kapatılıyor. Hangisinin seçileceği izdüşümün ne
kadar süre eksik kalabileceğine bağlıdır ve bu bir alan kararıdır, teknik bir tercih değil.

#### Özet

- İzdüşüm kütükten katlamayla türetilir; artımlı bakım ile sıfırdan yeniden üretim aynı satırları
  verdi ve kısmi üretim açık gönderilerin satırını tam üretimle aynı kurdu.
- Bir izdüşümün maliyeti kütüğün uzunluğuna değil olayın oynattığı satır sayısına bağlıdır: takip
  izdüşümü olay başına 1,00 satır ve 200 satır tutuyor, nokta sayacı 1,57 satır ve 5 satır.
- İzdüşüm günlük artışa 192 MB, saklanan veriye 140,16 GB ekliyor: 1168 → 1360 MB/gün, 852,64 →
  992,80 GB. K01'in tabanına göre iki kararın toplamı 1,39 kat.
- Yeniden üretimin ölçüsü okunan olaydır: tam pencere 2.336.000.000, açık pencere (OY3 = 5 gün)
  16.000.000 olay — 146 kat fark.
- Süre tüketme hızıyla akış hızının farkından çıkar: OY4 = 20'de açık pencere 2,11 saat, 10'da
  4,44 saat, 1,5'te 80 saat, 1'de hiç yakalayamaz.
- Yerinde üretim 2,11 saatlik gerilik penceresi bırakır; yanında üretim pencereyi 0 yapar ve
  karşılığında 140,16 GB geçici yer ister (tepede 1132,96 GB).

#### Sonraki Adım

Üç ders boyunca gerilik penceresi bir kaynak ölçüsü olarak ele alındı: kaç olay, kaç saat, kaç
gigabayt. Ama pencerenin içinde duran şey bir sayı değil, bir kullanıcıdır. Bir alıcı adresini
düzeltir ve sayfayı yeniden yükleyip eski adresi görür; bir taşıyıcı durum gönderir ve panoda
kendi gönderdiği olayı bulamaz; iki taraf aynı gönderiyi aynı anda değiştirir ve biri ötekinin
değişikliğinin kaybolduğunu fark eder. Sonraki ders pencerenin arayüzdeki karşılığını ölçer:
kullanıcının gördüğü bayat değer sayısını, beklediği tur sayısını, çakışma bildirimi sayısını ve
her çözümün K01'in depo yüküne geri eklediği isteği.

### Nihai Tutarlılığın Kullanıcıya Yansıması

Source: https://academia.sh/tr/kurslar/veri-olcekleme/nihai-tutarliligin-kullaniciya-yansimasi

Gerilik penceresinin arayüzdeki karşılığı: dört istemci stratejisinin aynı olay dizisinde ürettiği bayat görüş, bekleme turu, çakışma bildirimi ve sessiz kayıp sayıları, iyimser görüntünün gerilik görüş sayısını aşınca sessiz kayıp üretmesi ve kendi yazdığını okumanın yazma deposunu 97,22'den 137,22 işlem/s'ye çıkarması.

Önceki üç ders gerilik penceresini bir kaynak ölçüsü olarak ele aldı: kaç olay, kaç saat, kaç
gigabayt. Pencerenin içinde duran şey ise bir sayı değil, bir kullanıcıdır. Bir alıcı adresini
düzeltir ve sayfayı yeniden yükleyip eski adresi görür; iki taraf aynı gönderiyi aynı anda
değiştirir ve biri ötekinin değişikliğinin kaybolduğunu hiç öğrenmez.

Katalogdaki başlık "nihai tutarlılık" der; bu kursta kullanılan terim **sonunda
tutarlılıktır** (eventual consistency) ve Sistem Tasarımına Giriş kursunda tanımlandı: yazma
durduğunda kopyaların aynı değere yakınsayacağı güvencesi, yakınsamaya kadar okumaların ne
göreceği hakkında sessizlik. Monotonik okuma ve kendi yazdığını okuma güvenceleri de orada
tanımlıydı; burada tekrarlanmaz. Bu dersin sorusu, o sessizliğin arayüzde nasıl doldurulacağı ve
her doldurma biçiminin K01'in hesabına ne eklediğidir.

#### Ölçünün Kaynağı

Arayüz ölçümü için tarayıcı çıktısı üretilmez; kullanıcının gördüğü değer bir **durum
makinesinden** çıkarılır. Kullanıcı bir turda yazar, sonraki turlarda birkaç kez bakar; izdüşüm
yazmaları sabit bir gerilikle uygular ve tur soyut bir adımdır, saniye değildir. Oturumların
dörtte birinde aynı gönderiye başka bir taraf da yazar; kütükte sonra gelen kazanır.

Sonuncusu asıl ölçüdür. **Sessiz kayıp**, kullanıcının yazmasının başka bir yazmayla
geçersizleşmesi ve kullanıcının bunu hiç öğrenmemesidir. Bir arayüz kararının kalitesi, bayat
görüş sayısından çok bu sayıyla ölçülür.

```js
// arayuz/oturum.mjs — kullanici oturumunun surec ici modeli. Tarayici, ekran ve sure yoktur;
// tur soyut bir adimdir ve kullanicinin gordugu deger izdusumun gerilik penceresinden cikar.
// Dort strateji ayni olay dizisini ayni gerilikle gorur, yalniz istemcinin davranisi degisir.
export function kosum({ strateji, gerilik, gorus, oturum }) {
  const yazma = [];
  for (let i = 0; i < oturum; i += 1) {
    const t = i + 1, id = `G${i + 1}`;
    yazma.push({ tur: t, id, deger: `k${i}`, yazan: "kullanici" });
    if (i % 4 === 0) yazma.push({ tur: t, id, deger: `r${i}`, yazan: "rakip" });
  }
  const uygulanmis = (t) => {            // izdusum: tur <= t - gerilik olan yazmalar islenmis
    const m = new Map();
    for (const y of yazma) if (y.tur <= t - gerilik) m.set(y.id, y);
    return m;
  };
  const kutukten = (id) => {             // yazma tarafindan okuma: butun yazmalar gorunur
    let son = null;
    for (const y of yazma) if (y.id === id) son = y;
    return son;
  };

  const s = { bayat: 0, bekleme: 0, istek: 0, yazmaTarafi: 0, bildirim: 0, sessizKayip: 0, dogruTur: 0 };
  for (let i = 0; i < oturum; i += 1) {
    const t0 = i + 1, id = `G${i + 1}`;
    const kazanan = kutukten(id);
    const yenildi = kazanan.yazan !== "kullanici";
    let dogruTur = null, bildirdi = false;

    if (strateji === "ham") {
      for (let k = 1; k <= gorus; k += 1) {
        s.istek += 1;
        const m = uygulanmis(t0 + k);
        if (m.has(id) === false) { s.bayat += 1; continue; }
        if (dogruTur === null) dogruTur = k;
      }
    } else if (strateji === "iyimser") {
      for (let k = 1; k <= gorus; k += 1) {
        s.istek += 1;                    // istemci kendi yazdigini ekranda tutar: bayat yok
        const m = uygulanmis(t0 + k);
        if (m.has(id) === false) continue;
        if (dogruTur === null) dogruTur = k;
        if (yenildi && bildirdi === false) { s.bildirim += 1; bildirdi = true; }
      }
    } else if (strateji === "bekleme") {
      for (let k = 1; k <= gorus + gerilik; k += 1) {
        s.istek += 1;                    // yazma bir surum belirteci dondurur, istemci yoklar
        const m = uygulanmis(t0 + k);
        if (m.has(id) === false) { s.bekleme += 1; continue; }
        dogruTur = k;
        if (yenildi) { s.bildirim += 1; bildirdi = true; }
        break;
      }
      s.istek += gorus - 1;              // bekleme bittikten sonraki normal gorusler
    } else {
      for (let k = 1; k <= gorus; k += 1) {
        s.istek += 1;
        if (k <= gerilik) {              // kendi yazma penceresinde okuma yazma tarafindan
          s.yazmaTarafi += 1;
          if (dogruTur === null) dogruTur = k;
          if (yenildi && bildirdi === false) { s.bildirim += 1; bildirdi = true; }
          continue;
        }
        const m = uygulanmis(t0 + k);
        if (m.has(id) && dogruTur === null) dogruTur = k;
      }
    }
    if (yenildi && bildirdi === false) s.sessizKayip += 1;
    s.dogruTur += dogruTur ?? gorus;
  }
  return { ...s, dogruTur: s.dogruTur / oturum };
}
```

Dört strateji ayrılıyor. **Ham okuma** izdüşümü olduğu gibi gösterir. **İyimser görüntü**
istemcinin kendi yazdığını ekranda tutmasıdır; kaynağı sunucu değil istemcidir. **Belirteçle
bekleme** yazmanın döndürdüğü sürüm belirtecine ulaşana kadar yoklar. **Kendi yazdığını okuma**
gerilik penceresi boyunca okumayı izdüşüm yerine yazma tarafına yollar; oradaki yanıt kütüğün
katlanmasıyla üretilir ve önceki derste ölçüldüğü gibi 8 işlem eder.

```js
// arayuz/olc.mjs — dort strateji ayni gerilikte karsilastirilir, sonra gerilik taranir ve
// sonuclar K01'in okuma hizina uygulanir. Butun sayilar belirlenimlidir; sinif olarak hesaptir.
import { kosum } from "./oturum.mjs";

const OTURUM = 200, GORUS = 4, GERILIK = 3;
const STRATEJI = ["ham", "iyimser", "bekleme", "kendi-yazdigini"];
const b = (x, n = 2) => x.toFixed(n);
const p = (x, n) => String(x).padStart(n);

console.log(`${OTURUM} oturum, oturum basina ${GORUS} gorus, izdusum geriligi ${GERILIK} tur` +
  `, oturumlarin dortte birinde ayni gonderiye rakip yazma var`);
console.log(`\n${"strateji".padEnd(16)}${"bayat gorus".padStart(13)}${"bekleme turu".padStart(14)}` +
  `${"okuma istegi".padStart(14)}${"yazma tarafi".padStart(14)}${"bildirim".padStart(10)}` +
  `${"sessiz kayip".padStart(14)}${"dogru tur".padStart(11)}`);
const taban = {};
for (const st of STRATEJI) {
  const r = kosum({ strateji: st, gerilik: GERILIK, gorus: GORUS, oturum: OTURUM });
  taban[st] = r;
  console.log(`${st.padEnd(16)}${p(r.bayat, 13)}${p(r.bekleme, 14)}${p(r.istek, 14)}` +
    `${p(r.yazmaTarafi, 14)}${p(r.bildirim, 10)}${p(r.sessizKayip, 14)}${b(r.dogruTur).padStart(11)}`);
}

console.log(`\n${"gerilik".padStart(8)}${STRATEJI.map((s) => `${s} bayat`.padStart(24)).join("")}`);
for (const g of [1, 3, 6, 12]) {
  const satir = STRATEJI.map((s) => {
    const r = kosum({ strateji: s, gerilik: g, gorus: GORUS, oturum: OTURUM });
    return `${r.bayat}/${r.sessizKayip}`.padStart(24);
  });
  console.log(`${p(g, 8)}${satir.join("")}`);
}
console.log("(sutunlar: bayat gorus / sessiz kayip)");

// K01'e donus
const OKUMA = 41.67, YAZMA_ISLEM = 97.22, OY5 = 0.04, KUTUK_KATLAMA = 8;
console.log(`\nK01: onbellek arkasi okuma ${OKUMA}/s; yazma deposu ${YAZMA_ISLEM} islem/s (onceki ders)`);
console.log(`OY5 = ${OY5} -> yazma penceresindeki okuma ${b(OKUMA * OY5)}/s\n`);
console.log(`${"strateji".padEnd(16)}${"okuma/oturum".padStart(14)}${"ek okuma/s".padStart(12)}` +
  `${"okuma deposu/s".padStart(16)}${"depoya istek/s".padStart(16)}${"yazma deposu islem/s".padStart(22)}`);
for (const st of STRATEJI) {
  const r = taban[st];
  const oturumBasina = r.istek / OTURUM;
  const pencere = OKUMA * OY5;
  const ek = pencere * (oturumBasina - GORUS);
  const kayan = pencere * (r.yazmaTarafi / OTURUM);
  const okumaDeposu = OKUMA + ek - kayan;
  console.log(`${st.padEnd(16)}${b(oturumBasina).padStart(14)}${b(ek).padStart(12)}` +
    `${b(okumaDeposu).padStart(16)}${b(okumaDeposu + kayan + 97.22).padStart(16)}` +
    `${b(YAZMA_ISLEM + kayan * KUTUK_KATLAMA).padStart(22)}`);
}
for (const oy of [OY5, 2 * OY5]) {
  const pencere = OKUMA * oy;
  const bek = pencere * (taban.bekleme.istek / OTURUM - GORUS);
  const kdy = pencere * (taban["kendi-yazdigini"].yazmaTarafi / OTURUM) * KUTUK_KATLAMA;
  console.log(`OY5 = ${b(oy)} -> bekleme ek okuma ${b(bek)}/s (${b((100 * bek) / OKUMA)}%)` +
    `, kendi-yazdigini yazma deposuna ${b(kdy)} islem/s (${b((100 * kdy) / YAZMA_ISLEM)}%)`);
}
```

```
200 oturum, oturum basina 4 gorus, izdusum geriligi 3 tur, oturumlarin dortte birinde ayni gonderiye rakip yazma var

strateji          bayat gorus  bekleme turu  okuma istegi  yazma tarafi  bildirim  sessiz kayip  dogru tur
ham                       400             0           800             0         0            50       3.00
iyimser                     0             0           800             0        50             0       3.00
bekleme                     0           400          1200             0        50             0       3.00
kendi-yazdigini             0             0           800           600        50             0       1.00

 gerilik               ham bayat           iyimser bayat           bekleme bayat   kendi-yazdigini bayat
       1                    0/50                     0/0                     0/0                     0/0
       3                  400/50                     0/0                     0/0                     0/0
       6                  800/50                    0/50                     0/0                     0/0
      12                  800/50                    0/50                     0/0                     0/0
(sutunlar: bayat gorus / sessiz kayip)

K01: onbellek arkasi okuma 41.67/s; yazma deposu 97.22 islem/s (onceki ders)
OY5 = 0.04 -> yazma penceresindeki okuma 1.67/s

strateji          okuma/oturum  ek okuma/s  okuma deposu/s  depoya istek/s  yazma deposu islem/s
ham                       4.00        0.00           41.67          138.89                 97.22
iyimser                   4.00        0.00           41.67          138.89                 97.22
bekleme                   6.00        3.33           45.00          142.22                 97.22
kendi-yazdigini           4.00        0.00           36.67          138.89                137.22
OY5 = 0.04 -> bekleme ek okuma 3.33/s (8.00%), kendi-yazdigini yazma deposuna 40.00 islem/s (41.15%)
OY5 = 0.08 -> bekleme ek okuma 6.67/s (16.00%), kendi-yazdigini yazma deposuna 80.01 islem/s (82.29%)
```

#### Sayıların Okunması

İlk tablo dört stratejiyi aynı pencerede karşılaştırıyor. Ham okuma 800 görüşün 400'ünde
kullanıcının yazmasından önceki değeri gösteriyor ve 50 oturumda yazma sessizce kayboluyor —
kullanıcı kendi değerini hiç görmediği için sistemin ona söyleyeceği bir şey de yok. Bu, en ucuz
ve en zararlı seçenektir.

İyimser görüntü bayat görüşü sıfıra indiriyor ve 50 çakışmanın hepsini bildiriyor; ek istek
yok. Yalnız iyimser görüntünün doğruluğu bir koşula bağlı ve ikinci tablo o koşulu gösteriyor:
gerilik 6 ve 12 turken sessiz kayıp yeniden 50 oluyor. Sebep aritmetiktir — kullanıcı dört kez
bakıyor, izdüşüm altı tur sonra tazeleniyor, karşılaştırma hiç yapılmıyor. **İyimser görüntü,
gerilik penceresi kullanıcının bakış sayısını aştığında sessizleşir.**

Belirteçle bekleme bayat görüşü ve sessiz kaybı sıfırda tutuyor ama 400 bekleme turu ve 1200
okuma isteği ödüyor: oturum başına 4 yerine 6 istek, yani yüzde 50 fazla. Kendi yazdığını okuma
da ikisini sıfırda tutuyor ve doğru değeri **ilk turda** gösteriyor (öbür üçünde 3,00). Bedeli
600 okumanın yazma tarafına düşmesidir.

Gerilik taraması stratejileri sıralıyor. Gerilik 1 iken dördü de temiz; büyüdükçe önce ham
okuma, sonra iyimser görüntü bozuluyor. Bekleme ile kendi yazdığını okuma her gerilikte temiz
kalıyor, çünkü ikisi de pencereyi **beklemek** ya da **atlamak** yoluyla kapatıyor.

#### Hesaba Dönüş

Sayıları K01'e bağlamak için bir oran gerekiyor.

**OY5 — takip okumalarının kendi yazmasından sonraki gerilik penceresinde yapılan oranı: 0,04.**
Bu konunun varsayımıdır, K01'in tablosuna eklenmez. Gerekçesi, durum olaylarının çoğunun taşıyıcı
entegrasyonundan gelmesi ve arayüzden yazan tarafın yalnız adresini düzelten alıcı ile gönderisini
güncelleyen satıcı olmasıdır. Duyarlılığı 0,08 ile veriliyor.

Son tablo dört stratejinin K01 satırlarına ne yaptığını gösteriyor. Ham okuma ve iyimser görüntü
hiçbir sayıyı oynatmıyor: `depoya ulasan istek/s` 138,89'da kalıyor. Belirteçle bekleme yoklama
istekleriyle onu 142,22'ye çıkarıyor; artış okuma deposunda ve yüzde 8. Kendi yazdığını okuma
istek sayısını değiştirmiyor ama 5,00 istek/s'yi okuma deposundan yazma deposuna kaydırıyor;
her biri 8 işlem ettiği için yazma deposunun yükü 97,22'den **137,22 işlem/s**'ye, yani yüzde
41,15 artıyor. OY5 iki katına çıkarsa artış yüzde 82,29 olur.

Karşılaştırma tek cümlede toplanıyor: bekleme bedeli **okuma** deposuna, kendi yazdığını okuma
bedeli **yazma** deposuna yazar. Üç ders boyunca yazma deposu korunmaya çalışılmıştı; en güçlü
oturum güvencesi tam oraya yükleniyor. Bu yüzden karar gönderi bazında verilir: adresini
düzelten kullanıcı kendi yazdığını okur, gönderiyi yalnız izleyen kullanıcı izdüşümden okur.

#### Özet

- Sonunda tutarlılığın arayüzdeki ölçüsü bayat görüş değil **sessiz kayıptır**: kullanıcının
  yazmasının geçersizleşmesi ve bunu hiç öğrenmemesi.
- Ham okuma 800 görüşün 400'ünde eski değer gösterdi ve 50 sessiz kayıp üretti; iyimser görüntü
  ikisini de sıfırladı ve 50 çakışmayı bildirdi.
- İyimser görüntünün koşulu geriliğin bakış sayısından küçük olmasıdır: gerilik 6 ve 12 turken
  sessiz kayıp yeniden 50 oldu.
- Belirteçle bekleme 400 bekleme turu ve oturum başına 4 yerine 6 istek ödüyor; kendi yazdığını
  okuma doğru değeri ilk turda gösteriyor (öbürlerinde 3,00) ve 600 okumayı yazma tarafına yolluyor.
- K01'e dönüş: bekleme `depoya ulasan istek/s` satırını 138,89'dan 142,22'ye çıkarıyor; kendi
  yazdığını okuma istek sayısını değiştirmeden yazma deposunu 97,22'den 137,22 işlem/s'ye
  (yüzde 41,15) çıkarıyor. OY5 = 0,08'de artış yüzde 82,29 olur.

#### Kurs Kapanışı

Kurs tek bir depo varsayımını kaldırdı ve üç konu boyunca aynı üç soruyu sordu: karar nedir,
K01'in hesabındaki hangi sayıyı oynatıyor, karşılığında hangi sayı büyüyor.

| Ders | Kararı | Oynadığı sayı | Karşılığında büyüyen |
|---|---|---|---|
| Veri Deposu Seçimi | örüntüye göre depo ayrımı | okuma tarafında işlenen kayıt 291,69 → 41,67 | depo işlem hızı 138,89 → 236,11; oran 2,33 → 4,67 |
| Çoğaltma | okumanın kopyalara bölünmesi | kopya başına okuma 41,67 → 8,33 | `saklanan veri GB` 718,24 → 4309,44 (beş kopya); oran 23,33 |
| Federasyon | verinin işleve göre bölünmesi | tarama hızı 833,33 kayıt/s → 0 (çevrimiçi depo) | 976 → 1232 MB/gün, 712,48 → 899,36 GB |
| Parçalama | anahtarın erişim örüntüsünden seçilmesi | düğüm başına 78,11 GB ve 17,36 işlem/s | süzemeyen örüntüde dokunuş 138,89 → 1111,12/s |
| Bölümleme Stratejileri | anahtarı bölüme çeviren kural | sıcak nokta oranı 8,00 → 1,00; taranan kayıt 12.000.000 → 1.500.137 | `depoya ulasan istek/s` 138,89 → 277,78 |
| Denormalizasyon | okuma yolunun kısaltılması | ikincil çekilen kayıt 9000 → 40 | oran 2,33 → 8,71; 712,48 → 733,27 GB |
| Somutlaştırılmış Görünümler | sonucun önceden hesaplanması | tarama 833,33 → 8,33 kayıt/s, 10.800 → 3,28 MB | oran 2,33 → 7,13 (dakikalık tazelemede) |
| Depo Türleri | erişim biçimine göre tür seçimi | çizgede üç adımlık dolaşma 3 → 1 istek | anahtar–değer taramasında 1 → 3001 istek; 138,89 → 236,11/s |
| Önbellek Yerleşimi | isabetin hangi katmanda karşılandığı | uygulamaya varan okuma 416,67 → 60,51/s | ıska başına 9 dokunuş, ortalama 2,531 dokunuş/istek |
| Yanında Okuma | getirme sorumluluğunun uygulamada olması | isabet 0,90 varsayımı 0,7287 çıkıyor | `depoya ulasan istek/s` 138,89 → 210,26; oran 2,33 → 0,86 |
| İçinden ve Arkadan Yazma | yazma yolunun önbelleğe bağlanması | isabet 0,7285 → 0,9170; istek 229,01 → 151,04/s | kayıp penceresi 200 olay, 44.000 bayt |
| Önden Tazeleme | süre dolmadan yenileme | bekleyen istek 57.815 → 34.270 | tazeleme 165.730, boşa 27.825; istek 151,04 → 217,67/s |
| Önbelleksiz Tasarım | önbelleğin gerekmediği yer | `depoya ulasan istek/s` 513,89 → 139,90 | tarama örüntüsünde istek başına 2,00 boşa dokunuş |
| Tutarlılık ve Bayat Veri | bayatlık penceresinin kabul edilmesi | pencere 5 → 60 s; istek 250,85 → 140,23/s | bayat oranı 0,001195, en büyük gerilik 1 olay |
| Komut ve Sorgu Ayrımı | okuma ve yazma yollarının ayrılması | sorgu dokunuşu 8 → 1; düğüm 17 → 7 (okuma ×4) | komut 3 → 5 dokunuş; yazma ×4'te düğüm 15 → 21 |
| Olay Kaynaklı Tasarım | değişimin kaynak kayıt olması | istek başına işlem 4,50 → 3,10 | 976 → 1168 MB/gün, 712,48 → 852,64 GB; tarama 10,80 → 35,04 GB |
| Okuma Modellerinin Türetilmesi | izdüşümün kütükten türetilmesi | yeniden üretim 2.336.000.000 → 16.000.000 olay | 1168 → 1360 MB/gün, 852,64 → 992,80 GB |
| Nihai Tutarlılığın Yansıması | pencerenin arayüzde kapatılması | sessiz kayıp 50 → 0; doğru değeri görme 3,00 → 1,00 tur | yazma deposu 97,22 → 137,22 işlem/s |

Tablonun kuralı adıyla anılmalı. Bu kursta bir veri kararı ancak bir **erişim örüntüsüyle**
birlikte savunulabilir. Aynı karar bir örüntüde kazanç, başka bir örüntüde kayıptır: parçalama
anahtarı süzen örüntüde ucuz, süzmeyen örüntüde sekiz kat pahalı; önbellek tekrarlı okumada
kazanç, taramada ek dokunuş; olay kütüğü yazmada kazanç, dönemsel taramada 3,24 kat kayıp;
okuma–yazma ayrımı okuma büyürken kazanç, yazma büyürken kayıp. Örüntü söylenmeden verilen bir
karar ölçülemez, çünkü hangi sayının oynayacağı belli değildir. Kursun kendi varsayımları —
Veri Dağıtımı ile Önbellek Mimarisi konularının kendi adlarıyla yazdıkları ve bu konunun
OY1–OY5'i — K01'in tablosuna karışmadı; her biri gerekçesi ve duyarlılığıyla yazıldı.

Kursun bıraktığı soru bütün bu parçaların ortak varsayımındadır. Veri çoğaltıldı, işleve
bölündü, parçalandı, önbelleğe alındı ve okuma modeli yazma modelinden ayrıldı; her karar bir
sayıyı oynattı ve bir sayıyı büyüttü. Ama bütün ölçümler parçaların **çalıştığını** varsaydı.
Bir kopya düştüğünde, bir parça yavaşladığında, izdüşümü besleyen kütük ile okuma tarafı
arasındaki bağ koptuğunda ne olacağı bu kursta tasarlanmadı: kısmi arıza, yavaşlamanın yayılması,
ağ bölünmesi, devre kesme, geri basınç ve kurtarma hedefleri hiç sayılmadı. Sonraki kurs
**Dayanıklılık ve Güvenilirlik** o soruyu ele alır.

