---
title: 'Darboğaz Analizi'
source: 'https://academia.sh/tr/kurslar/islevsel-olmayan-test/darbogaz-analizi'
course: 'İşlevsel Olmayan Test'
language: tr
updated: '2026-08-17T18:10:51+00:00'
license: 'CC BY-SA 4.0'
---

# Darboğaz Analizi

Bir yük testi sonucunun bir katmana bağlanması: uygulama, veritabanı ve aktarım sürelerinin istek içinde ayrı ölçülmesi, aynı istemci belirtisini veren üç kipin üç ayrı katmana atfedilmesi, atfetme kuralının pay eşiğiyle taranması ve zamanın doğru katmanda geçtiği hâlde nedenin başka bir kararda olduğu durumun sayıyla gösterilmesi.

Önceki ders raporu şu cümleye indirdi: p99 25,35 milisaniye, eşik 20, kaldı. Cümle bir
kusuru bildirir ama **nerede** olduğunu söylemez. Aynı sayı uygulama katmanındaki bir
hesaptan, veritabanındaki bir sorgudan ya da yanıtın büyüklüğünden doğmuş olabilir ve
üçünün düzeltmesi birbirinden başkadır. Bu ders sonucu bir katmana bağlamayı ele alır.

## Katman Payı

Bir isteğin toplam süresi istemcide ölçülür ve tek bir sayıdır. **Darboğaz** (bottleneck)
o sürenin çoğunu tüketen yerdir; bulunması için sürenin istek **içinde** bölünmesi gerekir.
Sunucu her istekte üç saati okur: sorgu öncesi, sorgu sonrası, hesap sonrası ve yanıt
yazıldıktan sonra. Aradaki farklar **veritabanı**, **uygulama** ve **aktarım** katmanlarının
süreleridir.

Bu üç sayı tek başına yeterli değildir, çünkü her istekte her katman bir miktar iş yapar.
Anlamlı olan **fark**tır: aynı düzenekte hiçbir kusur taşımayan bir **taban** koşumu
alınır ve her katmanın süresi tabana göre farkıyla okunur. **Katman payı**, bir katmanın
farkının istemci farkına bölümüdür.

**IO6 (varsayım) — bir sonucu bir katmana atfetmek için o katmanın payı en az 0,50
olmalıdır; değilse sonuç atfedilemez sayılır.** Gerekçe: pay 0,50'nin altındayken o
katmanın tamamen giderilmesi bile belirtinin yarısından azını götürür ve düzeltme kararı
savunulamaz. Eşik aşağıda taranır.

Kararın sahibi bir bütçedir: atıf, düzeltmenin hangi ekibe ve hangi çalışmaya gideceğini
belirler. Yanlış atıf yalnız zaman kaybettirmez, kusuru yerinde bırakır. Başarım Karşıt
Kalıpları ve İzleme kursu belirtiden nedene giden on tanıyı kurmuştu; burada sorulan soru
ondan öncedir: sonuç hangi katmana **aittir**.

## Düzenek

Ödünç ucu beş kiple koşar. **taban** dizinli sorgu ve küçük yanıt; **db** aynı sorguyu
dizinsiz yapar; **uyg** uygulama katmanında bir hesap turu döner; **ag** açılışta bir kez
çekilmiş 4.000 satırlık bir sayfayı her yanıtta serileştirip yazar; **karma** aynı sayfayı
her istekte veritabanından yeniden çeker. Dördü aynı belirtiyi vermek üzere ayarlanmıştır.

