---
title: 'Test Senaryosu Modelleme'
source: 'https://academia.sh/tr/kurslar/islevsel-olmayan-test/test-senaryosu-modelleme'
course: 'İşlevsel Olmayan Test'
language: tr
updated: '2026-08-17T18:10:52+00:00'
license: 'CC BY-SA 4.0'
---

# Test Senaryosu Modelleme

Yükün sayısı değil biçimi: iş karışımının hangi payında kusurun görünür olduğunun ölçülmesi, arama ağırlıklı gerçekçi olmayan bir senaryonun aynı hedef hızda yanlış geçme üretmesi, kapalı döngülü üretecin sanal görevli sayısı ile düşünme süresine göre hedef hıza hiç ulaşamaması ve aynı nominal hızın üç ayrı p95 değeri vermesi.

Önceki dersin bütün koşumları tek bir uca, sabit hızda ve aralıksız istek gönderdi. Gerçek
bir gişe günü böyle değildir: görevli önce arar, sonra ödünç verir ve iki işlem arasında
durur. Bu ders yükün **sayısını** değil **biçimini** ele alır ve şunu ölçer: aynı hedef
hız, senaryo değiştiğinde aynı sistemde başka bir karar üretir.

## Senaryonun İki Parametresi

**İş karışımı** (workload mix), toplam yükün istek türlerine dağılımıdır. Bir sistemin
uçları aynı maliyette değildir; karışım, yükün hangi yola düşeceğini belirler ve böylece
hangi kusur sınıfının doyuma yaklaşacağını da belirler.

**Düşünme süresi** (think time), sanal bir kullanıcının iki işlemi arasındaki duraktır ve
yalnız kapalı döngülü üreteçte anlam taşır. Kapalı döngüde $N$ sanal görevli vardır, her
biri yanıtı bekler, $Z$ kadar durur ve yeni istek gönderir; ulaşılan hız $N / (Z + R)$
olur. Buradaki $R$ sistemin kendi yanıt süresidir — yani **yük, sınanan sistemin bir
işlevidir**. Açık döngülü üreteç bu geri beslemeyi taşımaz.

## Eşik ve Varsayımlar

Eşik önceki dersten aynen gelir. **IO1 (varsayım + hesap) — gişe akışının 60 ms'lik sunucu
bütçesinin üç servise eşit paylaştırılması: p95 ≤ 20 ms.** **IO2 (varsayım) — tepe saat
hedefi 1.000 istek/s.** Bu derste iki varsayım daha yazılır.

**IO3 (varsayım) — tepe saatte gişe iş karışımı: isteklerin yarısı ödünç, yarısı arama.**
Gerekçe: gişe tezgâhına gelen kişi ödünç almak için gelir; katalog araması ayrı uçlardan da
yapılır ama tezgâh trafiğinin yarısını aşmaz. **IO4 (varsayım) — kapalı döngü modelinde
sanal görevli sayısı hedef hızdan türetilir: $N = 1000 \cdot (Z + R_0)$, $R_0 \approx 2$
ms.** İki çift sınanır: 22 görevli / 20 ms ve 202 görevli / 200 ms. İkisinin de nominal
hızı 1.000 istek/s'tir.

Kararın sahibi bu derste yayım değil, testin kendisidir: senaryo seçimi hangi kusur
sınıfının ölçüleceğini belirler ve yanlış seçilmiş bir senaryo yeşil rapor eder.

## Düzenek

Servisin iki ucu var. `/ara` katalogda kitap numarasına göre arar ve her iki yapıda da
dizinlidir; `/odunc` üyenin açık ödünç sayısını doğrulayıp beş kayıt yazar ve dizin yalnız
sağlam yapıda vardır. Katalog 110.000 kayıtla başlar, yani tam tarama önceki derstekinden
altı kat pahalıdır.

