Kullanıcı "Öde" düğmesine bastı; sayfa dondu; düğmeye bir daha bastı. Ya da daha kötüsü: tek bir bastı, ama ağ zaman aşımına uğradı ve istemci isteği yeniden gönderdi. Sunucu için iki ödeme emri vardır ve ikisi de meşru görünebilir. Idempotency key, bu ikilemin standart cevabıdır: istemci her işleme bir kimlik ekler, sunucu aynı kimliği ikinci gördüğünde ilk cevabı tekrarlar, işi tekrarlamaz.

Sorun yalnızca ödeme uçlarında da değildir. Rezervasyon, form gönderimi, sipariş oluşturma, bildirim tetikleme: ağın üstünden gelen her yan etkili istek, aynı tuzağın potansiyel kurbanıdır. Yeniden deneme yapan her istemci — tarayıcı, mobil uygulama, aradaki bir ağ geçidi — bu tuzağı bir gün çalıştırır.

Anahtar Nasıl Çalışır?

Desen üç adımdır. İstemci, işlemi başlatırken rastgele ve o işlem için özgün bir anahtar üretir; aynı işlemi yeniden denerse aynı anahtarı taşır. Sunucu, anahtarı ilk kez görüyorsa işi yürütür ve sonucu anahtarla birlikte saklar. Anahtar daha önce görülmüşse, işi çalıştırmadan saklanan cevabı döndürür.

Cevabın birebir saklanmasına dikkat edin: aynı anahtarla gelen ikinci istek, ilk cevabın durum kodunu ve gövdesini aynen almalıdır. İlk istek 201 aldıysa, tekrarı 200 almamalıdır; istemci tarafındaki yeniden deneme mantığı bu tutarlılığa güvenir.

const mevcut = await db.idempotency.findUnique({ where: { anahtar } });
if (mevcut) return new Response(mevcut.govde, { status: mevcut.durum });

const sonuc = await odemeIsle(govde);
await db.idempotency.create({ data: { anahtar, govde: sonuc.govde, durum: sonuc.durum } });
return new Response(sonuc.govde, { status: sonuc.durum });

Ödeme sağlayıcılarının büyük bölümü bu deseni API'lerine gömmüştür; Stripe'ın Idempotency-Key başlığı, desenin en bilinen örneğidir. Kendi uçlarınızda da aynı isimle taşımak, entegrasyonculara yabancı gelmez.

Yarış Koşulu: Anahtar Yeterli Değildir

İki istek de neredeyse aynı anda gelirse, ikisi de "anahtar yok" cevabını alabilir; ikisi de işlemi başlatır ve mükerrer doğar. Bu, veritabanı seviyesinde çözülür: anahtar sütununu benzersiz bir dizin hâline getirin. İkinci ekleme denemesi çakışma hatası alır ve o noktada ilk işlemin sonucunu bekleyip döndürür.

CREATE TABLE idempotency (
  anahtar TEXT PRIMARY KEY,
  govde   JSONB,
  durum   INT,
  olusturma TIMESTAMPTZ DEFAULT now()
);

Bir de işin sonucu değil süresi meselesi var: ilk istek hâlâ işlenirken gelen ikinci istek ne yapmalı? Cevap, işlem başına bir durum satırı tutmaktır: işleniyor, tamamlandı, başarısız. İşleniyorsa bekleyip yeniden kontrol; tamamlandıysa saklanan cevap; başarısızsa aynı anahtarla yeniden deneme hakkı.

Bu durum satırının bir de eğitim değeri vardır: ekip, "işleniyor" hâlinde takılı kalan işleri gördüğünde, işin içindeki uzun süren adımı bulmak kolaylaşır. Anahtar tablosu, yalnızca mükerrerliği önlemekle kalmaz; işlem geçmişinin de en dürüst kaydıdır.

Anahtar Yaşam Döngüsü

Anahtarlar sonsuza kadar saklanmaz; bir tutma süresi belirleyip eskilerini temizleyin. Kullanıcı sepetini boşaltıp yeni bir sipariş açtıysa, yeni işlem yeni anahtar almalıdır: anahtarı istemci belirler, sunucu değil. Sunucu anahtarı kendisi üretirse, iki gerçekten farklı işlemi yanlışlıkla aynı anahtara ezebilir.

Idempotency anahtarı, ağı güvenilmez kılan her şeyin — tekrar denemelerin, zaman aşımının, çift tıklamanın — karşısına konan tek bir kelimelik sigortadır. Ödeme gibi geri alınamaz her uçta bulundurun; ağı değiştiremezsiniz ama ağırlığını taşıyan kodu siz yazarsınız. Sigortayı kaza olduktan sonra eklemek de mümkündür; ama o noktada eksik police, muhasebenizin iki kat ödeme yapmış olmasından sonra keşfedilir.