```js
// kutuphane/sunucu.mjs — odunc ucu, bes kiple. Her istekte uc katmanin suresi ayri olculur:
// veritabani (sorgu), uygulama (hesap), aktarim (serilestirme + yaniti yazma).
// Argumanlar: <baglanti-noktasi> <kip taban|db|uyg|ag|karma>
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const UYE = 500, BASLANGIC = 55000, TUR = 29000, SATIR = 4000;   // katalog boyu, hesap turu, sayfa satiri
const port = Number(process.argv[2]), kip = process.argv[3];

if (Number.isInteger(port) === false || ["taban", "db", "uyg", "ag", "karma"].includes(kip) === false) {
  console.log("kullanim: node kutuphane/sunucu.mjs <baglanti-noktasi> taban|db|uyg|ag|karma");
} else {
  const db = new DatabaseSync(":memory:");
  db.exec("CREATE TABLE odunc(no INTEGER PRIMARY KEY, uye_no TEXT, kitap_no TEXT, gun INT)");
  if (kip !== "db") db.exec("CREATE INDEX odunc_uye ON odunc(uye_no)");
  db.exec("BEGIN");
  const doldur = db.prepare("INSERT INTO odunc(uye_no, kitap_no, gun) VALUES(?,?,?)");
  for (let i = 0; i < BASLANGIC; i += 1) doldur.run(`U-${i % UYE}`, `K-${i % 9000}`, i % 400);
  db.exec("COMMIT");

  const say = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ?");
  const cek = db.prepare("SELECT no, kitap_no, gun FROM odunc LIMIT ?");
  const onbellek = kip === "ag" ? cek.all(SATIR) : [];   // ag kipinde sayfa acilista bir kez cekilir
  let istek = 0, sdb = 0, suyg = 0, sag = 0, bayt = 0, tarama = 0, hesap = 0;

  createServer((gelen, yanit) => {
    const u = new URL(gelen.url, "http://yerel");
    if (u.pathname === "/olcut") {
      yanit.writeHead(200, { "content-type": "application/json" });
      yanit.end(JSON.stringify({ istek, sdb, suyg, sag, bayt, tarama, hesap })); return;
    }
    istek += 1;
    const uye = u.searchParams.get("uye") ?? "U-1";
    const t0 = performance.now();
    const n = say.get(uye).n;                        // veritabani katmani
    const sayfa = kip === "karma" ? cek.all(SATIR) : onbellek;
    tarama += kip === "db" ? BASLANGIC : Math.ceil(Math.log2(BASLANGIC));
    const t1 = performance.now();
    let ozet = "";                                   // uygulama katmani
    if (kip === "uyg") for (let k = 0; k < TUR; k += 1) { ozet = `${uye}/${(n + k) % 97}`; hesap += 1; }
    const t2 = performance.now();
    const govde = JSON.stringify({ uye, odunc: n, ozet, sayfa });   // aktarim katmani
    sdb += t1 - t0; suyg += t2 - t1; bayt += govde.length;
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.on("finish", () => { sag += performance.now() - t2; });
    yanit.end(govde);
  }).listen(port);
}
```

Üreteç ilk saniyeyi ölçüme katmaz ve sunucu sayaçlarını o pencerenin başında bir kez daha
okur; böylece hem istemci hem sunucu tarafı aynı aralığı anlatır.

```js
// kutuphane/olc.mjs — acik dongulu uretec; istemci suresini ve sunucunun katman sayaclarini
// okur. Argumanlar: <baglanti-noktasi> <kip> <hedef hiz> <sure s>
import { Agent, request } from "node:http";

const ISINMA = 1;   // ilk saniye olcume katilmaz: JIT ve onbellek isinmasi
const [port, kip, hiz, sure] = process.argv.slice(2).map((x, i) => (i === 1 ? x : Number(x)));
const agent = new Agent({ keepAlive: true, maxSockets: 8192 });
const yuz = (d, p) => { const t = [...d].sort((a, b) => a - b);
  return t[Math.min(t.length - 1, Math.floor((p / 100) * t.length))]; };
let sira = 0;

function birIstek(kayit, sn) {
  const bas = performance.now(), i = sira++;
  return new Promise((coz) => {
    const r = request({ port, path: `/odunc?uye=U-${i % 500}`, agent }, (y) => {
      y.resume(); y.on("end", () => { kayit.push({ ms: performance.now() - bas, sn }); coz(); });
    });
    r.on("error", () => { kayit.push({ ms: performance.now() - bas, sn }); coz(); });
    r.end();
  });
}

const olcut = () => new Promise((coz) => {
  request({ port, path: "/olcut", agent }, (y) => { let g = "";
    y.on("data", (p) => { g += p; }); y.on("end", () => coz(JSON.parse(g))); }).end();
});

if (Number.isInteger(port) === false) {
  console.log("kullanim: node kutuphane/olc.mjs <baglanti-noktasi> <kip> <hedef hiz> <sure>");
} else {
  const kayit = [], ucus = [];
  let borc = 0, sonT = 0, o1 = null;
  const bas = performance.now();
  while (performance.now() - bas < sure * 1000) {
    const t = (performance.now() - bas) / 1000;
    if (o1 === null && t >= ISINMA) o1 = await olcut();     // isinma penceresi burada kapanir
    borc += hiz * (t - sonT); sonT = t;
    while (borc >= 1) { borc -= 1; ucus.push(birIstek(kayit, t)); }
    await new Promise((c) => setTimeout(c, 5));
  }
  await Promise.all(ucus);
  const o2 = await olcut(), n = o2.istek - o1.istek;
  const d = kayit.filter((r) => r.sn >= ISINMA).map((r) => r.ms);
  console.log(JSON.stringify({ kip, hiz, ornek: d.length,
    istemci: d.reduce((a, b) => a + b, 0) / d.length, p95: yuz(d, 95),
    vt: (o2.sdb - o1.sdb) / n, uyg: (o2.suyg - o1.suyg) / n, ag: (o2.sag - o1.sag) / n,
    bayt: (o2.bayt - o1.bayt) / n, tarama: (o2.tarama - o1.tarama) / n }));
}
agent.destroy();
```