```js
// kutuphane/sunucu.mjs — gise servisi: /ara katalogda kitap arar, /odunc uyenin acik odunc
// sayisini dogrulayip bes kayit yazar. Argumanlar: <baglanti-noktasi> <yapi 0|1>
// 0 = odunc yolunda uye dizini yok (tam tarama), 1 = dizinli.
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const UYE = 500, BASLANGIC = 110000, KITAP = 5;  // uye basina 220 kayit; gise islemi 5 kitap
const port = Number(process.argv[2]), yapi = process.argv[3];

if (Number.isInteger(port) === false || ["0", "1"].includes(yapi) === false) {
  console.log("kullanim: node kutuphane/sunucu.mjs <baglanti-noktasi> <yapi 0|1>");
} else {
  const db = new DatabaseSync(":memory:");
  db.exec("CREATE TABLE odunc(no INTEGER PRIMARY KEY, uye_no TEXT, kitap_no TEXT, gun INT)");
  db.exec("CREATE INDEX odunc_kitap ON odunc(kitap_no)");      // arama yolu her iki yapida dizinli
  if (yapi === "1") 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 ara = db.prepare("SELECT no, uye_no, gun FROM odunc WHERE kitap_no = ? LIMIT 5");
  const say = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ?");
  const ekle = db.prepare("INSERT INTO odunc(uye_no, kitap_no, gun) VALUES(?,?,?)");
  let arama = 0, odunc = 0, kayit = BASLANGIC, tarama = 0;

  createServer((gelen, yanit) => {
    const u = new URL(gelen.url, "http://yerel");
    yanit.writeHead(200, { "content-type": "application/json" });
    if (u.pathname === "/olcut") { yanit.end(JSON.stringify({ arama, odunc, kayit, tarama })); return; }
    if (u.pathname === "/ara") {
      arama += 1;
      yanit.end(JSON.stringify({ bulunan: ara.all(u.searchParams.get("kitap") ?? "K-1").length }));
      return;
    }
    odunc += 1;
    tarama += yapi === "0" ? kayit : Math.ceil(Math.log2(kayit));   // dizinsiz yapida tam tarama
    const uye = u.searchParams.get("uye") ?? "U-1";
    const n = say.get(uye).n;
    for (let j = 0; j < KITAP; j += 1) ekle.run(uye, `K-${(odunc + j) % 9000}`, odunc % 400);
    kayit += KITAP;
    yanit.end(JSON.stringify({ uye, odunc: n + 1 }));
  }).listen(port);
}
```

Üreteç iki döngü biçimini de taşır. Senaryo yalnız üç sayıdan oluşur: ödünç payı, sanal
görevli sayısı ve düşünme süresi.

```js
// kutuphane/senaryo.mjs — acik ve kapali dongulu uretec; senaryo istek karisimini ve
// dusunme suresini belirler. Argumanlar: <baglanti-noktasi> <yapi 0|1> <senaryo>
import { Agent, request } from "node:http";

const HEDEF = 1000, ESIK = 20, SURE = 3;   // IO2: hedef hiz, IO1: p95 esigi (ms), kosum suresi
const SENARYO = {                          // pay: odunc istegi orani, n/z: kapali dongu
  kolay: { pay: 0.10 }, "pay-25": { pay: 0.25 }, gercekci: { pay: 0.50 }, "pay-100": { pay: 1.00 },
  "kapali-22": { pay: 0.50, n: 22, z: 20 }, "kapali-202": { pay: 0.50, n: 202, z: 200 },
};
const [port, yapi, ad] = [Number(process.argv[2]), process.argv[3], process.argv[4]];
const agent = new Agent({ keepAlive: true, maxSockets: 8192 });
const yuz = (d, p) => { const s = [...d].sort((a, b) => a - b);
  return s.length === 0 ? 0 : s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))]; };
let sira = 0;

function birIstek(kayit, pay) {            // pay orani kadar istek odunc yoluna gider
  const i = sira++;
  const yol = (i * 9973) % 1000 < pay * 1000 ? `/odunc?uye=U-${i % 500}` : `/ara?kitap=K-${i % 9000}`;
  const bas = performance.now();
  return new Promise((coz) => {
    const r = request({ port, path: yol, agent }, (y) => {
      y.resume(); y.on("end", () => { kayit.push(performance.now() - bas); coz(); });
    });
    r.on("error", () => { kayit.push(performance.now() - bas); coz(); });
    r.end();
  });
}

async function acik(sure, hiz, pay) {      // hedef hizda gonderir, yanit beklemez
  const kayit = [], ucus = [];
  let borc = 0, sonT = 0;
  const bas = performance.now();
  while (performance.now() - bas < sure * 1000) {
    const t = (performance.now() - bas) / 1000;
    borc += hiz * (t - sonT); sonT = t;
    while (borc >= 1) { borc -= 1; ucus.push(birIstek(kayit, pay)); }
    await new Promise((c) => setTimeout(c, 5));
  }
  await Promise.all(ucus);
  return { kayit, gecen: (performance.now() - bas) / 1000 };
}

async function kapali(sure, n, z, pay) {   // n sanal gorevli; her istekten sonra z ms durur
  const kayit = [];
  const bas = performance.now();
  const gorevli = async () => {
    while (performance.now() - bas < sure * 1000) {
      await birIstek(kayit, pay);
      await new Promise((c) => setTimeout(c, z));
    }
  };
  await Promise.all(Array.from({ length: n }, gorevli));
  return { kayit, gecen: (performance.now() - bas) / 1000 };
}

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();
});

const s = SENARYO[ad];
if (Number.isInteger(port) === false || s === undefined) {
  console.log(`kullanim: node kutuphane/senaryo.mjs <nokta> <yapi 0|1> ${Object.keys(SENARYO).join("|")}`);
} else {
  await birIstek([], 1);                   // isinma istegi
  const r = s.n === undefined ? await acik(SURE, HEDEF, s.pay) : await kapali(SURE, s.n, s.z, s.pay);
  const o = await olcut();
  console.log(JSON.stringify({ ad, yapi: yapi === "0" ? "dizinsiz" : "dizinli", pay: s.pay,
    dongu: s.n === undefined ? "acik" : `kapali N=${s.n} Z=${s.z}`, p95: yuz(r.kayit, 95),
    hiz: r.kayit.length / r.gecen, odunc: o.odunc, tarama: o.tarama }));
}
agent.destroy();
```

