---
title: 'Giden Kutusu Kalıbı'
source: 'https://academia.sh/tr/kurslar/servis-mimarileri/giden-kutusu-kalibi'
course: 'Servis Mimarileri'
language: tr
updated: '2026-08-17T18:06:53+00:00'
license: 'CC BY-SA 4.0'
---

# Giden Kutusu Kalıbı

Veritabanı yazması ile ileti yayınının aynı işleme alınmasının ölçülen bedeli: yayıncı çöktüğünde giden kutusuz kaybolan ileti, giden kutusuyla ikinci kez teslim edilen ileti, yoklama aralığının belirlediği aktarıcı gecikmesi ve aktarıcı durduğunda tabloda biriken satır.

Önceki derste her adım bir sonrakini doğrudan çağırdı: katalog kendi deposuna yazdı ve hemen
ardından ödünç bilgisini üyeliğe bildirdi. Kod tek satırdı, ama iki iş vardı — biri yerel
işlemin içinde, öteki dışında. Aralarına düşen süreç, işi yapmış ama kimseye söylememiş olur.

M16/K04'ün `dağıtık işlem sorunu` dersi giden kutusu düzenini kurmuş ve tek bir iletinin iki
kez teslim edildiğini göstermişti. O ölçüm burada tekrarlanmıyor. Buradaki ölçü **bir yük
üzerinde**dir: yayıncı çöktüğünde kaç ileti kayboluyor, giden kutusuyla aynı çökme dizisinde
kaç ileti ikinci kez teslim ediliyor, aktarıcının gecikmesi ne oluyor ve tabloda kaç satır
birikiyor.

## Düzenek

**DT5.** Yayıncının çökmesi süreç gerçekten öldürülerek değil, kesinleştirme ile yayın
arasındaki noktada tohumlu bir üreteçten çekilen kararla modellenir. Tohum `20260731`, çökme
oranı `0,10`. Karar dizisi **teslim denemesi numarasına** göre okunduğu için, `k`'inci teslim
denemesi iki gerçekleştirmede de aynı kaderi paylaşır.

**DT6.** Aktarıcı tetiklenmez, kutuyu yoklar. Ölçülen gecikme bu tercihin doğrudan sonucudur;
tetiklemeli bir aktarıcıda gecikmenin alt sınırı yoklama aralığına bağlı olmaz.

```sh
# kur.sh — katalog kendi durumunu ve giden kutusunu tutar, uyelik teslimleri kaydeder
rm -f katalog.db* uyelik.db*
sqlite3 katalog.db "PRAGMA journal_mode=WAL;
  CREATE TABLE hareket (id INTEGER PRIMARY KEY, kitap_id INTEGER, uye_id INTEGER);
  CREATE TABLE giden_kutusu (ileti_id INTEGER PRIMARY KEY, kitap_id INTEGER, uye_id INTEGER,
                             yazilma INTEGER NOT NULL, durum TEXT NOT NULL DEFAULT 'bekliyor');" >/dev/null
sqlite3 uyelik.db "PRAGMA journal_mode=WAL;
  CREATE TABLE teslim (sira INTEGER PRIMARY KEY, ileti_id INTEGER, varis INTEGER);" >/dev/null
```

```js
// uretec.mjs — kendi yazdigimiz uretec; tohum gorunur, dizi her kosumda ayni
export function uretec(tohum) {
  let d = tohum >>> 0;
  return () => { d = (Math.imul(d, 1103515245) + 12345) >>> 0; return (d >>> 8) / 16777216; };
}
```

Üyelik tarafındaki `teslim` tablosu her teslimi ayrı satır olarak biriktirir; yinelenen bir
teslim aynı `ileti_id` ile ikinci kez görünür. Kaybolan ileti ile yinelenen ileti bu tek
tablodan sayılır.

## Aynı Yük, İki Gerçekleştirme

İki yüz ödünç isteği. Birinci gerçekleştirme durumu kesinleştirir, sonra yayımlar. İkincisi
durumu ve iletiyi tek işlemde yazar, yayını ayrı bir geçişe bırakır.

