İçeriğe geç
Muhammet Şafak
en
Güncelleme 3 dk okuma

Idempotency: Aynı Mesaj İki Kez Geldiğinde

At-least-once teslimat, bir handler'ın aynı mesajı iki kez göreceği anlamına gelir. Idempotency anahtarı, nereden geldiği ve çökmeden sağ çıkan dedupe.

Kısaca

At-least-once yok edilecek bir arıza değil, etrafında tasarım yapılacak bir garantidir. Anahtarı gönderici basar ve yalnızca payload'dan türetilmez; işi yapmakla anahtarı kaydetmek arasındaki boşluk da şekli belirler: etki senin kontrolündeyse anahtarı aynı transaction'da yaz, değilse seen-set'le yetin. Eşzamanlı mükerrerler için kontrol değil, unique constraint gerekir.

Idempotency: Aynı Mesaj İki Kez Geldiğinde — kapak görseli

Bir müşteri tek sipariş için iki kez ücretlendirildi. Hiçbir şey bilerek iki kez çalışmadı: ödeme worker’ı kartı çekti, sonra mesajı ack’leyemeden çöktü — broker da sözünü tutup mesajı yeniden teslim etti.

Hata, mükerrer teslimat değildi. Mükerrer teslimat için inşa edilmemiş handler’dı.

At-least-once bir söz, bir arıza değil

Neredeyse her queue — ve yeniden denenen her HTTP çağrısı — at-least-once’tır. Bir handler aynı mesajı birden çok kez görebilir: bir çökme, bir release, bir redelivery ya da timeout’a düşüp yeniden deneyen bir istemci sonrası. Ağ üzerinde exactly-once, broker’ın ucuza veremeyeceği bir koordinasyon olmadan pratik değil; bu yüzden çok az broker onu vaat eder — vaat edenler de (Google Cloud Pub/Sub gibi) dar koşullara sıkıştırır: tek bölge, yalnızca pull aboneliği.

Bu, problemi yeniden çerçeveler. Redelivery, yok edilecek bir başarısızlık değil; etrafında tasarım yapılacak bir garanti. (Queue’nun başarısız bir job’u kaybetmek yerine yeniden denerken yaptığı pazarlığın aynısı.)

Idempotent demek “iki kez = bir kez” demek

Bir operasyon, defalarca uygulandığında etkisi bir kez uygulanmışla aynıysa idempotent’tir — f(f(x)) = f(x).

  • “Sipariş durumunu paid yap” idempotent’tir.
  • “Bakiyeyi 10 artır” değildir — iki kez +20 eder.
  • “Karşılama e-postasını gönder” değildir — iki kez iki e-posta eder.

Bütün iş, ikinci tür operasyonu birinciye çevirmek.

Idempotency anahtarı göndericiden gelir

Bu operasyon için kararlı bir kimliğe ihtiyacın var ve bunu gönderici belirlemeli, alıcı değil:

  • Bir queue mesajı için bu, üreticinin ürettiği mesaj-başına id’dir — mesaj başına bir id, trace/correlation id’den ayrı (o, birçok mesajı kapsar).
  • Bir HTTP yazımı için bu, istemcinin ürettiği Idempotency-Key başlığı’dır — Stripe modeli.

İki kural anahtarı dürüst tutar:

  • Yalnızca payload’dan türetme. Meşru biçimde birbirinin aynı iki istek — aynı müşterinin aynı ürünü iki kez alması — çakışır ve ikincisi sessizce düşürülür.
  • Anahtarı alıcıya ürettirme. Alıcı, bir retry’ı yepyeni bir çağrıdan ayıramaz; “bu, daha önce denediğim aynı operasyon” bilgisi yalnızca göndericide vardır.

Dedupe: zaten yaptığını hatırla

Handler, işten önce tek bir kontrol yapar: bu anahtar işlendi mi? Evetse atla ve ack’le. Hayırsa işi yap, sonra anahtarı kaydet. Tüm desen bu — ağırlık, kaydın nerede durduğunda.

