---
title: 'Teslim Semantiği'
source: 'https://academia.sh/tr/kurslar/eszamansiz-isleme/teslim-semantigi'
course: 'Önbellekleme, Kuyruklar ve Eşzamansız İşleme'
language: tr
updated: '2026-08-17T18:06:47+00:00'
license: 'CC BY-SA 4.0'
---

# Teslim Semantiği

Onay ile etkinin sırasının belirlediği üç kip: aynı iş yükünün en çok bir kez, en az bir kez ve tekilleştirmeli koşumunda kayıp ile yinelenen etki sayısının ölçülmesi, tekillik bekçisinin teslim sayısını değil etki sayısını bire indirmesi ve etki veritabanının dışındayken hiçbir sıranın ikisini birden önleyememesi.

Önceki ders iki taşıma modelini karşılaştırdı ve ikisinde de aynı soruyu yanıtsız bıraktı:
işin yapılması ile "yapıldı" bilgisinin yazılması iki ayrı adımdır. Kuyrukta onay işten önce
mi sonra mı gönderilir, akışta konum işten önce mi sonra mı kaydedilir?

İki adımın sırası bir uygulama ayrıntısı değil; sistemin **teslim semantiğini** (delivery
semantics) belirleyen karardır. Bu ders aynı iş yükünü iki sırayla ve bir üçüncü düzenle
koşturur, kayıp ile yinelenen etki sayısını sayar.

## Üç Adın Anlamı

**En çok bir kez** (at-most-once): her ileti sıfır ya da bir kez işlenir; kayıp olabilir,
yineleme olamaz. Onayın etkiden **önce** yazılması bu kipi üretir — ileti işlenmiş sayıldıktan
sonra iş yapılır, arada bir şey olursa iş hiç yapılmaz.

**En az bir kez** (at-least-once): her ileti bir ya da daha çok kez işlenir; yineleme olabilir,
kayıp olamaz. Onayın etkiden **sonra** yazılması bu kipi üretir — iş yapıldıktan sonra ileti
işlenmiş sayılır, arada bir şey olursa ileti yeniden teslim edilir.

**Tam bir kez** (exactly-once) üçüncü bir sıra değildir. Onay ile etki iki ayrı adım olduğu
sürece aralarında bir aralık kalır ve bu aralıkta süreç ölebilir. Tam bir kez, bir teslim
kipi değil, en az bir kezin üstüne konan tekilleştirmenin **sonucudur**. Ölçüm bunu iki
sayıyla gösterecek.

Ölçüm düzeneği Veri Erişim Katmanı ve İş Mantığı kursundaki giden kutusu tablosunu, bir etki tablosunu ve Etkisiz
İşlemler dersinde tanıtılan `islenen_ileti` bekçisini kurar. Etki, gecikme bildiriminin
gönderilmesidir; her gönderim `bildirim` bağıntısına bir satır yazar.

```sh
# kur.sh — teslim olcumu icin giden kutusu, etki tablosu ve islenen ileti bekcisi
rm -f teslim.db
sqlite3 teslim.db <<'SQL'
CREATE TABLE giden_kutusu (ileti_id INTEGER PRIMARY KEY, tur TEXT NOT NULL,
                           govde TEXT NOT NULL, durum TEXT NOT NULL DEFAULT 'bekliyor');
CREATE TABLE bildirim (bildirim_id INTEGER PRIMARY KEY, ileti_id INTEGER NOT NULL,
                       uye_id INTEGER NOT NULL);
CREATE TABLE islenen_ileti (ileti_id INTEGER PRIMARY KEY, islenme TEXT NOT NULL);
SQL
echo "tablolar: $(sqlite3 teslim.db "SELECT group_concat(name, ' ') FROM sqlite_master WHERE type='table'")"
```

```
tablolar: giden_kutusu bildirim islenen_ileti
```

## Aynı İş Yükü, Üç Kip

On iki gecikme bildirimi işlenecek. Üçünde — 3, 7 ve 11 numaralı iletilerde — ilk denemenin
tam ortasında süreç ölecek. Ölüm bir özel durumla temsil edilmiştir; kesinleşmemiş iş geri
alınır, süreç yeniden başlar ve hâlâ beklemede görünen iletileri yeniden dener.

