---
title: 'Durumsuz Uygulama Tasarımı'
source: 'https://academia.sh/tr/kurslar/backend-uretim/durumsuz-uygulama-tasarimi'
course: 'Sunucu Güvenliği ve Üretime Alma'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Durumsuz Uygulama Tasarımı

Ölçeklemenin önkoşulunun kodda nerede kırıldığı: bellekteki oturum, kopyaya özel yerel dosya, süreç içi önbellek ve süreç başına kurulan zamanlayıcı için düğüme bağlı çağrı yerlerinin taranıp sayılması, aynı uygulamanın tek kopya ve iki kopyayla koşulup kaç isteğin yanlış sonuç aldığının ölçülmesi ve dört sızıntının görünürlük sırasına dizilmesi.

Önceki ders kopya sayısını bir ayar gibi ele aldı: süreç yöneticisine bir sayı yazıldı, dört süreç
kalktı, istekler kopyalar arasında paylaştırıldı. Üç yerleşimin hepsinde altmış isteğin her biri
herhangi bir kopyaya gidebildi ve sonuç değişmedi.

Bu, ölçeklemenin görünmeyen önkoşuludur ve kendiliğinden sağlanmaz. Kopyalar ancak isteğin
hangisine gittiği fark etmiyorsa çoğaltılabilir. Bu ders o önkoşulun kodda nerede kırıldığını
arar ve iki soruyu sayıyla yanıtlar: **kaç çağrı yeri düğüme bağlıdır** ve ikinci süreç
eklendiğinde **kaç istek yanlış sonuç alır**.

## Dört Bağ

Ödünç uygulamasında dört yer sürecin kendisine bağlıdır. **Bellekteki oturum**: giriş bir belirteç
üretir ve onu bir tabloya yazar. **Yerel dosya**: ödünç makbuzu sürecin kendi dizinine yazılır.
**Süreç içi önbellek**: katalog değeri ilk okuyuşta belleğe alınır. **Zamanlayıcı**: gecikme cezası
süreç içinde kurulan bir sayaçla işlenir.

- **DP1.** Paylaşılan tek kaynak `katalog.json` ve ceza defteridir; makbuz dizini kopyaya özeldir
  ve kopyanın yerel diskini temsil eder.
- **DP2.** İstekler kopyalar arasında sırayla paylaştırılır. Dağıtım kuralı önceki derste ölçüldü;
  buranın ölçüsü dağıtım değil, kopyaların ayrışmasıdır.
- **DP3.** Zamanlayıcı süreç başına tam üç tur işler ve durur. Ceza defterine düşen satır sayısı bu
  yüzden koşumdan bağımsızdır.

```js
// topoloji/uygulama.mjs — odunc uygulamasinin bir kopyasi. Dort durum bu surecin icinde durur:
// oturum tablosu, yerel makbuz dizini, katalog onbellegi ve gecikme cezasi zamanlayicisi.
// Paylasilan tek yer katalog.json ile defter.txt; makbuz dizini kopyaya ozeldir.
import { createServer } from "node:http";
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";

const nokta = Number(process.argv[2]);
const oturum = new Map();                        // bellekteki oturum
const onbellek = new Map();                      // surec ici katalog onbellegi
let belirtecSayaci = 0;

const katalogOku = () => JSON.parse(readFileSync("katalog.json", "utf8"));
const govde = (istek) => new Promise((coz) => { let b = ""; istek.on("data", (p) => { b += p; }); istek.on("end", () => coz(b)); });

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/uygulama.mjs <nokta>");
else {
  mkdirSync(`makbuz-${nokta}`, { recursive: true });          // kopyanin kendi yerel diski
  let tur = 0;
  const zamanlayici = setInterval(() => {                     // her kopya kendi zamanlayicisini kurar
    if (++tur > 3) { clearInterval(zamanlayici); return; }
    appendFileSync("defter.txt", `uye-7 gecikme cezasi 2 (kopya ${nokta}, tur ${tur})\n`);
  }, 150);

  createServer(async (istek, cevap) => {
    cevap.sendDate = false;
    const [, kok, alt] = istek.url.split("/");
    if (kok === "giris") {
      const belirtec = `B${nokta}-${++belirtecSayaci}`;
      oturum.set(belirtec, "uye-7");
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(belirtec);
    } else if (kok === "odunclerim") {
      const uye = oturum.get(istek.headers["x-belirtec"]);
      if (uye === undefined) cevap.writeHead(401, { "x-kopya": String(nokta) }).end("oturum yok");
      else cevap.writeHead(200, { "x-kopya": String(nokta) }).end(uye);
    } else if (kok === "makbuz" && istek.method === "POST") {
      const kimlik = `M${nokta}-${Date.now() % 100000}`;
      writeFileSync(`makbuz-${nokta}/${kimlik}.txt`, "odunc alindi");
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(kimlik);
    } else if (kok === "makbuz") {
      let icerik = null;
      try { icerik = readFileSync(`makbuz-${nokta}/${alt}.txt`, "utf8"); } catch { icerik = null; }
      if (icerik === null) cevap.writeHead(404, { "x-kopya": String(nokta) }).end("makbuz yok");
      else cevap.writeHead(200, { "x-kopya": String(nokta) }).end(icerik);
    } else if (kok === "kitap" && istek.method === "POST") {
      const k = katalogOku();
      k[alt] = Number(await govde(istek));
      writeFileSync("katalog.json", JSON.stringify(k));        // paylasilan kaynak guncellendi
      onbellek.delete(alt);                                    // yalniz bu surecin onbellegi tazelendi
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(String(k[alt]));
    } else if (kok === "kitap") {
      if (onbellek.has(alt) === false) onbellek.set(alt, katalogOku()[alt]);
      cevap.writeHead(200, { "x-kopya": String(nokta) }).end(String(onbellek.get(alt)));
    } else cevap.writeHead(404, { "x-kopya": String(nokta) }).end("yok");
  }).listen(nokta, "127.0.0.1");
}
```

