---
title: 'Önbellek Stratejileri'
source: 'https://academia.sh/tr/kurslar/eszamansiz-isleme/onbellek-stratejileri'
course: 'Önbellekleme, Kuyruklar ve Eşzamansız İşleme'
language: tr
updated: '2026-08-17T18:06:47+00:00'
license: 'CC BY-SA 4.0'
---

# Önbellek Stratejileri

Yanında okuma, içinden yazma ve arkadan yazma stratejilerinin aynı iş yükü üzerinde karşılaştırılması: kaynağa giden okuma ve yazma sayıları, isabet oranı, arkadan yazmanın dayanıklılık ödünü ve yanında okumanın ıska ile yazma arasındaki yarışı.

Önceki ders önbelleği okuma yolunun içine elle yerleştirdi: bak, yoksa kaynaktan getir, koy.
Bu yazımın bir adı vardır ve tek seçenek değildir. Önbellek okuma yolunun yanında durabilir
ya da bütün okumaların üzerinden geçtiği bir katman olabilir. Yazma yolunda ise ayrı bir
soru vardır: kaynak güncellendiğinde önbellekteki kopyaya ne olur — aynı anda mı güncellenir,
silinir mi, yoksa yazma önce önbelleğe mi gider?

Bu ders üç stratejiyi aynı iş yükü üzerinde koşturur: **yanında okuma** (cache-aside),
**içinden yazma** (write-through) ve **arkadan yazma** (write-behind). Ölçülen şey kaynağa
giden okuma ve yazma sayıları ile kaynağın gerçekten geride kaldığı kayıt sayısıdır.

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

Ölçüm, raftaki kopya sayısı üzerinden yapılır: ödünç verme bu sayıyı düşürür, iade artırır.
Bu, kütüphane verisinin en sık yazılan alanıdır ve yazma stratejilerini ayırt etmeye
elverişlidir. Önceki dersteki `kitap` tablosuna bu sayıyı tutan bir sütun eklenir.

```sh
# kur.sh — dersin veritabani: kitap tablosuna raftaki kopya sayisi eklenir
rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL);
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, yazar TEXT NOT NULL,
                    sube_id INTEGER REFERENCES sube(sube_id), rafta INTEGER NOT NULL);
INSERT INTO sube VALUES (1,'Merkez','Ankara'),(2,'Bahçelievler','Ankara'),(3,'Kadıköy','İstanbul');
INSERT INTO kitap SELECT n, 'Kitap ' || n, 'Yazar ' || (n % 10 + 1), n % 3 + 1, 3
  FROM (WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n < 60) SELECT n FROM s);
SQL
sqlite3 kutuphane.db "SELECT 'kitap=' || count(*) || ' toplam_rafta=' || sum(rafta) FROM kitap;"
```

```
kitap=60 toplam_rafta=180
```

İş yükü 1200 işlemdir: %85 okuma, %15 yazma, on iki popüler kitap üzerinde yoğunlaşmış.
Her strateji kendi veritabanı kopyasında koşar, böylece karşılaştırma aynı başlangıç
durumundan yapılır.

## Üç Strateji

Üç kurulum aynı arayüzü verir: `oku` ve `yaz`. Aralarındaki fark yalnız bu iki işlevin
gövdesindedir.