```js
// kipler.mjs — ayni is yuku uc kipte: en cok bir kez, en az bir kez, tekillestirme
import { DatabaseSync } from "node:sqlite";

const COKEN = new Set([3, 7, 11]);          // bu iletilerde ilk deneme yarida kesilir

function tazele(db) {
  for (const t of ["giden_kutusu", "bildirim", "islenen_ileti"]) db.exec(`DELETE FROM ${t}`);
  for (let n = 1; n <= 12; n++)
    db.prepare("INSERT INTO giden_kutusu (ileti_id, tur, govde) VALUES (?,?,?)")
      .run(n, "gecikme_bildirimi", JSON.stringify({ uyeId: (n % 3) + 1 }));
}

const gonder = (db, i) => db.prepare("INSERT INTO bildirim (ileti_id, uye_id) VALUES (?,?)")
  .run(i.ileti_id, JSON.parse(i.govde).uyeId);
const onayla = (db, i) => db.prepare("UPDATE giden_kutusu SET durum='islendi' WHERE ileti_id=?")
  .run(i.ileti_id);

function isle(db, i, kip, cokecek) {
  if (kip === "en cok bir kez") {
    onayla(db, i);                                   // once onay
    if (cokecek) throw new Error("cokme");
    gonder(db, i);                                   // sonra etki
  } else if (kip === "en az bir kez") {
    gonder(db, i);                                   // once etki
    if (cokecek) throw new Error("cokme");
    onayla(db, i);                                   // sonra onay
  } else {
    db.exec("BEGIN IMMEDIATE");                      // etki ve bekci ayni islemde
    if (db.prepare("SELECT 1 AS v FROM islenen_ileti WHERE ileti_id=?").get(i.ileti_id) === undefined) {
      gonder(db, i);
      db.prepare("INSERT INTO islenen_ileti (ileti_id, islenme) VALUES (?, '2025-07-20')").run(i.ileti_id);
    }
    db.exec("COMMIT");
    if (cokecek) throw new Error("cokme");
    onayla(db, i);
  }
}

function kosum(db, kip) {
  tazele(db);
  let teslim = 0;
  for (const tur of [1, 2]) {                        // 1: cokmeli gecis, 2: yeniden baslatma
    for (const i of db.prepare("SELECT ileti_id, govde FROM giden_kutusu WHERE durum='bekliyor' ORDER BY ileti_id").all()) {
      teslim += 1;
      try {
        isle(db, i, kip, tur === 1 && COKEN.has(i.ileti_id));
      } catch {
        if (db.isTransaction) db.exec("ROLLBACK");   // surec oldu: kesinlesmemis is geri alindi
      }
    }
  }
  const say = (s) => db.prepare(s).get().n;
  return {
    teslim,
    etki: say("SELECT count(*) AS n FROM bildirim"),
    kayip: 12 - say("SELECT count(DISTINCT ileti_id) AS n FROM bildirim"),
    yinelenen: say("SELECT count(*) AS n FROM bildirim") - say("SELECT count(DISTINCT ileti_id) AS n FROM bildirim"),
  };
}

const db = new DatabaseSync("teslim.db");
console.log("kip".padEnd(16) + "teslim  etki  kayip  yinelenen");
for (const kip of ["en cok bir kez", "en az bir kez", "tekillestirme"]) {
  const s = kosum(db, kip);
  console.log(kip.padEnd(16) +
    `${String(s.teslim).padStart(6)}${String(s.etki).padStart(6)}${String(s.kayip).padStart(7)}${String(s.yinelenen).padStart(11)}`);
}
```

```sh
node kipler.mjs
```

```
kip             teslim  etki  kayip  yinelenen
en cok bir kez      12     9      3          0
en az bir kez       15    15      0          3
tekillestirme       15    12      0          0
```

İlk satır en çok bir kez kipinin bedelini gösteriyor. On iki teslim yapıldı, dokuz bildirim
gönderildi; üç üye bildirim almadı ve kimse bunu fark etmedi, çünkü ileti işlenmiş
işaretlenmişti. Kayıp sessizdir: giden kutusunda bekleyen satır kalmadığı için sistem kendini
temiz görür.

İkinci satır ters yönü gösteriyor. Hiçbir ileti kaybolmadı, ama teslim sayısı on ikiden on
beşe çıktı ve on beş bildirim gönderildi — üç üye aynı bildirimi iki kez aldı. Bu kipte
yinelemenin ölçüsü doğrudan kaç kez yeniden teslim yapıldığıdır.

Üçüncü satır asıl noktadır ve dikkatle okunmalıdır. **Teslim sayısı hâlâ on beştir.**
Tekilleştirme teslimi tekilleştirmedi; kuyruk yine üç iletiyi iki kez verdi. Değişen şey
etkidir: on iki bildirim gönderildi, ne eksik ne fazla. Bekçi ikinci teslimde iletinin daha
önce işlendiğini gördü ve etkiyi atladı.

## Tekilleştirmenin Çalışma Koşulu