Birinci ölçü koda bakar. Tarayıcı dört kalıbı kaynakta arar ve düğüme bağlı satırları listeler.

```js
// topoloji/tarayici.mjs — kaynakta dugume bagli cagri yerlerini sayar. Her kalip bir durum turudur;
// bulunan satirlar o surece bagli olan ve ikinci kopya eklendiginde ayrisan yerlerdir.
// Ice aktarma satirlari sayilmaz; sayilan sey durumun bildirildigi ve kullanildigi yerlerdir.
import { readFileSync } from "node:fs";

const KALIP = [
  ["bellekteki oturum", /oturum\.(set|get|has|delete)|new Map\(\).*oturum|const oturum/],
  ["yerel dosya", /makbuz-\$\{nokta\}|mkdirSync/],
  ["surec ici onbellek", /onbellek\.(set|get|has|delete)|const onbellek/],
  ["zamanlayici", /setInterval|appendFileSync/],
];

const dosya = process.argv[2];
if (dosya === undefined) console.log("kullanim: node topoloji/tarayici.mjs <kaynak-dosya>");
else {
  const satir = readFileSync(dosya, "utf8").split("\n");
  let toplam = 0;
  console.log("durum turu           cagri yeri  satir");
  for (const [ad, kalip] of KALIP) {
    const yer = satir.map((s, i) => (kalip.test(s) && s.startsWith("import") === false ? i + 1 : 0)).filter(Boolean);
    toplam += yer.length;
    console.log(`${ad.padEnd(21)}${String(yer.length).padStart(10)}  ${yer.join(", ")}`);
  }
  console.log(`${"toplam".padEnd(21)}${String(toplam).padStart(10)}`);
}
```

İkinci ölçü çalışmaya bakar. İstemci dört senaryoyu sırayla koşar ve her birinde kaç isteğin yanlış
sonuç aldığını sayar.

```js
// topoloji/istemci.mjs — istekleri kopyalar arasinda sirayla paylastirir (onceki dersin dagiticisi
// da bunu yapiyordu; burada olculen sey dagitim degil, kopyalarin ayrismasidir) ve dort durum
// turunun her birinde kac istegin yanlis sonuc aldigini sayar.
import { request } from "node:http";
import { readFileSync } from "node:fs";

const [kopyaMetni, etiket] = [process.argv[2] ?? "", process.argv[3] ?? "-"];
const KOPYA = kopyaMetni.split(",").filter(Boolean).map(Number);
const ADET = 6;
let sira = 0;

const bir = (yol, { yontem = "GET", basliklar = {}, govde } = {}) => new Promise((coz) => {
  const hedef = KOPYA[sira++ % KOPYA.length];
  const r = request({ port: hedef, path: yol, method: yontem, headers: basliklar }, (y) => {
    let b = "";
    y.on("data", (p) => { b += p; });
    y.on("end", () => coz({ kod: y.statusCode, govde: b, kopya: y.headers["x-kopya"] }));
  });
  r.on("error", () => coz({ kod: 0, govde: "", kopya: "-" }));
  r.end(govde);
});

const yaz = (ad, yanlis, ek) => {
  console.log(`${etiket} ${ad.padEnd(20)} ${ADET - yanlis}/${ADET} dogru  ${String(yanlis).padStart(2)} yanlis  ${ek}`);
  return yanlis;
};

if (KOPYA.length === 0) console.log("kullanim: node topoloji/istemci.mjs <kopya,kopya> <etiket>");
else {
  const giris = await bir("/giris", { yontem: "POST" });
  const o = await Promise.all(Array.from({ length: ADET }, () => bir("/odunclerim", { basliklar: { "x-belirtec": giris.govde } })));
  const a = yaz("bellekteki oturum", o.filter((y) => y.kod !== 200).length, `oturum ${giris.kopya} kopyasinda`);

  const makbuz = await bir("/makbuz", { yontem: "POST" });
  const m = await Promise.all(Array.from({ length: ADET }, () => bir(`/makbuz/${makbuz.govde}`)));
  const b = yaz("yerel dosya", m.filter((y) => y.kod !== 200).length, `makbuz ${makbuz.kopya} kopyasinda`);

  await Promise.all([bir("/kitap/K1"), bir("/kitap/K1")]);            // iki kopyanin onbellegi isiniyor
  await bir("/kitap/K1", { yontem: "POST", govde: "2" });             // paylasilan katalog 1'den 2'ye
  const k = await Promise.all(Array.from({ length: ADET }, () => bir("/kitap/K1")));
  const c = yaz("surec ici onbellek", k.filter((y) => y.govde !== "2").length, "guncel deger 2");

  const satir = readFileSync("defter.txt", "utf8").trim().split("\n").filter((s) => s.includes("uye-7"));
  const d = Math.max(0, satir.length - 3);
  console.log(`${etiket} ${"zamanlayici".padEnd(20)} ${3}/${satir.length} dogru  ${String(d).padStart(2)} yanlis  ` +
    `uye-7 cezasi ${satir.length} kez islendi`);
  console.log(`${etiket} ${"toplam".padEnd(20)} yanlis sonuc alan istek ${a + b + c + d}/${3 * ADET + 3}`);
}
```

Aynı uygulama iki yerleşimle koşuyor. Kodda tek bir satır değişmiyor; değişen tek şey kaç süreç
kaldırıldığı.

```bash
# olc.sh — ayni uygulama once tek kopyayla, sonra iki kopyayla. Paylasilan katalog her kosumdan
# once sifirlaniyor; defter ve kopyalara ozel makbuz dizinleri de siliniyor.
kur() {                                   # kur <kopya-sayisi>
  rm -rf makbuz-897* defter.txt
  echo '{"K1":1}' > katalog.json
  : > defter.txt
  P=""; N=""
  for i in $(seq 1 "$1"); do node topoloji/uygulama.mjs $((8970 + i)) & P="$P $!"; N="$N,$((8970 + i))"; done
  sleep 0.9
}
kapat() { kill $P 2>/dev/null; wait $P 2>/dev/null; sleep 0.2; }

kur 1; node topoloji/istemci.mjs "${N#,}" "tek kopya"; kapat
kur 2; node topoloji/istemci.mjs "${N#,}" "iki kopya"; kapat
echo
node topoloji/tarayici.mjs topoloji/uygulama.mjs
```