```js
// yayinci.mjs — ayni yuk, iki gerceklestirme: --dogrudan ve --giden-kutusu
import { DatabaseSync } from "node:sqlite";
import { uretec } from "./uretec.mjs";

const kip = process.argv.includes("--dogrudan") ? "dogrudan" : "giden-kutusu";
const N = 200, TOHUM = 20260731, ORAN = 0.10;
const zar = uretec(TOHUM);
// deneme numarasina gore sabit cokme dizisi: k'inci teslim denemesi iki kipte de ayni kaderi paylasir
const coker = Array.from({ length: N * 4 }, () => zar() < ORAN);
let deneme = 0, cokme = 0;

const katalog = new DatabaseSync("katalog.db");
const uyelik = new DatabaseSync("uyelik.db");
const teslimEt = (iletiId) =>
  uyelik.prepare("INSERT INTO teslim (ileti_id, varis) VALUES (?,?)").run(iletiId, Date.now());

if (kip === "dogrudan") {
  for (let n = 1; n <= N; n++) {
    katalog.exec("BEGIN IMMEDIATE");
    const r = katalog.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
    katalog.exec("COMMIT");
    if (coker[deneme++]) { cokme += 1; continue; }   // surec burada duser: ileti hic uretilmedi
    teslimEt(Number(r.lastInsertRowid));
  }
} else {
  for (let n = 1; n <= N; n++) {
    katalog.exec("BEGIN IMMEDIATE");                 // durum ve ileti tek islemde
    katalog.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
    katalog.prepare("INSERT INTO giden_kutusu (kitap_id, uye_id, yazilma) VALUES (?,4,?)")
      .run(n, Date.now());
    katalog.exec("COMMIT");
  }
  for (let gecis = 0; gecis < 500; gecis++) {        // aktarici gecisleri
    const bekleyen = katalog.prepare(
      "SELECT ileti_id FROM giden_kutusu WHERE durum='bekliyor' ORDER BY ileti_id").all();
    if (bekleyen.length === 0) break;
    for (const i of bekleyen) {
      teslimEt(i.ileti_id);
      if (coker[deneme++]) { cokme += 1; break; }    // isaretleme oncesi duser: yeniden teslim edilecek
      katalog.prepare("UPDATE giden_kutusu SET durum='gonderildi' WHERE ileti_id=?").run(i.ileti_id);
    }
  }
}

const satir = uyelik.prepare("SELECT COUNT(*) c FROM teslim").get().c;
const benzersiz = uyelik.prepare("SELECT COUNT(DISTINCT ileti_id) c FROM teslim").get().c;
const kutu = katalog.prepare("SELECT COUNT(*) c FROM giden_kutusu").get().c;
console.log(`${kip.padEnd(13)}| uretilen=${N} cokme=${cokme} | teslim satiri=${satir}` +
  ` benzersiz=${benzersiz} kaybolan=${N - benzersiz} yinelenen=${satir - benzersiz}` +
  ` | kutuda satir=${kutu}`);
```

```sh
sh kur.sh
node yayinci.mjs --dogrudan
sh kur.sh
node yayinci.mjs --giden-kutusu
```

```
dogrudan     | uretilen=200 cokme=22 | teslim satiri=178 benzersiz=178 kaybolan=22 yinelenen=0 | kutuda satir=0
giden-kutusu | uretilen=200 cokme=24 | teslim satiri=224 benzersiz=200 kaybolan=0 yinelenen=24 | kutuda satir=200
```

Doğrudan yayında 200 durum değişikliğinin 22'si için hiçbir ileti üretilmedi. Bu 22 kitap
katalogda ödünç göründü, üyelik tarafında hiç görünmedi ve **bu durumdan hiçbir kayıt
kalmadı**: kutuda 0 satır var, yani sistemde "gönderilmesi gereken ama gönderilmemiş" diye
bakılabilecek bir yer yok. Kayıp sessizdir.