Üçüncü kipin doğru sonuç vermesi bir yerleşim ayrıntısına bağlıdır: etkinin yazılması ile
bekçi satırının yazılması **aynı işlem sınırının** içindedir. `BEGIN IMMEDIATE` ile `COMMIT`
arasındaki iki yazma ya birlikte kesinleşir ya birlikte geri alınır. Ayrılsalardı iki yeni
kusur doğardı: bekçi önce yazılıp etki yazılmadan süreç ölürse ileti işlenmiş sayılır ve
bildirim hiç gitmez — birinci satırdaki kayıp geri gelir. Etki önce yazılıp bekçi yazılmadan
ölürse ikinci teslimde etki yeniden uygulanır — ikinci satırdaki yineleme geri gelir.

İşlem Sınırları dersinde kurulan kural burada bir kez daha karşımıza çıkıyor: birlikte doğru
olması gereken iki yazma aynı atomik sınırı paylaşmalıdır. Bekçinin işi, "bu iletinin etkisi
uygulandı" olgusunu etkinin kendisiyle **aynı anda** kalıcı kılmaktır.

Bekçinin anahtarı burada iletinin kimliğiydi. İletide böyle bir kimlik yoksa ya da aynı işi
farklı iletiler taşıyabiliyorsa, anahtar iş anlamından türetilir; API Tasarımı kursunun
Etkisizlik Anahtarları dersinde konan ölçüt aynıdır — anahtar, tekrarın hangi ölçüte göre
"aynı iş" sayılacağını tanımlar.

## Etki Veritabanının Dışındaysa

Önceki ölçümde etki bir tablo satırıydı, yani bekçiyle aynı işleme sığıyordu. Gerçek gecikme
bildirimi bir tabloya değil, dışarıdaki bir servise gider ve o çağrı veritabanı işleminin
içine alınamaz. Aşağıdaki koşum bu durumu ölçer: dış etki bir dosyaya yazılır, onay
veritabanında tutulur ve süreç tam ikisinin arasında ölür.

```js
// dis-etki.mjs — etki veritabaninin disindaysa hangi sira neyi kaybeder
import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
import { DatabaseSync } from "node:sqlite";

const GIDEN = "gonderilen.log";        // bildirim servisinin gercekten yolladigi bildirimler
const ALICI = "alici-anahtar.log";     // bildirim servisinin gordugu tekillik anahtarlari

function servisGonder(anahtar, bekciVar) {
  if (bekciVar) {
    const gorulen = new Set(readFileSync(ALICI, "utf8").split("\n").filter(Boolean));
    if (gorulen.has(anahtar)) return false;         // ayni anahtar ikinci kez islenmez
    appendFileSync(ALICI, anahtar + "\n");
  }
  appendFileSync(GIDEN, `bildirim ${anahtar}\n`);
  return true;
}

function kosum(db, sira, bekciVar) {
  writeFileSync(GIDEN, ""); writeFileSync(ALICI, "");
  db.exec("DELETE FROM giden_kutusu");
  db.prepare("INSERT INTO giden_kutusu (ileti_id, tur, govde) VALUES (1,'gecikme_bildirimi','{}')").run();
  let cagri = 0;

  for (const tur of [1, 2]) {          // 1: cokmeli gecis, 2: yeniden baslatma
    for (const i of db.prepare("SELECT ileti_id FROM giden_kutusu WHERE durum='bekliyor'").all()) {
      const anahtar = `ileti=${i.ileti_id}`;
      const onayla = () => db.prepare("UPDATE giden_kutusu SET durum='islendi' WHERE ileti_id=?").run(i.ileti_id);
      try {
        if (sira === "etki once") { cagri += 1; servisGonder(anahtar, bekciVar); }
        else onayla();
        if (tur === 1) throw new Error("cokme");    // iki adimin arasinda surec oldu
        if (sira === "etki once") onayla();
        else { cagri += 1; servisGonder(anahtar, bekciVar); }
      } catch { /* surec oldu: sonraki turda yeniden denenir */ }
    }
  }
  const etki = readFileSync(GIDEN, "utf8").split("\n").filter(Boolean).length;
  return { cagri, etki, kayip: etki === 0 ? 1 : 0, yinelenen: Math.max(0, etki - 1) };
}

const db = new DatabaseSync("teslim.db");
console.log("sira".padEnd(32) + "cagri  dis etki  kayip  yinelenen");
for (const [ad, sira, bekci] of [["etki once, onay sonra", "etki once", false],
                                 ["onay once, etki sonra", "onay once", false],
                                 ["etki once + alicida tekillik", "etki once", true]]) {
  const s = kosum(db, sira, bekci);
  console.log(ad.padEnd(32) +
    `${String(s.cagri).padStart(5)}${String(s.etki).padStart(10)}${String(s.kayip).padStart(7)}${String(s.yinelenen).padStart(11)}`);
}
```