```
tek kopya bellekteki oturum    6/6 dogru   0 yanlis  oturum 8971 kopyasinda
tek kopya yerel dosya          6/6 dogru   0 yanlis  makbuz 8971 kopyasinda
tek kopya surec ici onbellek   6/6 dogru   0 yanlis  guncel deger 2
tek kopya zamanlayici          3/3 dogru   0 yanlis  uye-7 cezasi 3 kez islendi
tek kopya toplam               yanlis sonuc alan istek 0/21
iki kopya bellekteki oturum    3/6 dogru   3 yanlis  oturum 8971 kopyasinda
iki kopya yerel dosya          3/6 dogru   3 yanlis  makbuz 8972 kopyasinda
iki kopya surec ici onbellek   3/6 dogru   3 yanlis  guncel deger 2
iki kopya zamanlayici          3/6 dogru   3 yanlis  uye-7 cezasi 6 kez islendi
iki kopya toplam               yanlis sonuc alan istek 12/21

durum turu           cagri yeri  satir
bellekteki oturum             3  8, 29, 32
yerel dosya                   3  17, 37, 41
surec ici onbellek            4  9, 48, 51, 52
zamanlayici                   2  19, 21
toplam                       12
```

Bütün sayılar belirlenimlidir: istek sayısı sabit, paylaştırma sırayla, zamanlayıcı tur sayısı üç.

## Kodun Söylediği

Tarama elli beş satırlık bir kaynakta **on iki çağrı yeri** buluyor. Sayının kendisinden çok
dağılımı önemlidir. Oturum üç yerde geçiyor: bildirildiği satır, yazıldığı satır, okunduğu satır.
Önbellek dört yerde. Zamanlayıcı yalnız iki yerde ve o iki yer bir uç noktanın içinde bile değil —
sunucu kurulmadan önce, modülün gövdesinde duruyor.

Bu dağılım bir düzeltme planının maliyetidir. Oturumu paylaşılan bir depoya taşımak üç satıra
dokunur, önbelleği dörde. Zamanlayıcı iki satırdır ama ona dokunmak kodu değil **yerleşimi**
değiştirir: işin bir kopyada çalışması, ötekilerde çalışmaması gerekir; bu bir kod satırı değil,
bir dağıtım kararıdır.

Bir sayının okunmasında dikkat gerekir. Tarama on iki çağrı yeri, koşum on iki yanlış sonuç veriyor;
ikisi aynı şey değildir ve eşitlikleri bu düzeneğe özgüdür. Biri kaynağın **statik** ölçüsüdür ve
kopya sayısından bağımsızdır; öteki isteklerin **çalışma anındaki** ölçüsüdür ve kopya sayısıyla
büyür.

## Yirmi Bir İsteğin On İkisi

Tek kopyalı yerleşimde yirmi bir isteğin hepsi doğru sonuç aldı. Uygulama doğru çalışıyor,
sınamalardan geçiyor ve hiçbir sızıntı görünmüyor. Sızıntıların hepsi tek kopyada **doğru
davranıştır**: oturumu bellekte tutmak hızlıdır, makbuzu yerel diske yazmak ucuzdur, katalog
önbelleği bir okuma tasarrufudur, zamanlayıcı işini yapar.

İkinci süreç eklendiğinde aynı kod yirmi bir isteğin on ikisinde yanlış sonuç veriyor. Dört türün
her birinde tam üçer istek, çünkü paylaştırma sırayla yapılıyor ve durumun tutulduğu kopyaya
isteklerin yarısı gidiyor.

- **Oturum** 8971 kopyasında açıldı; 8972'ye düşen üç istek 401 aldı. Üye giriş yaptığını biliyor
  ve sistem her ikinci isteğinde onu tanımıyor.
- **Makbuz** 8972 kopyasının yerel dizinine yazıldı; 8971'e düşen üç istek 404 aldı. Makbuz
  kaybolmadı, yanlış düğümde duruyor.
- **Önbellek** iki kopyada da ısıtıldı, güncelleme yalnız birinde geçersizleştirildi. Paylaşılan
  katalog 2 değerini tutuyor, üç istek 1 değerini alıyor — **kod 200 ile.**
- **Zamanlayıcı** iki kopyada da kuruldu ve gecikme cezası üç yerine altı kez işlendi. Bu üç fazla
  işlemin karşılığında **hiçbir istek yoktur**; kimse bir şey sormadı, bir durum kodu üretilmedi.

## Dört Sızıntı, Dört Görünürlük

Dördü aynı ağırlıkta değildir ve sıralamayı belirleyen şey ne sıklıkları ne de sayılarıdır —
**dışarıdan ne olarak göründükleridir.**

| Sızıntı | İstemcinin gördüğü | Kayıtta görünür mü | Veriye dokunur mu |
|---|---|---|---|
| Bellekteki oturum | 401 | evet, hata olarak | hayır |
| Yerel dosya | 404 | evet, hata olarak | hayır |
| Süreç içi önbellek | 200, eski değer | hayır | hayır |
| Zamanlayıcı | hiçbir şey | hayır | evet |