```js
// kutuphane/atif.mjs — katman paylarini tabana gore hesaplar, atfi kurala baglar ve
// pay esigini tarar. Arguman: <jsonl>
import { readFileSync } from "node:fs";

const PAY = 0.5;                                   // IO6: bir katmana atfetmek icin en az pay
const GERCEK = { db: "veritabani", uyg: "uygulama", ag: "aktarim", karma: "aktarim" };
const KAT = ["vt", "uyg", "ag"], AD = { vt: "veritabani", uyg: "uygulama", ag: "aktarim" };
const s = readFileSync(process.argv[2], "utf8").trim().split("\n").map((x) => JSON.parse(x));
const bul = (k) => s.find((r) => r.kip === k);
const T = bul("taban"), KIP = ["db", "uyg", "ag", "karma"];
const v = (x, n = 3) => x.toFixed(n).replace(".", ",");
const g = (x, n) => String(x).padStart(n);
const fark = (r) => ({ istemci: r.istemci - T.istemci, vt: r.vt - T.vt, uyg: r.uyg - T.uyg, ag: r.ag - T.ag });
const atif = (r, esik) => {                        // en buyuk pay esigi asiyorsa o katman
  const f = fark(r);
  const en = KAT.reduce((a, k) => (f[k] > f[a] ? k : a), "vt");
  return f[en] / f.istemci >= esik ? AD[en] : "atfedilemez";
};

console.log(`taban: istemci ${v(T.istemci)} ms, vt ${v(T.vt)}, uyg ${v(T.uyg)}, aktarim ${v(T.ag)}, ` +
  `bayt ${Math.round(T.bayt)}`);
console.log("kip    istemci  p95   fark   vt payi  uyg payi  aktarim payi  bayt     atif");
for (const k of KIP) {
  const r = bul(k), f = fark(r);
  console.log(k.padEnd(7) + g(v(r.istemci, 2), 7) + g(v(r.p95, 2), 6) + g(v(f.istemci, 2), 7) +
    g(v(f.vt / f.istemci, 2), 9) + g(v(f.uyg / f.istemci, 2), 10) + g(v(f.ag / f.istemci, 2), 14) +
    g(Math.round(r.bayt), 9) + "  " + atif(r, PAY));
}

console.log("\npay esigi taramasi: dort kipin atfi (gercek katman: db=veritabani, uyg=uygulama, " +
  "ag ve karma=aktarim)");
console.log("pay esigi  dogru atif /4  yanlis atif /4  atfedilemez /4");
for (const e of [0.3, 0.5, 0.7, 0.8, 1.0]) {
  const a = KIP.map((k) => atif(bul(k), e));
  const dogru = a.filter((x, i) => x === GERCEK[KIP[i]]).length;
  const yok = a.filter((x) => x === "atfedilemez").length;
  console.log(v(e, 1).padEnd(11) + g(dogru, 13) + g(4 - dogru - yok, 16) + g(yok, 16));
}
```

