---
title: 'Etkisizlik Anahtarları'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/etkisizlik-anahtarlari'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Etkisizlik Anahtarları

Yeniden denemede yinelenen etkiyi önleyen tekillik anahtarı: anahtarın kapsamı ve gövde özetiyle eşleştirilmesi, aynı anahtarla gelen ikinci isteğin ilk yanıtı yeniden sunması, farklı gövdeyle çakışmanın reddi ve anahtar deposunun yaşam süresi.

Üçüncü ders POST'un etkisiz olmadığını ölçmüştü: aynı istek iki kez gönderildiğinde iki
ödünç kaydı doğdu. O zaman bu, POST'un doğası diye geçildi. Sekizinci ders okuma tarafını
kapatınca geriye bu açık kaldı.

Açık, gerçek bir durumdan doğar. İstemci ödünç kaydı oluşturma isteğini gönderir, ağ
kopar. Uygulama Mimarisi kursundaki hata sözleşmesinin ağ hatası sınıfı tam olarak bunu
anlatıyordu: **istek sunucuya ulaşmış da olabilir, ulaşmamış da.** Yanıt yolda kaybolduysa
kayıt oluşmuştur ama istemci bunu bilmez. İstemci yeniden denerse ikinci kayıt riski
vardır; denemezse üye kitabı alamamış olabilir. İki seçenek de kabul edilemez.

Bu ders üçüncü bir yol kurar: isteğe, istemcinin ürettiği bir kimlik eklemek.

## Anahtarın İşi

**Tekillik anahtarı** (idempotency key), istemcinin bir işlem niyetine verdiği benzersiz
addır. Anahtar `Idempotency-Key` başlığıyla taşınır ve üç kuralı vardır.

Anahtar **istek başına değil, niyet başına** üretilir. Kullanıcı "ödünç ver" düğmesine
bastığında bir anahtar üretilir; ağ koptuğu için yapılan üç deneme aynı anahtarı taşır.
Kullanıcı ikinci kez ödünç almak isterse yeni bir anahtar üretilir.

Anahtar **yalnız başına yeterli değildir**. Sunucu, anahtarın hangi işlemle
ilişkilendirildiğini de saklamalıdır: hangi adrese, hangi gövdeyle. Aksi hâlde istemcinin
yanlışlıkla aynı anahtarı başka bir istekte kullanması, o isteğin sessizce yutulmasına yol
açar.

Anahtar **yanıtla birlikte saklanır**. İkinci istek geldiğinde işlem tekrar yapılmaz; ilk
seferde üretilmiş yanıt olduğu gibi geri verilir. İstemci ilk yanıtı hiç görmemiş olsa bile
sonucu öğrenir.