```js
// kutuphane/rapor.mjs — on kosumu tabloya cevirir ve p95 esigini tarar. Arguman: <jsonl>
import { readFileSync } from "node:fs";

const ESIK = 20, SEN = ["kolay", "gercekci", "kapali-22", "kapali-202"];
const s = readFileSync(process.argv[2], "utf8").trim().split("\n").map((x) => JSON.parse(x));
const bul = (y, a) => s.find((r) => r.yapi === y && r.ad === a);
const v = (x) => x.toFixed(2).replace(".", ",");
const g = (x, n) => String(x).padStart(n);
const kar = (x) => (x <= ESIK ? "gecti" : "kaldi");

console.log(`esik p95 <= ${ESIK} ms (IO1), hedef hiz 1000 istek/s (IO2), kosum 3 s`);
console.log("senaryo      dongu              odunc payi  yapi      ulasilan/s  p95(ms)  karar");
for (const a of SEN) for (const y of ["dizinsiz", "dizinli"]) {
  const r = bul(y, a);
  console.log(a.padEnd(13) + r.dongu.padEnd(19) + g(r.pay.toFixed(2).replace(".", ","), 10) +
    "  " + y.padEnd(10) + g(v(r.hiz), 10) + g(v(r.p95), 9) + g(kar(r.p95), 7));
}

console.log("\nodunc payi taramasi (dizinsiz yapi, acik dongu, ayni 1000 istek/s hedefi)");
console.log("odunc payi  odunc istegi/s  p95(ms)  karar   tarama adimi");
for (const a of ["kolay", "pay-25", "gercekci", "pay-100"]) {
  const r = bul("dizinsiz", a);
  console.log(g(r.pay.toFixed(2).replace(".", ","), 10) + g(v(r.odunc / 3), 16) + g(v(r.p95), 9) +
    g(kar(r.p95), 7) + g(r.tarama.toLocaleString("tr-TR"), 15));
}

console.log("\nesik taramasi: dort senaryonun kaci yanlis karar veriyor");
console.log("p95 esigi (ms)  yanlis gecme /4  yanlis kalma /4  ayiran senaryo /4  ayiranlar");
for (const e of [2, 5, 15, 20, 30, 100]) {
  const gec = SEN.filter((a) => bul("dizinsiz", a).p95 <= e);
  const kal = SEN.filter((a) => bul("dizinli", a).p95 > e);
  const ayr = SEN.filter((a) => bul("dizinli", a).p95 <= e && bul("dizinsiz", a).p95 > e);
  console.log(String(e).padEnd(16) + g(gec.length, 15) + g(kal.length, 17) + g(ayr.length, 19) +
    "  " + (ayr.join(", ") || "-"));
}
```

