---
title: 'Olay Kaynaklı Tasarım'
source: 'https://academia.sh/tr/kurslar/veri-olcekleme/olay-kaynakli-tasarim'
course: 'Veri Katmanı Ölçekleme'
language: tr
updated: '2026-08-17T18:11:02+00:00'
license: 'CC BY-SA 4.0'
---

# Olay Kaynaklı Tasarım

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 5,7 kat büyütmü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.