```js
// anahtar-sunucusu.mjs — tekillik anahtariyla korunan odunc olusturma
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";
import { createHash } from "node:crypto";

const db = new DatabaseSync("kutuphane.db");
db.exec(`CREATE TABLE IF NOT EXISTS tekillik (
  anahtar   TEXT PRIMARY KEY,
  yol       TEXT NOT NULL,
  ozet      TEXT NOT NULL,
  durum     INTEGER,
  yanit     TEXT,
  olusma    TEXT NOT NULL
)`);

const govdeOku = (istek) => new Promise((coz) => {
  let veri = ""; istek.on("data", (p) => (veri += p));
  istek.on("end", () => coz(veri));
});
const gonder = (yanit, kod, nesne, ek = {}) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...ek });
  yanit.end(JSON.stringify(nesne));
};

const sunucu = createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, "http://127.0.0.1").pathname;
  if (istek.method !== "POST" || yol !== "/oduncler") return gonder(yanit, 404, { hata: "yol_yok" });

  const ham = await govdeOku(istek);
  const anahtar = istek.headers["idempotency-key"];
  const ozet = createHash("sha256").update(ham).digest("hex").slice(0, 16);
  const g = JSON.parse(ham);

  // Anahtarsiz istek: her cagri yeni kayit uretir.
  if (!anahtar) return gonder(yanit, ...olustur(g));

  const kayit = db.prepare("SELECT * FROM tekillik WHERE anahtar = ?").get(anahtar);
  if (kayit) {
    // Ayni anahtar, farkli govde: istemci anahtari yeniden kullanmis.
    if (kayit.yol !== yol || kayit.ozet !== ozet)
      return gonder(yanit, 409, { hata: "anahtar_cakismasi", anahtar });
    return gonder(yanit, kayit.durum, JSON.parse(kayit.yanit), { "idempotency-replayed": "true" });
  }

  // Ayirma ve is, arada bekleme olmadan tek blokta yapilir.
  db.prepare("INSERT INTO tekillik (anahtar, yol, ozet, olusma) VALUES (?,?,?,datetime('now'))")
    .run(anahtar, yol, ozet);
  const [durum, govde] = olustur(g);
  db.prepare("UPDATE tekillik SET durum = ?, yanit = ? WHERE anahtar = ?")
    .run(durum, JSON.stringify(govde), anahtar);
  gonder(yanit, durum, govde);
});

function olustur(g) {
  if (!db.prepare("SELECT 1 FROM kitap WHERE isbn = ?").get(g.isbn))
    return [422, { hata: "dogrulama", alan: "isbn" }];
  const s = db.prepare("INSERT INTO odunc (uye, isbn, verilis, iade) VALUES (?,?,?,NULL)")
              .run(g.uye, g.isbn, g.verilis);
  return [201, { id: Number(s.lastInsertRowid), uye: g.uye, isbn: g.isbn, verilis: g.verilis }];
}

sunucu.listen(8483, "127.0.0.1", () => console.log("anahtar sunucusu 127.0.0.1:8483"));
```

## Ölçüm

Aynı gövde iki kez gönderilir; önce anahtarsız, sonra anahtarla. Katalog, yedinci dersteki
`katalog.sql` dosyasıyla kurulur.

```bash
# Ayni istek iki kez gonderilir: once anahtarsiz, sonra tekillik anahtariyla.
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql && sqlite3 kutuphane.db < katalog.sql
sqlite3 kutuphane.db "DELETE FROM odunc;"
node anahtar-sunucusu.mjs & sunucu=$!
sleep 0.4

GOVDE='{"uye":"U-1001","isbn":"K-02","verilis":"2026-03-10"}'
say() { printf '%-30s odunc satiri: %s\n' "$1" "$(sqlite3 kutuphane.db 'SELECT COUNT(*) FROM odunc;')"; }
yolla() { curl -sS -D - -o /tmp/govde.$$ -X POST -H 'content-type: application/json' "$@" \
            -d "$GOVDE" http://127.0.0.1:8483/oduncler \
          | grep -iE '^(HTTP/|idempotency-replayed)' | tr -d '\r'; cat /tmp/govde.$$; echo; rm -f /tmp/govde.$$; }

echo "--- anahtarsiz, iki kez ---"
yolla; yolla; say "anahtarsiz x2"

sqlite3 kutuphane.db "DELETE FROM odunc; DELETE FROM tekillik;"
echo "--- ayni anahtarla, iki kez ---"
yolla -H 'Idempotency-Key: OD-7f21'
yolla -H 'Idempotency-Key: OD-7f21'
say "anahtarli x2"

echo "--- ayni anahtar, farkli govde ---"
curl -sS -o /dev/null -w 'durum: %{http_code}\n' -X POST -H 'content-type: application/json' \
  -H 'Idempotency-Key: OD-7f21' -d '{"uye":"U-1002","isbn":"K-03","verilis":"2026-03-11"}' \
  http://127.0.0.1:8483/oduncler
say "cakisma sonrasi"

kill $sunucu
```

```
anahtar sunucusu 127.0.0.1:8483
--- anahtarsiz, iki kez ---
HTTP/1.1 201 Created
{"id":1,"uye":"U-1001","isbn":"K-02","verilis":"2026-03-10"}
HTTP/1.1 201 Created
{"id":2,"uye":"U-1001","isbn":"K-02","verilis":"2026-03-10"}
anahtarsiz x2                  odunc satiri: 2
--- ayni anahtarla, iki kez ---
HTTP/1.1 201 Created
{"id":1,"uye":"U-1001","isbn":"K-02","verilis":"2026-03-10"}
HTTP/1.1 201 Created
idempotency-replayed: true
{"id":1,"uye":"U-1001","isbn":"K-02","verilis":"2026-03-10"}
anahtarli x2                   odunc satiri: 1
--- ayni anahtar, farkli govde ---
durum: 409
cakisma sonrasi                odunc satiri: 1
```