Giden kutusunda kaybolan sıfır. Çökme sayısı 22 değil 24; çünkü bu kip daha çok teslim
denemesi yaptı (224'e karşı 200) ve ilk 200 denemenin kaderi doğrudan kiple aynı olsa da
sonraki 24 denemede 2 çökme daha çıktı. Her çökme tam olarak bir ikinci teslim doğurdu:
`224 − 200 = 24` yinelenen. Kayıp, yinelenmeye dönüştü.

Üçüncü sayı gözden kaçmamalı: kutuda 200 satır var. Bunlar teslim edilmiş, işi bitmiş
satırlardır ve tablo kendiliğinden küçülmez.

## Aktarıcı Gecikmesi

Yukarıdaki geçişler ardışıktı. Gerçekte aktarıcı ayrı koşar ve kutuyu belirli aralıklarla
yoklar; aralık, iletinin yazılmasıyla teslimi arasındaki süreyi doğrudan belirler.

```js
// yazici.mjs — durum degisikligini ve iletiyi tek islemde, duzenli araliklarla kutuya yazar
import { DatabaseSync } from "node:sqlite";
const [adet, aralik] = process.argv.slice(2).map(Number);
const db = new DatabaseSync("katalog.db");
db.exec("PRAGMA busy_timeout = 500");
for (let n = 1; n <= adet; n++) {
  db.exec("BEGIN IMMEDIATE");
  db.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
  db.prepare("INSERT INTO giden_kutusu (kitap_id, uye_id, yazilma) VALUES (?,4,?)").run(n, Date.now());
  db.exec("COMMIT");
  await new Promise((r) => setTimeout(r, aralik));
}
console.log(`yazici: ${adet} ileti, ${aralik} ms arayla yazildi`);
```

```js
// aktarici.mjs — kutuyu belirli araliklarla yoklar; gecikmeyi ve bos yoklamayi olcer
import { DatabaseSync } from "node:sqlite";
const [aralik, sure] = process.argv.slice(2).map(Number);
const katalog = new DatabaseSync("katalog.db"); katalog.exec("PRAGMA busy_timeout = 500");
const uyelik = new DatabaseSync("uyelik.db");
const gecikmeler = [];
let yoklama = 0, bosYoklama = 0, enCokBekleyen = 0;
const bitis = Date.now() + sure;
while (Date.now() < bitis) {
  const bekleyen = katalog.prepare(
    "SELECT ileti_id, yazilma FROM giden_kutusu WHERE durum='bekliyor' ORDER BY ileti_id").all();
  yoklama += 1;
  if (bekleyen.length === 0) bosYoklama += 1;
  enCokBekleyen = Math.max(enCokBekleyen, bekleyen.length);
  for (const i of bekleyen) {
    const simdi = Date.now();
    uyelik.prepare("INSERT INTO teslim (ileti_id, varis) VALUES (?,?)").run(i.ileti_id, simdi);
    katalog.exec("BEGIN IMMEDIATE");
    katalog.prepare("UPDATE giden_kutusu SET durum='gonderildi' WHERE ileti_id=?").run(i.ileti_id);
    katalog.exec("COMMIT");
    gecikmeler.push(simdi - i.yazilma);
  }
  await new Promise((r) => setTimeout(r, aralik));
}
gecikmeler.sort((a, b) => a - b);
const ortanca = gecikmeler[Math.floor(gecikmeler.length / 2)] ?? 0;
console.log(`aktarici(aralik=${aralik} ms): yoklama=${yoklama} bos yoklama=${bosYoklama}` +
  ` teslim=${gecikmeler.length} en cok bekleyen satir=${enCokBekleyen}` +
  ` | gecikme ortanca=${ortanca} ms en buyuk=${gecikmeler.at(-1) ?? 0} ms (bu kosumda)`);
```

```sh
# ayni yazma hizi (60 ileti, 50 ms arayla), iki farkli yoklama araligi
for ARALIK in 20 250; do
  sh kur.sh
  node yazici.mjs 60 50 &
  node aktarici.mjs $ARALIK 4500
  wait
  sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
done
```

```
yazici: 60 ileti, 50 ms arayla yazildi
aktarici(aralik=20 ms): yoklama=204 bos yoklama=144 teslim=60 en cok bekleyen satir=1 | gecikme ortanca=13 ms en buyuk=23 ms (bu kosumda)
gonderildi|60
yazici: 60 ileti, 50 ms arayla yazildi
aktarici(aralik=250 ms): yoklama=18 bos yoklama=4 teslim=60 en cok bekleyen satir=5 | gecikme ortanca=131 ms en buyuk=252 ms (bu kosumda)
gonderildi|60
```

Koşumdan bağımsız olan taraf nettir: iki koşumda da 60 ileti yazıldı ve 60'ı teslim edildi.
Değişen, aynı işin kaç sorguyla ve ne kadar bekleyerek yapıldığıdır. Yirmi milisaniyelik
aralıkta 204 yoklamanın 144'ü boştur (bu koşumda) — teslim başına yaklaşık 3,4 sorgu, üçte
ikisi karşılıksız. İki yüz elli milisaniyelik aralıkta yoklama 18'e iner ve boş yoklama
neredeyse kalmaz, ama ortanca gecikme 13 ms'ten 131 ms'e çıkar ve bir geçişte kutuda en çok 1 yerine 5 satır
birikir. Aralık, boş sorgu ile gecikme arasında doğrudan bir takastır.

## Aktarıcı Durduğunda

Aktarıcı yayıncıdan bağımsız bir süreç olduğu için ayrı düşebilir. Bu, giden kutusunun
kazancının en görünür olduğu ve bedelinin en somutlaştığı durumdur.

```sh
sh kur.sh
node yazici.mjs 60 20
sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
node aktarici.mjs 20 1500
sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
sqlite3 katalog.db "DELETE FROM giden_kutusu WHERE durum='gonderildi';
                    SELECT 'budamadan sonra satir: ' || COUNT(*) FROM giden_kutusu;"
```

```
yazici: 60 ileti, 20 ms arayla yazildi
bekliyor|60
aktarici(aralik=20 ms): yoklama=68 bos yoklama=67 teslim=60 en cok bekleyen satir=60 | gecikme ortanca=743 ms en buyuk=1382 ms (bu kosumda)
gonderildi|60
budamadan sonra satir: 0
```

Aktarıcı yokken 60 satır kutuda birikti ve hiçbiri kaybolmadı. Aktarıcı geri geldiğinde
hepsini tek geçişte teslim etti; bedeli, ortanca gecikmenin 13 ms'ten 743 ms'e, en büyük
gecikmenin 1382 ms'e çıkmasıdır (bu koşumda). Aktarıcı ne kadar uzun durursa biriken satır ve yakalama
gecikmesi o kadar büyür — bu, izlenmesi gereken bir niceliktir, kendiliğinden sınırlı değildir.

Son satır tablonun büyümesini gösterir: teslim edilen 60 satır tabloda kalır ve ancak açık bir
budama ile silinir. Giden kutusu, iş kuralıyla ilgisi olmayan bir tabloyu yazma yoluna ekler;
o tablonun temizlenmesi de bir iştir.

## Üç Sütun

**Neyi ucuzlattı:** durum değişikliği ile iletinin birlikte yazılması, "yapıldı ama
duyurulmadı" durumunu ortadan kaldırdı. Aynı çökme dizisinde doğrudan yayın 22 ileti
kaybederken giden kutusu 0 kaybetti; üstelik kayıp sessiz değil, kutuda bekleyen bir satır
olarak görünür.

**Neyi pahalılaştırdı:** her ödünç isteği artık iki tabloya yazıyor; ayrıca yeni bir süreç
(aktarıcı) ve yeni bir ayar (yoklama aralığı) var. Teslim başına 3,4 sorguya kadar çıkan boş
yoklama, gecikmeyi kısaltmanın bedelidir. Kutu kendiliğinden küçülmez; budama ayrı bir iştir.

**Hangi yeni arıza kipi doğdu:** iki tane. Birincisi **ikinci teslim**: aktarıcının teslim ile
işaretleme arasında düşmesi 24 iletinin iki kez ulaşmasına yol açtı. İkincisi **aktarıcı
gerilemesi**: aktarıcı durduğunda sistem hata vermez, yalnız geç kalır; ölçüm yapılmazsa
biriken satır ve büyüyen gecikme fark edilmez.

## Özet

- Aynı çökme dizisi altında doğrudan yayın 200 durum değişikliğinin 22'si için hiç ileti
  üretmedi; giden kutusu hiç ileti kaybetmedi.
- Kaybın yerine yinelenme geçti: giden kutusu 224 teslim yaptı, bunların 24'ü ikinci
  teslimdir; her çökme tam olarak bir yinelenme doğurdu.
- Yoklama aralığı gecikme ile boş sorgu arasında takastır: bu koşumda 20 ms'de ortanca gecikme
  13 ms ve 204 yoklamanın 144'ü boş; 250 ms'de gecikme 131 ms'e çıkar, yoklama 18'e iner.
- Aktarıcı durduğunda 60 satır kutuda birikti, hiçbiri kaybolmadı; yakalama sırasında ortanca
  gecikme 743 ms, en büyük 1382 ms oldu (bu koşumda).
- Teslim edilen satırlar tabloda kalır (200 ve 60 satır); giden kutusu açık bir budama işi
  gerektirir.

## Sonraki Adım

Giden kutusunun bıraktığı 24 ikinci teslim, yayıncı tarafında çözülemez: aktarıcı ile hedef
ayrı depolarda olduğu sürece "gönderdim" bilgisi ile "işledim" bilgisi aynı işleme giremez.
Geriye tek yol kalıyor — bedeli tüketiciye taşımak. Sonraki ders iletileri hem **yinelenmiş**
hem de **sırası bozulmuş** olarak tüketiciye verir, iki tüketici gerçekleştirmesinin ürettiği
yan etkiyi sayar ve sürüm damgasının eski iletiyi nasıl attığını ölçer.
