---
title: 'Meşgul Veritabanı'
source: 'https://academia.sh/tr/kurslar/basarim-ve-izleme/mesgul-veritabani'
course: 'Başarım Karşıt Kalıpları ve İzleme'
language: tr
updated: '2026-08-17T18:10:58+00:00'
license: 'CC BY-SA 4.0'
---

# Meşgul Veritabanı

Aynı belirtiyi veren iki karşıt nedenin bir ölçümle ayrılması: depo katmanı doluyken uygulama katmanının boşta kalması, azaltmayan işin depoda bırakılması ile azaltan işin uygulamaya taşınmasının aynı belirtiyi vermesi, sınır oranının düzeltmenin yönünü seçmesi ve tek bir sayının hangi durumda yanlış planı önerdiği.

Önceki kurs yirmi kalıbı iki ayrı günün sayısıyla savundu, ama hepsi aynı varsayımın üstünde
durdu: arıza **fark edilmiş** olacak. Devre kesici bir eşiğin aşıldığını, devralma bir kopyanın
düştüğünü bilecekti. Farkın nasıl edileceği tasarlanmadı. Bu kurs oradan başlar ve arızadan
değil, arızasız günün kendisinden: bir sistem hiç bozulmadan da kötüleşir, kötüleşmenin adı
konmadan da düzeltilemez.

Bu dersin ve ardından gelen dokuzun yürüttüğü yol üç adımdır. **Belirti** bir sayıdır; "yavaş"
bir belirti değildir. **Ayırt etme**, aynı belirtiyi veren en az iki nedeni birbirinden ayıran
bir ölçümdür; ölçüm yoksa yapılan şey tahmindir ve bu kursta tahmin diye adlandırılır.
**Düzeltme**, kalıbı giderir ve karşılığında neyin büyüdüğünü sayar. Üçü tamamlanmadan bir
**karşıt kalıp** (antipattern) tanınmış olmaz. Ve hiçbir karşıt kalıp her ölçekte yanlış
değildir; her ders aynı biçimin doğru olduğu koşulu bir sayıyla sınırlar.

## Belirti: Bir Katman Dolu, Öteki Boş

Belirti gün sonu ücretlendirme akışında görünüyor. K01'in `kabaca büyüklük hesabı` bu işi
otuz günün 12.000.000 kaydını dört saatlik pencerede taramak olarak yazmıştı: saniyede 833,33
kayıt. İş pencereye sığmıyor. Aynı anda iki **doluluk** okuması var ve ikisi ters yönde: depo
katmanının işlemci doluluğu tepe yapıyor, uygulama düğümlerinin doluluğu düşük kalıyor.
Doluluk M19/K01 `temel-nitelikler/01`'de tanımlandı, burada yeniden tanımlanmaz.

İlk refleks bir tahmindir ve tahmin olduğu için adı konur: "depo yetmiyor, daha büyük makine
gerekiyor." Bu cümlenin tehlikesi yanlış olması değil, **yönsüz** olmasıdır. Aşağıdaki iki neden
aynı belirtiyi verir ve düzeltmeleri birbirinin tersidir; yön seçilmeden yapılan her müdahalenin
yarısı belirtiyi kötüleştirir.

**Neden A — azaltmayan iş depoda.** Depo, sonuç kayıt sayısını hiç değiştirmeyen bir iş yapıyor:
satır başına dize üretimi, biçimlendirme, sunum için kod çözümü. Girdi kaydı ile çıktı kaydı
eşit; harcanan işlemci işi sınırdan geçen veriyi bir bayt bile azaltmıyor.

**Neden B — azaltan iş uygulamada.** Uygulama ham kayıtları çekip toplamayı kendi yapıyor. Depo
satır başına hiç hesap yapmıyor, ama on iki milyon kaydı okuyup seri hâle getirip göndermek
zorunda. Depo yine dolu, işin cinsi başka.

**Meşgul veritabanı** (busy database) bu iki nedenin ortak adıdır: hesabın yanlış katmanda
yapılması. Adın tek başına bir işe yaramadığı buradan görünüyor — ad iki nedeni de kapsıyor,
düzeltme ise nedeni bilmeyi gerektiriyor.

## Dört Plan, Tek Düzenek

Ayırt etme için aynı gün sonu işi dört planla koşuluyor. İki istek var: satıcının **kalem
dökümü** (her gönderi bir satır) ve **fatura satırı** (satıcı-gün başına bir satır, K01'in
V13 = 100 kalem oranı). Her istek iki katman seçimiyle yazılıyor.

Ölçüm gerçek bir motorda yapılıyor ve model ölçeği M19/K04'ün ölçeğidir: 20 satıcı, 30 gün,
günde 100 gönderi. Sayılan üç şey var — sınırdan geçen kayıt, iki katmanda yapılan **satır işi**
(satır başına çalıştırılan hesap sayısı, motora kayıtlı bir sayaç işleviyle sayılıyor) ve sınırdan
geçen bayt. Süre ölçülmüyor: süre bu makineye bağlıdır, sayılan üç nicelik değildir.

```js
// katman/plan.mjs — ayni gun sonu isinin dort plani: sinirdan gecen kayit, sinir orani,
// iki katmanda yapilan satir isi ve tasinan bayt. node:sqlite ile gercek bir motor kosar.
import { DatabaseSync } from "node:sqlite";

const SATICI = 20, GUN = 30, GUNLUK = 100, TARIFE = 40, BOLGE = 12;   // M19/K04 model olcegi
const N = SATICI * GUN * GUNLUK, FATURA = SATICI * GUN;               // 60000 gonderi, 600 fatura satiri
const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE gonderi(no TEXT PRIMARY KEY, satici INT, gun INT, durum INT,
  agirlik REAL, oran REAL, katsayi REAL, indirim REAL);`);
db.exec("BEGIN");
const ekle = 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) {
  const ti = (s * 7 + d + k) % TARIFE, bi = (s + k) % BOLGE;
  ekle.run(`TR-${s}-${d}-${k}`, s, d, k % 5, 1 + (k % 30),
    12 + (ti % 7) * 0.5, 1 + (bi % 5) * 0.1, (s % 9) * 0.01);
}
db.exec("COMMIT");

const TUTAR = "agirlik * oran * katsayi * (1 - indirim)";
const DURUM = "CASE durum WHEN 0 THEN 'kabul' WHEN 1 THEN 'aktarma' WHEN 2 THEN 'dagitimda' " +
  "WHEN 3 THEN 'teslim' ELSE 'bekliyor' END";
const AD = ["kabul", "aktarma", "dagitimda", "teslim", "bekliyor"];
const SQL = (i) => ({                                    // i: satir isini sayan sarmalayici
  D1: `SELECT no, ${i(`printf('%s / %02d. gun', no, gun)`)} AS baslik,
    ${i(`printf('%.2f', ${i(TUTAR)})`)} AS tutar, ${i(`printf('%.1f kg', agirlik)`)} AS agirlik,
    ${i(DURUM)} AS durum FROM gonderi`,
  D2: `SELECT no, gun, ${i(TUTAR)} AS tutar, agirlik, durum FROM gonderi`,
  F1: `SELECT satici, gun, count(*) AS kalem, sum(${i(TUTAR)}) AS tutar FROM gonderi GROUP BY satici, gun`,
  F2: `SELECT satici, gun, agirlik, oran, katsayi, indirim FROM gonderi`,
});
const UYGULAMA = {                                       // uygulama katmaninda yapilan satir isi
  D1: (r) => 0,
  D2: (r) => { let c = 0; for (const x of r) { c += 4;
    void [`${x.no} / ${String(x.gun).padStart(2, "0")}. gun`, x.tutar.toFixed(2),
      `${x.agirlik.toFixed(1)} kg`, AD[x.durum]]; } return c; },
  F1: (r) => { let c = 0; for (const x of r) { c += 2; void [x.tutar.toFixed(2), `${x.kalem} kalem`]; }
    return c; },
  F2: (r) => { const t = new Map(); let c = 0;
    for (const x of r) { c += 1; const k = `${x.satici}/${x.gun}`;
      const v = t.get(k) ?? { kalem: 0, tutar: 0 }; v.kalem += 1;
      v.tutar += x.agirlik * x.oran * x.katsayi * (1 - x.indirim); t.set(k, v); }
    for (const v of t.values()) { c += 2; void [v.tutar.toFixed(2), `${v.kalem} kalem`]; } return c; },
};
const GEREKLI = { D1: N, D2: N, F1: FATURA, F2: FATURA };   // istegin sonucundaki kayit sayisi

let sayac = 0;
db.function("i", (x) => { sayac += 1; return x; });
const olcu = {};
for (const [ad, sorgu] of Object.entries(SQL((e) => `i(${e})`))) {
  sayac = 0;
  const satir = db.prepare(sorgu).all();
  const depo = sayac;
  const bayt = Buffer.byteLength(JSON.stringify(satir.map((r) => Object.values(r))));
  olcu[ad] = { gecen: satir.length, gerekli: GEREKLI[ad], oran: satir.length / GEREKLI[ad],
    depo, uygulama: UYGULAMA[ad](satir), bayt };
}

const PLAN = ["D1", "D2", "F1", "F2"];
const ETIKET = { D1: "D1 dokum, bicim depoda", D2: "D2 dokum, bicim uygulamada",
  F1: "F1 fatura, toplama depoda", F2: "F2 fatura, toplama uygulamada" };
const say = (x) => x.toLocaleString("tr-TR");
console.log(`model: ${say(N)} gonderi, ${SATICI} satici, ${GUN} gun; dokum istegi ${say(N)} satir, ` +
  `fatura istegi ${FATURA} satir dondurur; taranan kayit dort planda da ${say(N)}`);
console.log(`\n${"plan".padEnd(28)}${"gecen kayit".padStart(13)}${"gerekli".padStart(10)}` +
  `${"sinir orani".padStart(13)}${"depoda satir isi".padStart(18)}${"uygulamada".padStart(12)}` +
  `${"gecen bayt".padStart(12)}${"kayit basina".padStart(14)}`);
for (const p of PLAN) {
  const o = olcu[p];
  console.log(ETIKET[p].padEnd(28) + say(o.gecen).padStart(13) + say(o.gerekli).padStart(10) +
    o.oran.toFixed(2).padStart(13) + say(o.depo).padStart(18) + say(o.uygulama).padStart(12) +
    say(o.bayt).padStart(12) + (o.bayt / o.gecen).toFixed(1).padStart(14));
}

const OLCEK = 12_000_000 / N, PENCERE = 4 * 3600;        // K01: 30 gunun 12.000.000 kaydi, 4 saat
console.log(`\nK01 olceginde (olcek ${OLCEK}, pencere ${PENCERE} s, tarama ` +
  `${(12e6 / PENCERE).toFixed(2)} kayit/s); sinir bandi F2 = K01'in 6 Mbit/s'i olarak olceklendi`);
console.log(`${"plan".padEnd(28)}${"depoda satir isi/s".padStart(20)}${"uygulamada/s".padStart(14)}` +
  `${"sinir bandi Mbit/s".padStart(20)}`);
for (const p of PLAN) {
  const o = olcu[p];
  console.log(ETIKET[p].padEnd(28) + ((o.depo * OLCEK) / PENCERE).toFixed(2).padStart(20) +
    ((o.uygulama * OLCEK) / PENCERE).toFixed(2).padStart(14) +
    ((6 * o.bayt) / olcu.F2.bayt).toFixed(3).padStart(20));
}

console.log(`\nKK1 (depoda bir satir isi uygulamadakinin k katina mal olur) ile plan cifti secimi`);
console.log(`${"plan cifti".padEnd(14)}${"k=2".padStart(12)}${"k=6".padStart(12)}${"k=12".padStart(12)}` +
  `${"gecen kayit".padStart(14)}${"sinir bandi Mbit/s".padStart(20)}`);
for (const [a, b] of [["D1", "F1"], ["D2", "F1"], ["D1", "F2"], ["D2", "F2"]]) {
  const depo = olcu[a].depo + olcu[b].depo, uyg = olcu[a].uygulama + olcu[b].uygulama;
  const bayt = olcu[a].bayt + olcu[b].bayt;
  console.log(`${a} + ${b}`.padEnd(14) + [2, 6, 12].map((k) => say(depo * k + uyg).padStart(12)).join("") +
    say(olcu[a].gecen + olcu[b].gecen).padStart(14) + ((6 * bayt) / olcu.F2.bayt).toFixed(3).padStart(20));
}
```

```
model: 60.000 gonderi, 20 satici, 30 gun; dokum istegi 60.000 satir, fatura istegi 600 satir dondurur; taranan kayit dort planda da 60.000

plan                          gecen kayit   gerekli  sinir orani  depoda satir isi  uygulamada  gecen bayt  kayit basina
D1 dokum, bicim depoda             60.000    60.000         1.00           300.000           0   3.991.401          66.5
D2 dokum, bicim uygulamada         60.000    60.000         1.00            60.000     240.000   1.988.049          33.1
F1 fatura, toplama depoda             600       600         1.00            60.000       1.200      13.407          22.3
F2 fatura, toplama uygulamada       60.000       600       100.00                 0      61.200   1.302.519          21.7

K01 olceginde (olcek 200, pencere 14400 s, tarama 833.33 kayit/s); sinir bandi F2 = K01'in 6 Mbit/s'i olarak olceklendi
plan                          depoda satir isi/s  uygulamada/s  sinir bandi Mbit/s
D1 dokum, bicim depoda                   4166.67          0.00              18.386
D2 dokum, bicim uygulamada                833.33       3333.33               9.158
F1 fatura, toplama depoda                 833.33         16.67               0.062
F2 fatura, toplama uygulamada                0.00        850.00               6.000

KK1 (depoda bir satir isi uygulamadakinin k katina mal olur) ile plan cifti secimi
plan cifti             k=2         k=6        k=12   gecen kayit  sinir bandi Mbit/s
D1 + F1            721.200   2.161.200   4.321.200        60.600              18.448
D2 + F1            481.200     961.200   1.681.200        60.600               9.220
D1 + F2            661.200   1.861.200   3.661.200       120.000              24.386
D2 + F2            421.200     661.200   1.021.200       120.000              15.158
```

Tablonun sayıları **ölçüm** sınıfındadır; K01 ölçeğine çevrilmiş satırlar **hesap** sınıfındadır.
Sınır bandı sütununda mutlak bir bayt iddiası yoktur: modelin ham satırı K01'in 900 baytlık
gönderi kaydından dardır, bu yüzden band F2 planı K01'in yazdığı 6 Mbit/s'e eşitlenerek
ölçeklendi. Taşınabilir olan planların birbirine **oranıdır**.

## Sınır Oranı Yönü Seçiyor

Üçüncü sütun tanının kendisidir. **Sınır oranı**, bir isteğin sınırdan geçirdiği kayıt sayısının
o isteğin sonucunda gerçekten bulunan kayıt sayısına bölümüdür. Dört planın üçünde 1,00; F2'de
100,00.

Oran 1,00 olduğunda depo zaten azaltmıştır ve depo yine de doluysa suçlu **azaltmayan iştir**.
D1 bunu tam olarak gösteriyor: sınır oranı 1,00 ama depoda satır işi 300.000, yani kayıt başına
beş. D2 aynı sonucu üretiyor ve depodaki iş 60.000'e, kayıt başına bire iniyor. Fark biçimlendirme
işidir ve o iş sınırdan geçen kaydı bir tane bile azaltmıyor — üstelik **artırıyor**: geçen bayt
3.991.401'den 1.988.049'a, kayıt başına 66,5 bayttan 33,1 bayta iniyor. Depoda üretilen dizeler,
üretildikleri sayılardan geniştir. Düzeltmenin yönü yukarıdır.

Oran 1'in çok üstünde olduğunda azaltma yanlış katmandadır. F2'de depoda satır işi **sıfır** —
depo hiç hesap yapmıyor — ama 60.000 kaydı okuyup seri hâle getirip gönderiyor, oysa isteğin
sonucu 600 satır. F1 aynı sonucu 600 kayıtla veriyor ve sınır bandını 6,000'den 0,062 Mbit/s'e
indiriyor: **96,8 kat.** Düzeltmenin yönü aşağıdır.

İki neden de "depo dolu, uygulama boşta" belirtisini verir, ama biri işi yukarı, öteki aşağı
taşımayı ister. Sınır oranı bu iki durumu tek bir sayıyla ayırır ve ayırdığı şey düzeltmenin
**yönüdür**.

## Tek Sayı Tanı Vermiyor

Yönü bulmak planı seçmeye yetmez. Son tablo bir varsayımla plan çiftlerini karşılaştırıyor.

**KK1 — depoda yapılan bir satır işi, uygulamada yapılan bir satır işinin $k$ katına mal olur;
temel değer $k = 6$.** Gerekçe: uygulama katmanı durumsuzdur ve düğüm eklenerek yatay ölçeklenir
(M19/K01 `temel-nitelikler/03`), depo katmanında ise yazma tek noktadadır ve çekirdek eklemek
makine değiştirmektir. İki katmandaki bir birim iş aynı fiyata alınmaz. Duyarlılık $k = 2$ ve
$k = 12$ sütunlarında verildi.

Ağırlıklı satır işi ölçüsü üç $k$ değerinde de aynı çifti öneriyor: **D2 + F2**, 661.200 birimle
(k = 6). Bu öneri yanlıştır. Aynı satırda sınırdan geçen kayıt 120.000, sınır bandı 15,158
Mbit/s; D2 + F1 çiftinde ise 60.600 kayıt ve 9,220 Mbit/s. Satır işi ölçüsü F2'yi ucuz sanıyor,
çünkü F2'nin depoya yüklediği iş satır işi değil — okuma, seri hâle getirme ve taşımadır ve o
sütunda hiç görünmüyor.

Kursun ilk kuralı budur: **bir belirti tek bir sayıyla açıklanmaz.** Depo iki ayrı para
biriminde meşgul olur — satır başına hesap ve sınırdan geçen bayt — ve bir plan yalnız birine
bakılarak seçilirse öteki büyür. Doğru seçim D2 + F1'dir: her iki istekte de gereksiz iş
katmandan çıkarılmıştır.

## Depoda Kalmanın Doğru Olduğu Koşul

Hesabın depoda yapılması bir yasak değildir; F1 tam olarak bunu yapıyor ve dört planın en
ucuzudur. Koşul ölçülebilir: **depoda yapılan iş, sınırdan geçen kaydı azalttığı ölçüde
haklıdır.** F1'de 60.000 satır işi, sınırdan geçen kaydı 60.000'den 600'e indiriyor — kayıt
başına 100 birim azaltma, K01'in V13 = 100 oranının kendisi. D1'de 240.000 fazladan satır işi
sınırdan geçen kaydı hiç azaltmıyor, baytı 2,01 katına çıkarıyor.

Koşul bir ölçekle sınırlıdır. Fatura satırı başına düşen kalem sayısı 100'den 1'e inseydi —
yani her gönderi kendi fatura satırını doğursaydı — F1 ile F2 arasındaki 96,8 katlık band farkı
kaybolur ve toplamayı depoda yapmanın hiçbir karşılığı kalmazdı. Aynı biçim, aynı sistemde,
başka bir kırılım oranında yanlış tarafa geçer.

## Düzeltmenin Karşılığında Büyüyen

D1'den D2'ye geçiş depodaki satır işini 300.000'den 60.000'e indirdi; uygulamadaki satır işi
0'dan 240.000'e çıktı. K01 ölçeğinde bu, depoda saniyede 4166,67 satır işinden 833,33'e inmek ve
uygulamada 0'dan 3333,33'e çıkmaktır. KK1'in $k = 6$ değerinde takas kârlıdır, $k = 1$ olsaydı
olmazdı — yani düzeltmenin gerekçesi katmanların ölçekleme asimetrisidir, işin kendisi değil.

İkinci karşılık koddadır ve sayılmaz ama görülür: biçimlendirme kuralı depodan çıkınca sorgunun
her tüketicisi onu kendi tarafında tekrar yazmak zorunda kalır. Depoda tek bir yerde duran
kural, uygulamada tüketici sayısı kadar yerde durur. Bu bedel D2'yi yanlış yapmaz; bedelin
adının konmamış olması yanlış yapar.

## Özet

- Tanı üç adımdır: belirti bir sayıdır, ayırt etme aynı belirtiyi veren iki nedeni ayıran bir
  ölçümdür, düzeltme karşılığında büyüyeni sayar. Ölçümsüz yapılan şey tahmindir.
- Meşgul veritabanının iki nedeni aynı belirtiyi verir: azaltmayan iş depoda (D1) ve azaltan iş
  uygulamada (F2). Düzeltmeleri birbirinin tersidir.
- Sınır oranı yönü seçer: 1,00 ise iş yukarı taşınır (D1 → D2, geçen bayt 3.991.401 → 1.988.049),
  1'in çok üstündeyse aşağı indirilir (F2 → F1, band 6,000 → 0,062 Mbit/s, 96,8 kat).
- Tek sayı tanı vermez: ağırlıklı satır işi ölçüsü D2 + F2'yi öneriyor (661.200, k = 6), oysa o
  çift sınırdan 120.000 kayıt ve 15,158 Mbit/s geçiriyor; doğru seçim D2 + F1'dir.
- Hesabın depoda kalması, sınırdan geçen kaydı azalttığı ölçüde haklıdır: F1'de 60.000 satır işi
  kaydı 100 kat azaltıyor. Kırılım oranı 1'e inseydi aynı seçim yanlışa dönerdi.
- Düzeltmenin karşılığı uygulama katmanında büyüyor: satır işi 0 → 240.000 (K01 ölçeğinde
  saniyede 0 → 3333,33) ve biçimlendirme kuralı tek yerden tüketici sayısı kadar yere dağılıyor.

## Sonraki Adım

Sınır oranı, işin hangi katmanda yapılacağını iki katman için çözdü: depo ve uygulama. Oysa
zincirin bir ucu daha var ve orası hiç ölçülmedi. D2 planı biçimlendirmeyi depodan çıkardı ve
uygulamaya koydu; aynı iş bir adım daha ileri gidip **istemciye** de konabilir — sunucu ham
kaydı gönderir, tarayıcı biçimlendirir. Bu, aynı takasın üçüncü katmanıdır ve orada ölçünün
adı değişir: uygulama katmanında iş düğüm eklenerek dağıtılır, istemcide dağıtılacak bir düğüm
yoktur. Sonraki ders bu katmanı ele alır: sunucunun gönderdiği yükün istemcideki işi nasıl
büyüttüğü, aynı yavaşlık belirtisinin sunucu kaynaklı mı istemci kaynaklı mı olduğunu ayıran
ölçüm ve yükü küçültmenin karşılığında sunucuda büyüyen iş.