Anahtarsız iki istek iki kayıt üretti ve iki farklı kimlik döndürdü. Anahtarlı iki istek
tek kayıt üretti ve **aynı gövdeyi** döndürdü: ikinci yanıtın `id` alanı da 1. İkinci
yanıt ayrıca `Idempotency-Replayed` başlığını taşıyor; bu başlık zorunlu değildir, ama
istemcinin ve kütüklerin durumu ayırt etmesini sağlar.

Son bölüm anahtarın yeniden kullanımını sınıyor. Aynı `OD-7f21` anahtarı farklı bir gövdeyle
geldi ve istek 409 ile reddedildi; ödünç kaydı sayısı bir kaldı. Gövde özeti saklanmasaydı
bu istek sessizce yutulur, istemci ikinci ödüncün oluştuğunu sanırdı. Reddetmek doğru
davranıştır: anahtarın yanlış kullanıldığını yalnızca istemci düzeltebilir.

## Eş Zamanlı İstekler

İki isteğin arka arkaya gelmesi kolay durumdur. Zor durum, ikisinin aynı anda gelmesidir:
istemci zaman aşımına uğrayıp yeniden denerken ilk istek hâlâ işlenmekte olabilir.

```bash
# Ayni anahtarli bes istek es zamanli gonderilir; uretilen kayit sayilir.
rm -f kutuphane.db yanit-*.json && sqlite3 kutuphane.db < sema.sql && sqlite3 kutuphane.db < katalog.sql
sqlite3 kutuphane.db "DELETE FROM odunc;"
node anahtar-sunucusu.mjs & sunucu=$!
sleep 0.4

pidler=""
for i in 1 2 3 4 5; do
  curl -sS -o "yanit-$i.json" -X POST -H 'content-type: application/json' \
    -H 'Idempotency-Key: OD-9c04' \
    -d '{"uye":"U-1002","isbn":"K-05","verilis":"2026-03-12"}' \
    http://127.0.0.1:8483/oduncler &
  pidler="$pidler $!"
done
wait $pidler

for f in yanit-*.json; do cat "$f"; echo; done | sort -u > cesitler.txt
echo "donen govde cesidi : $(wc -l < cesitler.txt | tr -d ' ')"
echo "govde              : $(cat cesitler.txt)"
echo "odunc satiri       : $(sqlite3 kutuphane.db 'SELECT COUNT(*) FROM odunc;')"

echo "--- anahtar deposunun yasi ---"
sqlite3 kutuphane.db "
  INSERT INTO tekillik (anahtar, yol, ozet, durum, yanit, olusma)
  VALUES ('OD-eski','/oduncler','0000',201,'{}',datetime('now','-30 day'));
  SELECT anahtar || '  yas: ' || CAST(julianday('now') - julianday(olusma) AS INT) || ' gun'
    FROM tekillik ORDER BY olusma;
  DELETE FROM tekillik WHERE olusma < datetime('now','-7 day');
  SELECT 'temizlik sonrasi kalan: ' || COUNT(*) FROM tekillik;"

kill $sunucu
```

```
anahtar sunucusu 127.0.0.1:8483
donen govde cesidi : 1
govde              : {"id":1,"uye":"U-1002","isbn":"K-05","verilis":"2026-03-12"}
odunc satiri       : 1
--- anahtar deposunun yasi ---
OD-eski  yas: 30 gun
OD-9c04  yas: 0 gun
temizlik sonrasi kalan: 1
```