```bash
# olc.sh — bes kosum: taban ve dort kip, ayni hedef hizda, her biri yeni bir sunucuyla.
rm -f sonuc.jsonl
for K in taban db uyg ag karma; do
  node kutuphane/sunucu.mjs 8854 $K & SUNUCU=$!
  sleep 1.2
  node kutuphane/olc.mjs 8854 $K 250 6 >> sonuc.jsonl
  kill $SUNUCU
  wait $SUNUCU 2>/dev/null
done
node kutuphane/atif.mjs sonuc.jsonl
```

```
taban: istemci 1,040 ms, vt 0,043, uyg 0,000, aktarim 0,177, bayt 48
kip    istemci  p95   fark   vt payi  uyg payi  aktarim payi  bayt     atif
db        2,99  4,95   1,95     1,09     -0,00         -0,08       48  veritabani
uyg       2,80  4,90   1,76     0,00      0,78          0,02       55  uygulama
ag        2,93  4,87   1,89    -0,01     -0,00          0,84   164730  aktarim
karma     3,14  6,80   2,10     0,56     -0,00          0,33   164730  veritabani

pay esigi taramasi: dort kipin atfi (gercek katman: db=veritabani, uyg=uygulama, ag ve karma=aktarim)
pay esigi  dogru atif /4  yanlis atif /4  atfedilemez /4
0,3                    3               1               0
0,5                    3               1               0
0,7                    3               0               1
0,8                    2               0               2
1,0                    1               0               3
```

## Aynı Belirti, Üç Ayrı Katman

Birinci sütun kararın kendisidir ve dört kipte de aynıdır. İstemci ortalaması bu koşumda
2,80 ile 3,14 milisaniye arasında, yani en yüksek ile en düşük arasında yüzde on ikilik bir
fark var; p95 üçünde 4,87–4,95, birinde 6,80. Yalnız istemci sayısına bakan bir rapor bu
dört yapıyı **birbirinden ayıramaz**.

Pay sütunları ayırıyor. `db` kipinde veritabanı payı 1,09, `uyg` kipinde uygulama payı
0,78, `ag` kipinde aktarım payı 0,84. Üç kip aynı belirtiyi veriyor ve üç ayrı katmana
atfediliyor; düzeltmeleri de birbirinden başkadır — dizin eklemek, hesabı seyreltmek, yanıtı
küçültmek.

Bir payın 1'i aşması ölçümün sınırını gösterir. `db` kipinde veritabanı farkı istemci
farkından büyük çıktı; iki koşumun taban gürültüsü aynı değildir ve fark küçüldükçe bu
gürültü paya oranla büyür. Kural yalnız en büyük paya baktığı için sonuç değişmiyor, ama
pay 1'i aştığında okunacak şey "veritabanı belirtinin tamamını açıklıyor"dur, daha fazlası
değil.

## Atfetme Yanlış Olduğunda

Dördüncü satır kuralın kırıldığı yerdir. `karma` kipinde veritabanı payı 0,56, aktarım payı
0,33; kural veritabanını suçluyor. Ölçüm doğrudur: zaman gerçekten sorguda geçiyor, çünkü
sorgu 4.000 satır okuyor. **Sonuç yine de yanlıştır.**

Kanıt son sütunda ve koşumdan bağımsızdır: `karma` ile `ag`, yanıt başına **aynı 164.730
baytı** gönderiyor. `ag` kipinde o sayfa açılışta bir kez çekilmiş ve önbelleğe alınmıştır,
bu yüzden veritabanı payı sıfırdır ve kural aktarımı suçlar. İki kipin farkı sorguda değil,
**yanıtın büyüklüğünde alınan karardadır** — ve o karar ikisinde de aynıdır. `karma` kipinde
veritabanına dizin eklemek hiçbir şey değiştirmez; sorgu zaten dizin gerektirmiyor, 4.000
satır okuyor.

Kural şudur: **katman payı zamanın nerede geçtiğini söyler, onu hangi kararın ürettiğini
söylemez.** İkincisini söyleyen ölçüm koşumdan bağımsız niceliktir — gönderilen bayt,
okunan satır, taranan kayıt. Bu koşumda `db` kipi istek başına 55.000 satır tarıyor,
ötekiler 16; `ag` ve `karma` istek başına 164.730 bayt gönderiyor, ötekiler 48 ve 55. İki
katmanın aynı belirtiyi verdiği her yerde ayırt eden ölçüm budur.