```bash
# olc.sh — on kosum: dort senaryo x iki yapi, arti dizinsiz yapiya iki ek odunc payi.
# Her kosum yeni baslatilmis bir sunucuyla calisir; katalog hep 110.000 kayitla baslar.
rm -f sonuc.jsonl
for Y in 0 1; do
  for S in kolay gercekci kapali-22 kapali-202; do
    node kutuphane/sunucu.mjs 8852 $Y & SUNUCU=$!
    sleep 1.2
    node kutuphane/senaryo.mjs 8852 $Y $S >> sonuc.jsonl
    kill $SUNUCU
    wait $SUNUCU 2>/dev/null
  done
done
for S in pay-25 pay-100; do
  node kutuphane/sunucu.mjs 8852 0 & SUNUCU=$!
  sleep 1.2
  node kutuphane/senaryo.mjs 8852 0 $S >> sonuc.jsonl
  kill $SUNUCU
  wait $SUNUCU 2>/dev/null
done
node kutuphane/rapor.mjs sonuc.jsonl
```

```
esik p95 <= 20 ms (IO1), hedef hiz 1000 istek/s (IO2), kosum 3 s
senaryo      dongu              odunc payi  yapi      ulasilan/s  p95(ms)  karar
kolay        acik                     0,10  dizinsiz      997,90     8,24  gecti
kolay        acik                     0,10  dizinli       997,83     2,84  gecti
gercekci     acik                     0,50  dizinsiz      924,03   568,66  kaldi
gercekci     acik                     0,50  dizinli       997,86     2,85  gecti
kapali-22    kapali N=22 Z=20         0,50  dizinsiz      693,17    28,90  kaldi
kapali-22    kapali N=22 Z=20         0,50  dizinli       977,58     4,17  gecti
kapali-202   kapali N=202 Z=200       0,50  dizinsiz      857,23    77,31  kaldi
kapali-202   kapali N=202 Z=200       0,50  dizinli       969,12    15,92  gecti

odunc payi taramasi (dizinsiz yapi, acik dongu, ayni 1000 istek/s hedefi)
odunc payi  odunc istegi/s  p95(ms)  karar   tarama adimi
      0,10           99,00     8,24  gecti     32.889.780
      0,25          249,67    13,02  gecti     83.790.630
      0,50          498,33   568,66  kaldi    170.033.825
      1,00          999,67  6104,57  kaldi    352.367.505

esik taramasi: dort senaryonun kaci yanlis karar veriyor
p95 esigi (ms)  yanlis gecme /4  yanlis kalma /4  ayiran senaryo /4  ayiranlar
2                             0                4                  0  -
5                             0                1                  3  kolay, gercekci, kapali-22
15                            1                1                  2  gercekci, kapali-22
20                            1                0                  3  gercekci, kapali-22, kapali-202
30                            2                0                  2  gercekci, kapali-202
100                           3                0                  1  gercekci
```

## Karışım Kararı Değiştiriyor

Hedef hız dört koşumda da 1.000 istek/s. Değişen tek şey bu hızın uçlara dağılımı. Ödünç
payı 0,10 iken kusurlu yapı 8,24 milisaniyede kalıyor ve **geçiyor**; 0,25'te 13,02 ile hâlâ
geçiyor; 0,50'de 568,66 milisaniyeye fırlıyor. Aradaki fark sistemde değil senaryodadır.

Nedeni ikinci sütunda: ödünç isteği hızı 99'dan 498'e çıkıyor. Kusurlu yapıda bu uç tam
tarama yapar ve kendi tavanı yaklaşık 400 istek/s'tir; pay 0,25'te tavanın altında, 0,50'de
üstündedir. Tarama adımı bunu koşumdan bağımsız gösterir: 32.889.780'den 170.033.825'e,
**5,2 kat**. Yakalanan kusur sınıfının adı **tek yolun doyumudur**: sistem bütün olarak
hedef hızı karşılarken tek bir uç tavana dayanır ve bütün isteklerin gecikmesini bozar.

Kolay senaryo bunun kaçtığı yerdir. Arama isteği yazması en ucuz olan istektir, bu yüzden
senaryolar arama ağırlıklı yazılmaya eğilimlidir. IO3 gerçekçi karışımı 0,50 diye
yazıyorsa, 0,10 ile koşulan bir test **yanlış geçme** üretir ve kaçırdığı sınıfın adı
bellidir.

## Kapalı Döngü Kendi Yükünü Kısıyor