```sh
node dis-etki.mjs
```

```
sira                            cagri  dis etki  kayip  yinelenen
etki once, onay sonra               2         2      0          1
onay once, etki sonra               0         0      1          0
etki once + alicida tekillik        2         1      0          0
```

İlk iki satır kapalı bir ikilemdir. Gönderim önce yapılırsa bildirim iki kez gider; onay önce
yazılırsa bildirim hiç gitmez. Arada bir üçüncü sıra yoktur, çünkü iki farklı sistemin —
veritabanı ile bildirim servisi — ortak bir işlem sınırı yoktur. Dağıtık İşlem Sorunu dersinde
kurulan güçlük tam olarak budur.

Üçüncü satır çözümün nerede olduğunu gösteriyor: **çağrı iki kez yapıldı, etki bir kez oldu.**
Fark, bekçinin gönderen tarafta değil, alan tarafta durmasıdır. Bildirim servisi tekillik
anahtarını görüp ikinci çağrıyı yok saydığında, gönderen tarafın en az bir kez kipinde
kalması bir kusur olmaktan çıkar.

Buradan dersin sonucu çıkar: uçtan uca **tam bir kez teslim** kurulamaz; kurulabilen şey **tam
bir kez etkidir** ve bunun koşulu, tekrarın etkisiz olmasını sağlayan bir bekçinin etkiyle
aynı sınırda durmasıdır. Etki dışarıdaysa o sınır alıcının içindedir.

## Hangi Kip Nerede

En çok bir kez kipi bir kusur değil, bilinçli bir seçimdir; koşulu, kaybın maliyetinin
işlemenin maliyetinden küçük olmasıdır. Saniyede binlerce satır üreten bir kullanım ölçütü
akışında tek satırın kaybı sonucu değiştirmez ve her satır için onay yazmak taşıma
maliyetinin katlanmasına yol açar.

Ödünç sisteminde ölçüt tersine döner. Gecikme bildiriminin gitmemesi üyenin ceza ödemesine
yol açar, aylık rapordan düşen satır sayıyı yanlış gösterir, şube stok özetinin kaçırdığı
iade kitabı raftaymış gibi gösterir. Üçü de en az bir kez ister. Yinelemenin maliyeti ise
işten işe değişir: aynı stok özeti iki kez hesaplanırsa sonuç aynıdır, ama aynı bildirim iki
kez gönderilirse üye iki ileti alır. Bekçi, ikinci gruptaki işler için zorunludur.

## Özet

- Onayın etkiden önce yazılması en çok bir kez, sonra yazılması en az bir kez kipini üretir;
  tam bir kez ayrı bir sıra değil, en az bir kezin üstüne konan tekilleştirmenin sonucudur.
- Aynı on iki iletilik iş yükünde en çok bir kez kipi 12 teslimle 9 etki ve 3 kayıp üretti;
  en az bir kez kipi 15 teslimle 15 etki ve 3 yinelenen bildirim üretti.
- Tekilleştirmeli koşumda teslim sayısı yine 15 kaldı ama etki 12'ye indi: bekçi teslimi
  değil, etkiyi tekilleştirir.
- Bekçinin doğru çalışması, etkinin ve bekçi satırının aynı işlem sınırında yazılmasına
  bağlıdır; ayrıldıklarında kayıp ya da yineleme geri gelir.
- Etki veritabanının dışındayken hiçbir sıra ikisini birden önlemez: gönderim önce yapılınca
  bildirim iki kez gitti, onay önce yazılınca hiç gitmedi; alıcı tarafta tekillik anahtarı
  olduğunda iki çağrı tek etkiye indi.

## Sonraki Adım

Teslim sayısı ile etki sayısı ayrıldı, ama iki ölçüm de teslimin **sırasına** hiç bakmadı.
Ödünç sisteminde sıranın önemli olduğu yerler var: aynı kitabın "verildi" ve "iade alındı"
olayları ters sırayla işlenirse stok özeti kitabı hem rafta hem üyede gösterir. Önceki
derslerde işi hızlandırmanın yolu tüketici eklemekti; tüketici eklemek ise sırayı bozar.
Sonraki ders bu gerilimi ölçer: anahtara göre bölümleme ile sıranın nerede korunup nerede
kaybolduğunu, bölüm sayısı arttıkça koşutluğun ve sıra ihlallerinin nasıl değiştiğini sayar.