İş ile kayıt arasındaki çökme

Zor kısım, işi yapmakla anahtarı kaydetmek arasındaki boşluk. İki şekil var ve doğrusu yan etkiye bağlı:

  • Seen-set — başarıdan sonra kaydet. Handler döndükten sonra anahtarı Redis’e ya da bir tabloya yaz. Ucuz ve geniş çapta uygulanabilir. Pencere: yan etkiden sonra ama kayıttan önce çökersen, bir redelivery yeniden işler. Yan etkinin kendisi idempotent’se sorun değil — bir UPSERT, bir “set to paid”.
  • Transactional — işle birlikte kaydet. Idempotency kaydını, iş değişikliğiyle aynı veritabanı transaction’ında yaz. Pencere yok: anahtar ve etki birlikte commit olur ya da birlikte geri alınır. Bedeli: etki, senin kontrol ettiğin bir DB yazımı olmalı ve anahtar deposu aynı veritabanı olmalı — ayrı bir Redis değil. Kendi kontrolündeki bir etki için mükerrer işlemeyi gerçekten bitiren budur.

Yani yan etki senin yerine seçer: sahip olduğun bir satır → transactional; transaction’ına dahil edemeyeceğin üçüncü-taraf bir çağrı (e-posta, bir çekim) → seen-set ve kendi anahtarını onun Idempotency-Key’i olarak ileterek downstream’i de idempotent yap.

Eşzamanlı mükerrerlere kontrol değil, constraint lazım

Aynı anahtarın, ikisi de kaydetmeden yarışan iki teslimatı bir “önce kontrol et sonra yaz” dizisinden sıyrılır — o dizi atomik değil. Anahtar kolonundaki bir unique constraint atomiktir: ikinci insert, yakalayıp “zaten işlendi” diye ele aldığın bir conflict’e dönüşür. Bu, sıralı numara üretiminde bir race’in açtığı gap’lerin aynı şekli — benzersizlik gerçekte uygulamada değil, veritabanında zorlanır.

Buna ne zaman hiç ihtiyacın yok?

Handler zaten idempotent’se — kararlı bir id ile anahtarlanmış saf bir UPSERT, bir “alanı şu değere ayarla” — hiçbir şeye ihtiyacın olmayabilir. Anahtar + dedupe’a, operasyonun idempotent olmayan bir yan etkisi (para, e-posta, dış bir POST) ya da birikimi (increment, append) olduğunda uzan. Karmaşıklığı yalnızca ikinci çalıştırmanın gerçekten zarar verdiği yere harca.


At-least-once, düzelteceğin kısım değil; kabul ettiğin kısım. Handler’ı, ikinci teslimat bir no-op olacak şekilde kur; o zaman redelivery bir olay olmaktan çıkar ve olduğu şeye döner — broker’ın sözünü tutması.


İlgili: BabelQueue idempotency spec’i anahtarın nereden geldiğini ve dedupe’un nasıl davrandığını sabitler; idempotency-payments örneği ise tam da bu çift-çekim vakasının çalıştırılabilir hâlidir.

Sık sorulanlar

2 soru

  • Idempotency anahtarını kim üretmeli — gönderici mi, alıcı mı?

    Bunu gönderici belirlemeli, alıcı değil. Bir queue mesajı için bu, üreticinin ürettiği mesaj-başına id'dir; bir HTTP yazımı için bu, istemcinin ürettiği Idempotency-Key başlığıdır — Stripe modeli. Alıcı bir retry'ı yepyeni bir çağrıdan ayıramaz; "bu, daha önce denediğim aynı operasyon" bilgisi yalnızca göndericide vardır.

  • Handler bir mesajın daha önce işlendiğini nasıl anlar?

    Handler, işten önce tek bir kontrol yapar: bu anahtar işlendi mi? Evetse atla ve ack'le. Hayırsa işi yap, sonra anahtarı kaydet. Tüm desen bu — ağırlık, kaydın nerede durduğunda.

Kaynaklar

İlgili

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi