---
title: 'Zehirli Mesaj ve Yeniden Deneme'
source: 'https://academia.sh/tr/kurslar/eszamansiz-isleme/zehirli-mesaj-ve-yeniden-deneme'
course: 'Önbellekleme, Kuyruklar ve Eşzamansız İşleme'
language: tr
updated: '2026-08-17T18:06:47+00:00'
license: 'CC BY-SA 4.0'
---

# Zehirli Mesaj ve Yeniden Deneme

Tüketiciyi düşüren iletinin yalıtılması ve bekleme sürelerinin hesaplanması: deneme sayacının teslim anında artırılmasının zorunluluğu, anında–sabit–üstel çizelgelerin toplam bekleme ve deneme bütçesi karşılaştırması, sarsıntının yeniden deneme dalgasının tepesini düşürmesi ve görünmezlik süresiyle geri çekilmenin uyumu.

Önceki ders eşiğin kaç kez deneneceğini söylediğini, ne zaman deneneceğini söylemediğini
belirtti. Bu dersin iki konusu o boşluktan çıkıyor. Birincisi, eşiğin işlemediği bir hata
türü: iletiyi işleyen tüketiciyi ayakta bırakmayan ileti. İkincisi, denemeler arasındaki
beklemenin nasıl hesaplanacağı.

İkisi de aynı iş üzerinde ölçülecek: giden kutusundan alınan gecikme bildirimleri.

## Zehirli Mesaj

Önceki dersteki bozuk iletiler nazik davranıyordu: bir hata fırlatıyor, tüketici hatayı
yakalıyor, deneme sayacını artırıyor ve sonraki iletiye geçiyordu. **Zehirli mesaj** (poison
message) bunu yapmaz. İşlenmesi tüketici sürecini sonlandırır — belleği tüketen bir gövde,
sonsuz döngüye giren bir çözümleme, yakalanamayan bir başarısızlık. Süreç öldüğü için hata
işleme kodu hiç çalışmaz.

Sonucu ölçülebilir bir kısır döngüdür. Süreç yeniden başlar, kuyruktan aynı iletiyi alır,
yine ölür. Deneme sayacı hata yakalandığında artırılıyorsa hiç artmaz; eşik hiçbir zaman
dolmaz; ölü mektup kuyruğu hiçbir zaman devreye girmez.

Aşağıdaki koşum beş iletilik bir kuyrukta üçüncü iletiyi öldürücü yapar ve sayacın iki farklı
konumunu karşılaştırır: hata yakalandığında artan sayaç ile teslim anında, iş başlamadan
artan sayaç.

```js
// zehirli-sayac.mjs — sureci olduren ileti: sayac hangi anda artarsa yalitim calisir
const OLDURUCU = new Set([3]);            // bu ileti tuketici surecini sonlandiriyor
const ESIK = 3;

// Tek bir surec omru: oldurucu iletide donus yapar, arkasindaki kod hic calismaz.
function surecOmru(kuyruk, sayacTeslimde, olu) {
  let teslim = 0;
  for (const i of kuyruk.filter((m) => m.durum === "bekliyor")) {
    teslim += 1;
    if (sayacTeslimde) {                  // sayac isten once, teslim aninda artar
      i.deneme += 1;
      if (i.deneme >= ESIK) { i.durum = "olu"; olu.push(i.id); continue; }
    }
    if (OLDURUCU.has(i.id)) return { teslim, oldu: true };
    i.durum = "islendi";                  // sayac hata yakalaninca artacakti; surec olunce artmaz
  }
  return { teslim, oldu: false };
}

function kosum(sayacTeslimde, enCokBaslatma = 12) {
  const kuyruk = [1, 2, 3, 4, 5].map((id) => ({ id, deneme: 0, durum: "bekliyor" }));
  const olu = [];
  let teslim = 0, olum = 0, baslatma = 0;
  for (let b = 1; b <= enCokBaslatma; b++) {
    baslatma = b;
    const r = surecOmru(kuyruk, sayacTeslimde, olu);
    teslim += r.teslim;
    if (r.oldu) olum += 1;
    if (kuyruk.every((m) => m.durum !== "bekliyor")) break;
  }
  return { baslatma, teslim, olum, olu: olu.length,
           islenen: kuyruk.filter((m) => m.durum === "islendi").length,
           kalan: kuyruk.filter((m) => m.durum === "bekliyor").length };
}

console.log("sayac konumu".padEnd(22) + "baslatma  teslim  surec olumu  islenen  olu mektup  kalan");
for (const [ad, teslimde] of [["hata yakalaninca", false], ["teslim aninda", true]]) {
  const s = kosum(teslimde);
  console.log(ad.padEnd(22) +
    `${String(s.baslatma).padStart(8)}${String(s.teslim).padStart(8)}${String(s.olum).padStart(13)}` +
    `${String(s.islenen).padStart(9)}${String(s.olu).padStart(12)}${String(s.kalan).padStart(7)}`);
}
```

```sh
node zehirli-sayac.mjs
```

```
sayac konumu          baslatma  teslim  surec olumu  islenen  olu mektup  kalan
hata yakalaninca            12      14           12        2           0      3
teslim aninda                3       7            2        4           1      0
```

İlk satırda on iki başlatmanın on ikisi ölümle bitti, yalnız iki ileti işlendi ve üç ileti
kuyrukta kaldı. On iki sayısı bir sonuç değil, koşumun üst sınırıdır: döngü orada durdurulduğu
için durdu, kendiliğinden değil. Ölü mektup sütunu sıfır — eşik vardı, ama sayaç hiç artmadığı
için hiçbir zaman dolmadı.

İkinci satırda üç başlatma yetti. Zehirli ileti iki kez teslim edildi, üçüncü teslimde sayaç
eşiğe ulaştığı için iş hiç başlatılmadan yalıtıldı; arkasındaki iki ileti de işlendi. Kuyruk
boşaldı.

Kural tek cümledir: **deneme sayacı, işin sonucundan bağımsız olarak teslim anında
artırılmalıdır.** Sayacı hata yoluna koymak, yalnız hata yolunun çalıştığı durumlar için
koruma sağlar. Önceki derste kurulan `deneme` alanının işten önce yazılmasının nedeni budur
ve aynı gerekçe kuyruğun teslim sayacı için de geçerlidir: 02. derste ölçtüğümüz teslim
sayısı `al` çağrısında artıyordu, işin bitişinde değil.

Yalıtım zehirli mesajın maliyetini sınırlar ama sıfırlamaz. İki süreç ölümü olmuştur ve o
süreçlerin o sırada elinde tuttuğu başka işler yarım kalmıştır. Bu yüzden zehirli mesaj
riski taşıyan işlerde ikinci bir savunma kullanılır: gövdenin işlenmeden önce doğrulanması.
Doğrulama Katmanları dersinde konan ölçüt burada da geçerlidir — biçimi bozuk gövde işe hiç
girmez, ölü mektup kuyruğuna doğrudan alınır.

## Bekleme Süresinin Hesabı

Şimdiye kadarki koşumlarda denemeler arka arkaya yapıldı, aralarında hiç bekleme yoktu.
Geçici hataların çoğu bir kaynağın aşırı yüklenmesinden doğar ve beklemeden yapılan yeniden
deneme o kaynağı daha da yükler. Bekleme süresini veren kurala **geri çekilme çizelgesi**
denir.

```js
// geri-cekilme.mjs — bekleme cizelgeleri ve bir deneme butcesine sigan deneme sayisi
const TABAN = 500, TAVAN = 30_000;                 // ms
const CIZELGE = {
  "aninda": () => 0,
  "sabit 2 sn": () => 2000,
  "ustel": (n) => TABAN * 2 ** (n - 1),
  "ustel + tavan": (n) => Math.min(TAVAN, TABAN * 2 ** (n - 1)),
};
const ADLAR = Object.keys(CIZELGE);

console.log("deneme" + ADLAR.map((a) => a.padStart(15)).join(""));
for (let n = 1; n <= 8; n++)
  console.log(String(n).padStart(6) + ADLAR.map((a) => String(CIZELGE[a](n)).padStart(15)).join(""));

const toplam = (ad, adet) => Array.from({ length: adet }, (_, i) => CIZELGE[ad](i + 1))
  .reduce((t, x) => t + x, 0);
console.log("toplam" + ADLAR.map((a) => String(toplam(a, 8)).padStart(15)).join(""));

const BUTCE = 5 * 60 * 1000;                       // 5 dakikalik zaman butcesi
console.log("\ncizelge".padEnd(16) + "5 dk icinde sigan deneme  butce dolunca gecen sure");
for (const ad of ADLAR) {
  let gecen = 0, deneme = 0;
  while (gecen + CIZELGE[ad](deneme + 1) <= BUTCE && deneme < 1000) {
    deneme += 1; gecen += CIZELGE[ad](deneme);
  }
  console.log(ad.padEnd(16) + String(deneme === 1000 ? "sinirsiz" : deneme).padStart(24) +
              String(gecen).padStart(25) + " ms");
}
```

```sh
node geri-cekilme.mjs
```

```
deneme         aninda     sabit 2 sn          ustel  ustel + tavan
     1              0           2000            500            500
     2              0           2000           1000           1000
     3              0           2000           2000           2000
     4              0           2000           4000           4000
     5              0           2000           8000           8000
     6              0           2000          16000          16000
     7              0           2000          32000          30000
     8              0           2000          64000          30000
toplam              0          16000         127500          91500

cizelge        5 dk icinde sigan deneme  butce dolunca gecen sure
aninda                          sinirsiz                        0 ms
sabit 2 sn                           150                   300000 ms
ustel                                  9                   255500 ms
ustel + tavan                         14                   271500 ms
```

Dört çizelge dört ayrı davranış üretiyor. Anında yeniden deneme hiç beklemez: zaman bütçesi
hiçbir zaman dolmaz, dolayısıyla denemeye sınırı ancak eşik koyabilir. Kaynağın yükünü
azaltmak bir yana, hata anında yükü yoğunlaştırır.

Sabit aralık öngörülebilirdir ama iki uçta da yanlıştır: bir saniyede toparlanacak bir hata
için iki saniye beklemek gereksizdir, bir dakika sürecek bir kesintide iki saniyede bir
denemek kaynağı yüz elli kez boşuna yorar.

**Üstel geri çekilme** (exponential backoff) her denemede bekleme süresini katlayarak ikisini
de çözer: hata kısa sürerse erken denemeler onu yakalar, uzun sürerse denemeler seyrelir.
Ödediği bedel yedinci satırda görünüyor — bekleme 32 saniyeye çıkmış. Bu yüzden bir **tavan**
konur; tavanlı sürümde toplam bekleme 127,5 saniyeden 91,5 saniyeye iner ve beş dakikalık
bütçeye dokuz yerine on dört deneme sığar. Tavan, seyrelmenin sınırsızlaşmasını önler.

Frontend Mimarisi kursunun Yeniden Deneme ve Geri Çekilme dersinde aynı çizelgeler istemci
tarafı için kurulmuştu. Tüketici tarafındaki fark, bekleyen tarafın bir kullanıcı değil bir
ileti olmasıdır: bekleme süresince kuyruk derinliği artmaya devam eder ve gecikme yalnız o
iletiye değil, arkasındaki bütün işlere yansır.

## Sarsıntı

Geri çekilme çizelgesi tek bir tüketici için doğru olsa da, çok sayıda tüketici aynı anda
başarısız olduğunda yeni bir sorun doğurur. Hepsi aynı çizelgeyi kullandığı için hepsi aynı
anda yeniden dener; kaynak toparlanmaya çalışırken art arda gelen dalgalarla karşılaşır.

Çözüm bekleme süresine rastgelelik katmaktır. Buna **sarsıntı** (jitter) denir. **Tam
sarsıntı** (full jitter) beklemeyi sıfır ile hesaplanan süre arasında rastgele seçer;
**ilişkisiz sarsıntı** (decorrelated jitter) her adımda bir önceki beklemeye bağlı, daha geniş
bir aralıktan seçer.

Aşağıdaki koşum aynı anda başarısız olan iki yüz tüketiciyi dört deneme boyunca izler ve
yeniden deneme anlarını yüz milisaniyelik kovalara böler. Üreteç sabit tohumludur; sayılar
her koşumda aynıdır.

```js
// sarsinti.mjs — ayni anda basarisiz olan 200 tuketicinin yeniden deneme dalgasi
const uretec = (tohum) => { let x = tohum; return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648; };
const TUKETICI = 200, DENEME = 4, KOVA = 100;      // kova genisligi 100 ms
const TABAN = 500, TAVAN = 30_000;

function dalga(kip, tohum = 20250720) {
  const rast = uretec(tohum);
  const anlar = [];
  for (let t = 0; t < TUKETICI; t++) {
    let an = 0, onceki = TABAN;
    for (let n = 1; n <= DENEME; n++) {
      const taban = Math.min(TAVAN, TABAN * 2 ** (n - 1));
      if (kip === "sarsintisiz") an += taban;
      else if (kip === "tam sarsinti") an += Math.round(rast() * taban);          // [0, taban]
      else {                                                                       // iliskisiz
        onceki = Math.min(TAVAN, Math.round(TABAN + rast() * (onceki * 3 - TABAN)));
        an += onceki;
      }
      anlar.push(an);
    }
  }
  const kova = new Map();
  for (const a of anlar) { const k = Math.floor(a / KOVA); kova.set(k, (kova.get(k) ?? 0) + 1); }
  return { istek: anlar.length, tepe: Math.max(...kova.values()), doluKova: kova.size,
           sonKova: Math.max(...kova.keys()) };
}

console.log("cizelge".padEnd(20) + "toplam istek  en yogun 100 ms  dolu kova  son kova");
for (const kip of ["sarsintisiz", "tam sarsinti", "iliskisiz sarsinti"]) {
  const s = dalga(kip);
  console.log(kip.padEnd(20) + String(s.istek).padStart(12) + String(s.tepe).padStart(17) +
              String(s.doluKova).padStart(11) + String(s.sonKova).padStart(10));
}
```

```sh
node sarsinti.mjs
```

```
cizelge             toplam istek  en yogun 100 ms  dolu kova  son kova
sarsintisiz                  800              200          4        75
tam sarsinti                 800               69         65        67
iliskisiz sarsinti           800               28        163       389
```

Üç çizelgede de toplam istek sayısı aynı: sekiz yüz. Değişen tek şey bu isteklerin zamana
nasıl yayıldığıdır. Sarsıntısız çizelgede sekiz yüz istek yalnız dört kovaya sığıyor ve her
kovada iki yüz istek var — iki yüz tüketicinin tamamı aynı yüz milisaniyede kapıyı çalıyor.
Tam sarsıntı tepeyi 69'a indiriyor ve isteği altmış beş kovaya yayıyor. İlişkisiz sarsıntı
tepeyi 28'e indiriyor, ama son kova 389 — yani yayılım otuz dokuz saniyeye uzuyor.

Ödünleşme buradadır. Sarsıntı, kaynağın gördüğü ani yükü düşürür; karşılığında bazı
iletilerin yeniden denenmesi geç kalır. Yükün tepesi kaynağı yeniden düşürecek büyüklükteyse
sarsıntı zorunludur; işin gecikmeye duyarlılığı yüksekse yayılım dar tutulur.

## Deneme Bütçesi ve Görünmezlik Süresi

Yeniden deneme kuralı iki sınırla birlikte tanımlanır. **Deneme bütçesi** (retry budget)
sayıya bakar: eşik. Zaman bütçesi süreye bakar: iletinin ilk teslimden itibaren ne kadar süre
sonra ölü mektup kuyruğuna alınacağı. İkisi birlikte gerekir, çünkü tek başına eşik geri
çekilmeyle birleştiğinde süreyi öngörülemez kılar — tavanlı üstel çizelgede on dört deneme
dört buçuk dakikadan uzun sürüyordu, tavansızda dokuz deneme aynı süreyi buluyordu.

İkinci uyum kuyruğun kendisiyledir. 02. derste kurulan görünmezlik süresi, alınan iletinin ne
kadar süre başkasına verilmeyeceğini söylüyordu. Tüketici geri çekilme süresi boyunca iletiyi
elinde tutup bekliyorsa, bu bekleme görünmezlik süresinden uzun olduğu anda ileti başka bir
tüketiciye görünür olur ve aynı iş koşut yürür. Doğru yerleşim, tüketicinin beklerken iletiyi
elinde tutmamasıdır: ileti kuyruğa geri bırakılır ve yeniden görünür olacağı an geri çekilme
süresi kadar ileriye alınır. Böylece bekleme kuyruğun kendi zaman düzleminde tutulur ve
tüketici süreci bu sırada başka iş yapar.

Üçüncü bir sınır hata oranıyla ilgilidir. Bir kaynak toptan erişilemez hâle geldiğinde her
ileti aynı hatayı alır ve yeniden denemeler tek başına bir yük kaynağına dönüşür. Bu durumda
tek tek iletilerin çizelgesi değil, tüketicinin tamamının durması gerekir; yukarıda anılan
Yeniden Deneme ve Geri Çekilme dersinde tanıtılan **devre kesici** (circuit breaker) kalıbının
çözdüğü sorun budur.

## Özet

- Zehirli mesaj yalnız başarısız olan ileti değildir; tüketici sürecini sonlandırdığı için
  hata işleme kodunu hiç çalıştırmaz.
- Sayaç hata yakalandığında artırıldığında on iki başlatmanın on ikisi ölümle bitti, iki ileti
  işlendi ve üç ileti kuyrukta kaldı; teslim anında artırıldığında üç başlatma yetti, ileti
  üçüncü teslimde yalıtıldı ve kuyruk boşaldı.
- Tavanlı üstel geri çekilme sekiz denemede toplam beklemeyi 127,5 saniyeden 91,5 saniyeye
  indirdi ve beş dakikalık bütçeye dokuz yerine on dört deneme sığdırdı.
- İki yüz tüketicinin sekiz yüz yeniden denemesi sarsıntısız çizelgede dört kovaya yığıldı ve
  tepe 200 oldu; tam sarsıntı tepeyi 69'a, ilişkisiz sarsıntı 28'e indirdi ve yayılımı
  otuz dokuz saniyeye uzattı.
- Deneme bütçesi ile zaman bütçesi birlikte tanımlanır; geri çekilme süresi görünmezlik
  süresini aşmamalı, bekleme tüketicide değil kuyruğun zaman düzleminde tutulmalıdır.

## Sonraki Adım

Mesaj artık dayanıklıdır. Kalıcı bir yere yazılıyor, birden çok ilgiliye ulaşıyor, sırası
gerektiği yerde korunuyor, tekrarı etkisiz kılınıyor, işlenemeyeni yalıtılıyor ve yeniden
denemesi ölçülü bir çizelgeye bağlanıyor. Bütün bu ölçümlerde bir taraf ise hep verili
kabul edildi: iletiyi işleyen tüketici. Kaç tane işçi süreci vardır, biri saniyede kaç ileti
bitirir, sayıları artırıldığında ne kazanılır, üretim hızı kapasitelerini aştığında ne olur?
Sonraki konu tüketiciyi bir ölçekleme birimi olarak ele alır ve İşçi Süreçleri dersiyle
başlar.