İlk iki satır gürültülüdür: yanlış sonuç bir hata kodu olarak döner, kayda düşer, sayaçta görünür.
Rahatsız edicidir ve tam bu yüzden erken bulunur.

Üçüncü satır sessizdir. İstek başarılıdır, kayıt temizdir, gecikme düşüktür. Ödünç sisteminde bu,
elde olmayan bir kitabın rafta görünmesidir. Üçüncü satırın ölçülebilmesinin tek nedeni istemcinin
doğru cevabı önceden bilmesidir; bir kullanıcı bilmez.

Dördüncü satır hem sessizdir hem de tek başına **paylaşılan veriyi bozar**. Üye yediye gecikme
cezası iki kez işlendi. İstek yok, hata yok, kayıtta bir sapma yok — yalnız defterde iki katı
satır. Kopya sayısı üçe çıkarılsa ceza üç kat işlenirdi; sızıntının şiddeti doğrudan kopya sayısına
bağlıdır ve kopya sayısı ölçekleme kararının kendisidir.

## Ayarın Yeri

| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Kopya sayısı | süreç yöneticisi | 1 satır | uygulamada 12 çağrı yeri ayrışır, 21 isteğin 12'si yanlışlanır |
| Oturumun tutulduğu yer | uygulama kodu | 3 | her ikinci istekte oturum kaybı |
| Makbuz dizini | uygulama kodu | 3 | yazan kopya dışında makbuz yok |
| Önbellek geçersizleştirme | uygulama kodu | 4 | eski değer 200 ile döner |
| Zamanlayıcının kurulduğu yer | uygulama kodu, modül gövdesi | 2 | iş kopya sayısı kadar tekrar edilir |

Tablodaki asimetri bu dersin kuralıdır. Değişen ayar **bir** satırdır ve süreç yöneticisindedir;
sonucunu belirleyen satırlar **on iki** tanedir ve uygulama kodundadır. Kopya sayısını artıran
değişiklik uygulama kodunda hiçbir satıra dokunmaz, bu yüzden gözden geçirmede uygulama kodu hiç
açılmaz.

## Özet

- Aynı uygulama tek kopyayla yirmi bir isteğin hepsinde doğru sonuç verdi, iki kopyayla on ikisinde
  yanlış sonuç verdi; kodda tek satır değişmedi.
- Dört sızıntı türünün her biri tam üç isteği yanlışladı: oturum 401, makbuz 404, önbellek eski
  değeri 200 ile, zamanlayıcı üye yedinin gecikme cezasını üç yerine altı kez işleyerek.
- Kaynak taraması elli beş satırda on iki düğüme bağlı çağrı yeri buldu: oturum 3, yerel dosya 3,
  önbellek 4, zamanlayıcı 2; bu dağılım düzeltmenin dokunacağı satır sayısıdır.
- Statik on iki ile çalışma anındaki on iki farklı ölçülerdir; ilki kopya sayısından bağımsızdır,
  ikincisi kopya sayısıyla büyür.
- En sessiz iki sızıntı önbellek ile zamanlayıcıdır: biri yanlış değeri 200 ile döndürür, öteki
  hiçbir istek üretmeden paylaşılan veriyi bozar ve şiddeti kopya sayısıyla çarpılır.

## Sonraki Adım

Dört sızıntının üçü için yön bellidir: durum sürecin dışına, kopyaların paylaştığı bir yere taşınır.
Oturum için bu, akla gelen tek çözüm değildir ve çoğu zaman ilk denenen de o olmaz. Daha ucuz
görünen bir yol vardır: durumu taşımak yerine **isteği** taşımak, yani aynı üyenin bütün isteklerini
oturumunun açıldığı kopyaya göndermek. Bu, ölçümdeki üç yanlış isteği sıfıra indirir ve uygulama
kodunda hiçbir satıra dokunmaz. Bedelini başka bir yerde ödetir. Sonraki ders bu yolu adlandırır ve
faturasını çıkarır: dağılımın ne kadar bozulduğunu, bir kopya düştüğünde kaç oturumun kaybolduğunu
ve oturumu paylaşılan bir depoya taşımanın aynı ölçümde ne değiştirdiğini sayar.