Alt iki senaryo aynı karışımı (0,50) kapalı döngüyle koşuyor ve ikisinin de nominal hızı
1.000 istek/s. Kusurlu yapıda ulaşılan hız 693,17 ve 857,23 istek/s — yani hedefin yüzde
69,3'ü ve yüzde 85,7'si. **Üreteç, sınadığı sistem yavaşladığı için yükü kendiliğinden
azalttı.** IO2'nin istediği yük hiç uygulanmadı; ölçülen p95 başka bir hızın p95'idir.

Bunun sonucu bir küçültmedir. Açık döngü aynı karışımda 568,66 milisaniye raporladı, kapalı
döngü 28,90 ve 77,31. Aynı sistem, aynı kusur, **yirmi kat ve yedi kat** küçük bir sayı. Bu
koşumda üçü de 20 milisaniyelik eşiği aştığı için karar aynı kaldı; eşik 30 milisaniye
olsaydı kapalı-22 geçer, açık döngü kalırdı.

Sağlam yapı sütunu düşünme süresinin kendi etkisini ayrı gösteriyor. Kusur yokken bile aynı
nominal hız üç ayrı p95 veriyor: açık döngüde 2,85, 22 görevli / 20 ms ile 4,17, 202
görevli / 200 ms ile 15,92 milisaniye — **5,6 kat yayılım**. Sayı sistemin değil, üreteç
parametrelerinin sayısıdır.

## Eşik Taraması ve Bedel

Tarama iki yönlü bir sonuç veriyor. IO1'in 20 milisaniyesinde yanlış geçme 1 (kolay
senaryo), yanlış kalma 0. Eşiği 15 milisaniyeye çekmek yanlış kalmayı bire çıkarıyor ve
kırmızıya dönen koşum **sağlam yapının kapali-202 senaryosudur** — yani eşiği sıkmak,
kusuru değil senaryonun kendi gürültüsünü yakalıyor. 100 milisaniyede yanlış geçme üçe
çıkıyor; yalnız açık döngülü gerçekçi senaryo ayırt ediyor.

Kaçan sınıf da sayılabilir: kapalı döngülü iki senaryo IO2'nin hızına hiç ulaşmadığı için
**hedef hızda doğan doyum** onlarda ölçülmemiştir; kusurlu yapıda eksik kalan yük saniyede
307 ve 143 istektir. Bedelin koşuma bağlı yanı ortama bağlıdır — on koşum bu makinede bir
dakikanın altında sürdü. Koşumdan bağımsız yanı: açık döngülü her koşum 3.000 istek gönderir
ve ödünç isteği sayısı doğrudan paydan gelir (300, 750, 1.500, 3.000); dizinsiz yapıdaki
tarama adımı 32.889.780 ile 352.367.505 arasında, **10,7 kat**.

## Özet

- Senaryonun iki parametresi vardır: iş karışımı yükün hangi uca düşeceğini, düşünme süresi
  kapalı döngüde ulaşılan hızı belirler.
- Aynı 1.000 istek/s hedefi ödünç payı 0,10'da geçti (8,24 ms), 0,25'te geçti (13,02 ms),
  0,50'de kaldı (568,66 ms); kusur sistemde değil, senaryoda görünür oldu.
- Yakalanan sınıf tek yolun doyumudur: ödünç isteği hızı 99'dan 498'e çıkarken uç kendi
  tavanını aşıyor, tarama adımı 5,2 kat artıyor.
- Kapalı döngü hedef hıza ulaşamadı (693,17 ve 857,23 istek/s) ve bozulmayı 20 ile 7 kat
  küçük raporladı; ölçülen p95 istenen yükün p95'i değildir.
- Sağlam yapıda bile üreteç parametreleri p95'i 2,85'ten 15,92 milisaniyeye taşıyor; eşik
  15 milisaniyeye çekilirse yanlış kalma bu gürültüden doğar.

## Sonraki Adım

Buraya kadar her karar tek bir sayıya bakılarak verildi: p95. Bu seçim tartışılmadı. Ortalama
alınsaydı 568,66 milisaniyelik koşum bambaşka görünürdü; p99 seçilseydi karar yine değişirdi
ve üç saniyelik bir koşumda p99'un kaç örneğe dayandığı hiç sorulmadı. Verim ile gecikmenin
birlikte okunması gerektiği de yalnız ima edildi. Sonraki ders ölçüt seçimini ele alır:
ortalamanın neyi gizlediğini, bir yüzdeliğin anlamlı olması için kaç örnek gerektiğini ve
gecikmenin verimden ayrı okunduğunda niçin yanıltıcı olduğunu ölçer.