```js
// stratejiler.mjs — ayni is yuku uc stratejiyle: kaynak erisimi ve tutarsizlik olculur
import { DatabaseSync } from "node:sqlite";
import { copyFileSync, rmSync } from "node:fs";

// Ayni is yuku: %85 okuma, %15 yazma; istekler ilk 12 kitapta yogunlasir.
function isYuku(adet) {
  let tohum = 20250729;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  return Array.from({ length: adet }, () => {
    const yaz = rast() < 0.15;
    return [yaz ? "yaz" : "oku", Math.floor(rast() * 12) + 1, yaz ? (rast() < 0.5 ? -1 : 1) : 0];
  });
}

function kaynakAc(dosya) {
  rmSync(dosya, { force: true });
  copyFileSync("kutuphane.db", dosya);
  const db = new DatabaseSync(dosya);
  const s = { okuma: 0, yazma: 0 };
  return {
    s,
    oku: (id) => { s.okuma += 1; return db.prepare("SELECT rafta FROM kitap WHERE kitap_id = ?").get(id).rafta; },
    yaz: (id, d) => { s.yazma += 1; db.prepare("UPDATE kitap SET rafta = ? WHERE kitap_id = ?").run(d, id); },
    denetim: (id) => db.prepare("SELECT rafta FROM kitap WHERE kitap_id = ?").get(id).rafta,
    kapat: () => db.close(),
  };
}

function kosu(ad, kur) {
  const kaynak = kaynakAc(`${ad}.db`);
  const onbellek = new Map();
  const gercek = new Map();                       // is yukunun mantiksal dogrusu
  const o = { isabet: 0, iska: 0, bayat: 0 };
  const { oku, yaz, bitir } = kur(kaynak, onbellek, o);
  for (const [tur, id, delta] of isYuku(1200)) {
    if (tur === "oku") {
      const d = oku(id);
      if (!gercek.has(id)) gercek.set(id, kaynak.denetim(id));
      if (d !== gercek.get(id)) o.bayat += 1;
    } else {
      if (!gercek.has(id)) gercek.set(id, kaynak.denetim(id));
      const yeni = Math.max(0, gercek.get(id) + delta);
      gercek.set(id, yeni);
      yaz(id, yeni);
    }
  }
  const bekleyen = bitir ? bitir() : 0;
  const sapan = [...gercek.keys()].filter((id) => kaynak.denetim(id) !== gercek.get(id)).length;
  kaynak.kapat();
  rmSync(`${ad}.db`, { force: true });
  return { ad, ...kaynak.s, ...o, bekleyen, sapan };
}

const yanindaOkuma = (k, c, o) => ({
  oku(id) {
    if (c.has(id)) { o.isabet += 1; return c.get(id); }
    o.iska += 1; const d = k.oku(id); c.set(id, d); return d;
  },
  yaz(id, d) { k.yaz(id, d); c.delete(id); },     // yazma sonrasi girdi silinir
});

const icindenYazma = (k, c, o) => ({
  oku(id) {
    if (c.has(id)) { o.isabet += 1; return c.get(id); }
    o.iska += 1; const d = k.oku(id); c.set(id, d); return d;
  },
  yaz(id, d) { k.yaz(id, d); c.set(id, d); },     // kaynak ve onbellek birlikte guncellenir
});

const arkadanYazma = (k, c, o) => {
  const bekleyen = new Map();
  let sayim = 0;
  return {
    oku(id) {
      if (c.has(id)) { o.isabet += 1; return c.get(id); }
      o.iska += 1; const d = k.oku(id); c.set(id, d); return d;
    },
    yaz(id, d) {                                  // yazma once onbellege, kaynak sonra
      c.set(id, d); bekleyen.set(id, d);
      if ((sayim += 1) % 20 === 0) { for (const [i, v] of bekleyen) k.yaz(i, v); bekleyen.clear(); }
    },
    bitir: () => bekleyen.size,                   // surec burada dursaydi kaybolacak yazma sayisi
  };
};

const basliklar = ["strateji", "kay.okuma", "kay.yazma", "isabet", "iska", "bayat", "bekleyen", "sapan"];
console.log(basliklar.map((b, i) => (i === 0 ? b.padEnd(16) : b.padStart(10))).join(""));
for (const [ad, kur] of [["yaninda okuma", yanindaOkuma], ["icinden yazma", icindenYazma], ["arkadan yazma", arkadanYazma]]) {
  const r = kosu(ad.replace(/ /g, "-"), kur);
  console.log(ad.padEnd(16) + [r.okuma, r.yazma, r.isabet, r.iska, r.bayat, r.bekleyen, r.sapan]
    .map((n) => String(n).padStart(10)).join(""));
}
```

```
strateji         kay.okuma kay.yazma    isabet      iska     bayat  bekleyen     sapan
yaninda okuma          156       175       869       156         0         0         0
icinden yazma            7       175      1018         7         0         0         0
arkadan yazma            7        74      1018         7         0        10         5
```

## Sayıların Söyledikleri

**Yanında okuma** 156 kaynak okuması yaptı. On iki farklı anahtar var; kalan 144 ıska, her
yazmadan sonra girdinin silinmesinden doğdu. Silinen her girdi bir sonraki okumada yeniden
yüklenir. Bu, stratejinin bedelidir: yazma sıklığı arttıkça ıska sayısı da artar.

**İçinden yazma** 7 kaynak okuması yaptı — yalnız ilk kez görülen her anahtar için bir tane.
Yazma önbellekteki girdiyi silmek yerine yeni değerle güncellediği için girdi hiç düşmedi.
İsabet sayısı 869'dan 1018'e çıktı. Karşılığında önbellek, hiç okunmayacak değerleri de
taşımaya başlar: nadiren okunan bir kitabın raf sayısı her yazmada önbelleğe girer ve yer
kaplar.

**Arkadan yazma** kaynak yazma sayısını 175'ten 74'e düşürdü. Yazmalar önce önbelleğe gitti,
her yirmi yazmada bir toplu olarak kaynağa aktarıldı ve aynı kitaba yapılan ardışık yazmalar
tek bir güncellemede birleşti. Bu, kaynak üzerindeki yazma yükünü yarıdan aşağı indiren tek
stratejidir.

Son iki sütun bu kazancın bedelini gösteriyor. İş yükü bittiğinde 10 yazma hâlâ önbellekteydi
ve kaynaktaki 5 kayıt gerçek değerden sapmış durumdaydı. Süreç o anda dursaydı bu yazmalar
kaybolurdu — önbellek belleğe dayanır, kaynak diske. Arkadan yazma, yazma yükünü düşürmek
için **dayanıklılığı** öder. Kütüphane örneğinde raf sayısının bir kopyası kaybolabilir; aynı
strateji ödünç kaydının kendisi için kullanılamaz.

`bayat` sütununun her satırda sıfır olması dikkat çekicidir. Üç strateji de okuyucuya yanlış
değer vermedi, çünkü hepsi yazmayı önbelleğe yansıttı. Tutarsızlık okuyucuda değil, kaynak
ile önbellek arasında oluştu ve `sapan` sütununda göründü.

## Yanında Okumanın Yarışı

Yukarıdaki koşuda işlemler sırayla çalıştı. Gerçek bir sunucuda iki istek aynı anda ilerler
ve yanında okumanın bilinen bir açığı ortaya çıkar: bir ıska yüklemesi ile bir yazma
çakışabilir. Aşağıdaki blok bu sıralamayı adım adım kurar.

```js
// yaninda-yarisma.mjs — yaninda okumanin klasik yarisi: iska yuklemesi ile yazma cakisir
import { DatabaseSync } from "node:sqlite";
import { copyFileSync, rmSync } from "node:fs";

rmSync("yaris.db", { force: true });
copyFileSync("kutuphane.db", "yaris.db");
const db = new DatabaseSync("yaris.db");
const kaynakOku = (id) => db.prepare("SELECT rafta FROM kitap WHERE kitap_id = ?").get(id).rafta;
const kaynakYaz = (id, d) => db.prepare("UPDATE kitap SET rafta = ? WHERE kitap_id = ?").run(d, id);
const onbellek = new Map();
const yaz = (adim, ne) => console.log(`${String(adim).padStart(2)}. ${ne.padEnd(46)} ` +
  `onbellek=${onbellek.has(7) ? onbellek.get(7) : "-"} kaynak=${kaynakOku(7)}`);

// A istegi: okuma yapiyor, iska aldi ve kaynagi okudu; henuz onbellege koymadi.
const aOkudu = kaynakOku(7);
yaz(1, "A: iska, kaynaktan okudu (deger elinde)");

// B istegi arada tamamlaniyor: bir kopya odunc verildi.
kaynakYaz(7, aOkudu - 1);
onbellek.delete(7);
yaz(2, "B: kaynagi guncelledi, onbellek girdisini sildi");

// A istegi simdi elindeki eski degeri onbellege koyuyor.
onbellek.set(7, aOkudu);
yaz(3, "A: elindeki eski degeri onbellege koydu");

let bayat = 0;
for (let i = 0; i < 50; i++) {
  const d = onbellek.has(7) ? onbellek.get(7) : kaynakOku(7);
  if (d !== kaynakOku(7)) bayat += 1;
}
yaz(4, `sonraki 50 okumadan bayat olan: ${bayat}`);
db.close();
rmSync("yaris.db", { force: true });
```