## Pay Eşiği Taraması

İkinci tablo IO6'nın 0,50'sini kaydırıyor. Tablonun tek tek sayıları koşuma bağlıdır — pay,
ölçülen süre farklarından çıkar ve bu makinede koşumdan koşuma oynar — ama **yönü koşumdan
bağımsızdır** ve karar o yönde verilir. Düşük eşikte dört kipin dördü de bir katmana bağlanır
ve `karma` **yanlış atıf** alır; eşik yükseldikçe önce yanlış atıf sıfırlanır, sonra doğru
atıflar da eşiğin altında kalıp **atfedilemez** olur. Yukarıdaki koşumda dönüş 0,70'te
başlıyor; başka bir koşumda bir basamak önce ya da sonra başlayabilir, çünkü eşiği geçen şey
payın kendisidir.

İki hata biçimi arasındaki takas budur. Düşük eşik suçsuz bir katmanı suçlar; yüksek eşik
kusuru kimseye bağlayamaz ve bulgu bir kayda dönüşmeden kapanır. IO6'nın 0,50'si yanlış
atfı sıfırlamaz — onu sıfırlayan şey eşik değil, koşumdan bağımsız niceliğin okunmasıdır.

## Bedel ve Kaçan Sınıf

Bedelin koşuma bağlı yanı ortama bağlıdır: beş koşum bu makinede kırk saniyenin altında
sürdü. Koşumdan bağımsız yanı sayılır — beş sunucu süreci, koşum başına altı saniyenin beş
saniyesi ölçülüyor (ilk saniye ısınmaya ayrıldı) ve her koşumda istek başına dört saat
okuması yapılıyor, yani enstrümantasyon istek başına dört ek ölçüm ekliyor.

Kaçan sınıf da sayılabilir: bu düzenek yalnız **sunucu içindeki** süreyi bölüyor. İstemci
farkı ile ölçülen katmanların toplamı arasındaki artık — `uyg` kipinde farkın yüzde
yirmisi — hiçbir katmana yazılmıyor. Ağ turu, olay döngüsünde bekleme ve işletim sisteminin
soket işi orada durur. Doygunluğa yaklaşan bir sistemde bu artık büyür ve pay tabanlı
atfetme kullanılamaz hâle gelir; o durumda okunacak ölçüt katman payı değil, önceki dersin
verim–gecikme çiftidir.

## Özet

- Katman payı, bir katmanın taban koşumuna göre farkının istemci farkına bölümüdür; atıf
  kuralı en büyük payın IO6'nın 0,50'sini aşmasını ister.
- Aynı istemci belirtisi (ortalama 2,80–3,14 ms) üç ayrı katmana atfedildi: veritabanı 1,09,
  uygulama 0,78, aktarım 0,84 payla.
- Atfetme `karma` kipinde yanlış: zaman gerçekten sorguda geçiyor ama `ag` ile aynı 164.730
  baytı gönderiyor; nedeni sorgu değil, yanıtın büyüklüğü kararıdır.
- Pay eşiği taraması takası gösteriyor: eşik yükseldikçe önce yanlış atıf, sonra doğru atıf
  düşer ve atfedilemez koşum artar; dönüşün hangi basamakta olduğu koşuma bağlıdır.
- Katman payı zamanın nerede geçtiğini söyler; hangi kararın onu ürettiğini koşumdan
  bağımsız nicelik söyler (55.000 taranan satır, 164.730 gönderilen bayt).

## Sonraki Adım

Buraya kadarki her sayı **tek bir koşumdan** okundu. Bu dersin taban koşumu bir kez alındı
ve bütün paylar ona bölündü; bir katman payının 1'i aşması da, aynı düzeneğin iki
koşumunun aynı sayıyı vermediğini gösteriyor. Üstelik düzenek ilk saniyeyi ölçüme katmıyor
— yani bir ısınma etkisinin varlığı kabul edildi ama büyüklüğü hiç ölçülmedi. Sonraki ders
ölçümün kendisini sınar: ısınmanın kaç koşum sürdüğünü, aynı yapının koşumları arasındaki
değişkenliği ve iki yapı arasındaki bir farkın anlamlı sayılabilmesi için kaç koşum
gerektiğini sayar.