Beş eş zamanlı istek tek kayıt üretti ve beşi de aynı gövdeyi aldı. Bunu sağlayan şey
kodun yapısıdır: anahtarın kayda geçirilmesi ile işin yapılması arasında hiçbir bekleme
noktası yok. Araya bir bekleme girerse iki istek de "anahtar yok" görüp ikisi de kayıt
oluşturabilir.

Bu düzenin tek süreçli bir sunucuda yeterli olması, çok süreçli bir dağıtımda yeterli
olacağı anlamına gelmez. Genel çözüm, anahtarın **birincil anahtar olarak yazılmasıdır**:
ikinci yazma girişimi veritabanı tarafından reddedilir ve o istek bekleyip yeniden okumaya
ya da "işlem sürüyor" yanıtına yönlendirilir. Bu tür yarışların işlem düzeyinde nasıl
yalıtıldığı İleri SQL kursundaki işlemler konusunun kapsamındadır.

## Anahtar Deposunun Yaşam Süresi

Çıktının son bölümü anahtar deposunun yaşlanmasını gösteriyor. Otuz gün önce yazılmış bir
anahtar, yedi günlük saklama penceresiyle temizlendi.

Süre kararının iki sınırı vardır. Alt sınırı **istemcinin en uzun deneme penceresi**
belirler: bir istemci yirmi dört saat sonra hâlâ yeniden deniyorsa, anahtar o süre boyunca
saklanmalıdır. Uygulama Mimarisi kursundaki Yeniden Deneme ve Geri Çekilme dersinde
tanımlanan deneme bütçesi bu pencereyi verir. Üst sınırı depolama maliyeti ve tabloya
yazılan yanıt gövdelerinin büyüklüğü belirler.

Pencere dolduktan sonra aynı anahtarla gelen bir istek yeni bir işlem sayılır ve yeni kayıt
üretir. Bu, tasarımın kabul edilmiş sınırıdır; süre seçilirken bu bilinerek seçilir.

Son bir sınır da kapsamdır. Anahtar tek başına değil, kimliği doğrulanmış istemciyle
birlikte benzersiz olmalıdır: iki farklı istemcinin aynı anahtar dizgisini üretmesi,
birinin isteğinin diğerinin yanıtını almasına yol açmamalıdır. Yukarıdaki tabloda anahtar
tek başına birincil anahtardır; gerçek bir dağıtımda istemci kimliği de anahtarın parçası
olur.

## Özet

- Ağ hatasında isteğin sunucuya ulaşıp ulaşmadığı bilinmez; yeniden denemek yinelenen etki,
  dememek eksik etki riski taşır.
- Tekillik anahtarı istek başına değil niyet başına üretilir; aynı işlemin bütün denemeleri
  aynı anahtarı taşır.
- Sunucu anahtarı yol ve gövde özetiyle birlikte saklar; ölçümde anahtarsız iki istek iki
  kayıt, anahtarlı iki istek bir kayıt üretti ve ikinci yanıt ilkinin aynısı oldu.
- Aynı anahtarın farklı gövdeyle gelmesi 409 ile reddedilir; sessizce yutmak istemciye
  olmayan bir işlemi olmuş gibi gösterir.
- Anahtarın kayda geçirilmesi ile işin yapılması arasına bekleme girmemelidir; eş zamanlı
  beş istek bu yapıyla tek kayıt üretti.
- Anahtar deposunun saklama süresi istemcinin en uzun deneme penceresinden kısa olamaz;
  pencere dolduktan sonra aynı anahtar yeni bir işlem sayılır.

## Sonraki Adım

Dokuz ders boyunca istemcinin her adresi kendisi kurduğu varsayıldı: koleksiyon adresini,
sayfa imlecini, alan listesini, hatta yeni oluşturulan kaydın adresini. İstemci bunu
yapabilmek için sözleşmenin biçimini kendi içine gömer — ve sözleşme değiştiğinde her
istemci ayrı ayrı güncellenir. Sonraki ders bu bağımlılığı gevşetir: yanıt, istemcinin
bir sonraki isteğinde kullanacağı adresleri kendi içinde taşır. Ödünç kaydının hangi
işlemlere açık olduğu da aynı yerden okunur; iade edilmiş bir kayıt artık iade bağlantısı
sunmaz.