```
 1. A: iska, kaynaktan okudu (deger elinde)        onbellek=- kaynak=3
 2. B: kaynagi guncelledi, onbellek girdisini sildi onbellek=- kaynak=2
 3. A: elindeki eski degeri onbellege koydu        onbellek=3 kaynak=2
 4. sonraki 50 okumadan bayat olan: 50             onbellek=3 kaynak=2
```

Yazma kuralına harfiyen uyuldu — kaynak güncellendi, girdi silindi — ve önbellek yine de
bayat kaldı. Kusur sıradadır: silme, konmamış bir girdiyi silemez. Bayatlık kendiliğinden
düzelmez; girdi bir daha yazılana ya da süresi dolana kadar yanlış değer sunulur.

Bu, yanında okumanın hiçbir zaman tek başına yeterli olmadığının kanıtıdır. Yanına en az bir
zaman sınırı konur; sıralamayı kesin olarak çözmek gerektiğinde girdiye bir sürüm damgası
eklenir ya da aynı anahtarın yüklenmesi tek bir çağrıya kısılır. Üçü de sonraki derslerin
konusudur.

## Seçim Ölçütü

Üç strateji birbirinin yerine geçen seçenekler değildir; farklı okuma–yazma oranlarına
uyarlar.

Okuma ağırlıklı ve seyrek değişen veride **yanında okuma** yeterlidir; kodun yalnız okuma
yolunu bilmesi gerekir ve önbellek düştüğünde uygulama çalışmayı sürdürür. Aynı anahtarın
hem sık okunduğu hem sık yazıldığı yerde **içinden yazma** ıskaları önler, karşılığında
önbelleği okunmayan değerlerle doldurur. Yazma hızının kaynağın dayanabileceğinin üstüne
çıktığı ve tek tek yazmaların kaybının katlanılabilir olduğu yerde **arkadan yazma** yükü
düşürür; kayıp katlanılamazsa bu strateji elenir.

Yazmanın önbelleğe hiç uğramadığı bir dördüncü seçenek de vardır: **yanından yazma**
(write-around). Kaynak güncellenir, önbellek girdisi ne silinir ne yazılır; girdi süresi
dolana kadar bayat kalır. Bir kerelik yazılıp uzun süre okunmayan verilerde önbelleği
kirletmemek için kullanılır, ama bayatlık penceresi açıkça kabul edilmiş olur.

## Özet

- Yanında okuma yazmada girdiyi siler; 1200 işlemlik iş yükünde 156 kaynak okuması üretti,
  bunun 144'ü silinen girdilerin yeniden yüklenmesidir.
- İçinden yazma girdiyi silmek yerine güncellediği için kaynak okumasını 7'ye indirdi ve
  isabeti 1018'e çıkardı; karşılığında okunmayan değerleri de önbellekte tutar.
- Arkadan yazma kaynak yazmasını 175'ten 74'e düşürdü, çünkü aynı anahtara ardışık yazmalar
  toplu aktarımda birleşti; bedeli, süreç durduğunda kaybolacak 10 bekleyen yazmadır.
- Üç stratejide de okuyucu bayat değer görmedi; tutarsızlık kaynak ile önbellek arasında
  oluştu. Dayanıklılık gerektiren veride arkadan yazma kullanılamaz.
- Yanında okumada ıska yüklemesi ile yazma çakıştığında silme işe yaramaz ve bayat girdi
  kendiliğinden düzelmez; strateji tek başına yeterli değildir.

## Sonraki Adım

Son ölçüm bir soruyu açıkta bıraktı: önbellekteki bayat girdi ne zaman gider? Bu dersteki
stratejilerin hepsi yazma anını yakaladıklarını varsaydı. Yazma başka bir süreçten geldiğinde,
veri doğrudan veritabanında değiştirildiğinde ya da yukarıdaki gibi bir sıralama kusuru
oluştuğunda o varsayım düşer. Girdinin ne kadar yaşayacağı, hangi olayın onu düşüreceği ve
anahtarın kendisinin değişmesiyle eski girdinin nasıl erişilemez kılınacağı ayrı bir karar
kümesidir. Sonraki ders süre, olay ve sürüm tabanlı geçersizleştirmeyi aynı iş yükünde
karşılaştırır ve her birinin ürettiği bayat okuma sayısını ölçer.
