Idempotency Neden Yalnızca Bir API Detayı Değildir?
Ödeme, sipariş oluşturma, üyelik başlatma ve stok düşme gibi işlemlerde istemci; zaman aşımı, bağlantı kopması veya kullanıcı tekrar tıklaması nedeniyle aynı isteği yeniden gönderebilir. İlk işlem sunucuda tamamlanmış olsa bile yanıt istemciye ulaşmamış olabilir. Bu nedenle istemci tarafındaki kontrol tek başına yeterli değildir.
Yalnızca uygulama kodunda “bu istek daha önce geldi mi?” kontrolü yapmak, eşzamanlı worker'lar arasında yarış koşuluna yol açar. Sağlam tasarımda idempotency anahtarı veritabanında benzersiz bir kısıtla korunur. Böylece uygulama hatalı davransa bile veri bütünlüğü için son savunma katmanı veritabanı olur.
Anahtarın kapsamı önceden tanımlanmalıdır. Örneğin aynı anahtar; tenant, kullanıcı, işlem türü veya ödeme hesabı bağlamında mı benzersiz olacaktır? Kapsam belirsiz bırakılırsa farklı siparişlerin yanlışlıkla aynı işlem kabul edilmesi ya da aynı işlemin farklı bağlamlarda yeniden çalıştırılması mümkün olur.
Veri Modeli ve Benzersizlik Kuralları
İstek kaydı en az şu bilgileri içerebilir:
Temel akış şu şekilde modellenebilir:
Sona erme süresi belirlenirken istemcinin ve dış sağlayıcının retry penceresi dikkate alınmalıdır. Kayıt çok erken silinirse aynı anahtar yeniden kullanıldığında işlem ikinci kez gerçekleşebilir. Çok uzun saklama ise tablo büyümesini ve arşivleme maliyetini artırır.
Transaction, Isolation ve Dış Servis Sınırları
İş etkisini ve idempotency kaydını hangi transaction'ın koruduğu açıkça tanımlanmalıdır. Sipariş kaydı, stok rezervasyonu ve idempotency durumunun aynı transaction içinde yazılması atomikliği güçlendirir. Ancak dış ödeme sağlayıcısı veya başka bir HTTP servisi çağrısını uzun süreli veritabanı transaction'ı içinde tutmak kilit beklemelerini, bağlantı havuzu tüketimini ve deadlock riskini artırır.
Dış çağrılar için çoğu sistemde outbox yaklaşımı daha güvenlidir:
Isolation seviyesini yükseltmek her sorunu çözmez. Önce korunacak invariant'ı yazın: “aynı sipariş iki kez oluşturulamaz”, “stok sıfırın altına inemez” veya “aynı ödeme iki kez kesinleşemez”. Ardından bunu UNIQUE kısıtı, atomik koşullu UPDATE, uygun satır kilidi ya da gerektiğinde retry ile uygulayın. Daha yüksek isolation seçiminin lock wait, deadlock ve transaction retry maliyetini ölçmeden yapılması performansı kötüleştirebilir.
Retry, Queue ve Belirsiz Sonuçlar
Hataları en az üç sınıfa ayırın:
Ölçümleme ve Test Kontrolü
Başarıyı yalnızca ortalama gecikmeyle değerlendirmeyin. İşlem türü, tenant, hata sınıfı ve sonuç durumuna göre şu metrikleri izleyin:
Son kabul kriteri net olmalıdır: Aynı iş beş kez teslim edilse bile veritabanındaki benzersiz kısıtlar, transaction sınırları, worker davranışı ve dış servis mutabakatı birlikte tek bir geçerli iş sonucu oluşmasını nasıl garanti ediyor?
Ödeme, sipariş oluşturma, üyelik başlatma ve stok düşme gibi işlemlerde istemci; zaman aşımı, bağlantı kopması veya kullanıcı tekrar tıklaması nedeniyle aynı isteği yeniden gönderebilir. İlk işlem sunucuda tamamlanmış olsa bile yanıt istemciye ulaşmamış olabilir. Bu nedenle istemci tarafındaki kontrol tek başına yeterli değildir.
Yalnızca uygulama kodunda “bu istek daha önce geldi mi?” kontrolü yapmak, eşzamanlı worker'lar arasında yarış koşuluna yol açar. Sağlam tasarımda idempotency anahtarı veritabanında benzersiz bir kısıtla korunur. Böylece uygulama hatalı davransa bile veri bütünlüğü için son savunma katmanı veritabanı olur.
Anahtarın kapsamı önceden tanımlanmalıdır. Örneğin aynı anahtar; tenant, kullanıcı, işlem türü veya ödeme hesabı bağlamında mı benzersiz olacaktır? Kapsam belirsiz bırakılırsa farklı siparişlerin yanlışlıkla aynı işlem kabul edilmesi ya da aynı işlemin farklı bağlamlarda yeniden çalıştırılması mümkün olur.
Veri Modeli ve Benzersizlik Kuralları
İstek kaydı en az şu bilgileri içerebilir:
- Idempotency anahtarı ve kapsamı
- İşlem türü
- İsteğin güvenli bir özet değeri veya hash'i
- İşlem durumu: processing, succeeded, failed veya benzeri açık durumlar
- Oluşturulma, güncellenme ve sona erme zamanları
- Yeniden döndürülebilecek durum kodu ve yanıt gövdesi
- İlişkili sipariş, ödeme veya iş kaydının kimliği
Temel akış şu şekilde modellenebilir:
İstek geldi
-> kayıt yoksa atomik biçimde oluştur
-> aynı anahtar ve farklı içerik varsa tutarsızlık hatası döndür
-> tamamlanmışsa kalıcı sonucu yeniden döndür
-> çalışıyorsa bekleme, çakışma veya durum sorgulama politikasını uygula
-> başarısızsa güvenli retry koşullarını değerlendir
Sona erme süresi belirlenirken istemcinin ve dış sağlayıcının retry penceresi dikkate alınmalıdır. Kayıt çok erken silinirse aynı anahtar yeniden kullanıldığında işlem ikinci kez gerçekleşebilir. Çok uzun saklama ise tablo büyümesini ve arşivleme maliyetini artırır.
Transaction, Isolation ve Dış Servis Sınırları
İş etkisini ve idempotency kaydını hangi transaction'ın koruduğu açıkça tanımlanmalıdır. Sipariş kaydı, stok rezervasyonu ve idempotency durumunun aynı transaction içinde yazılması atomikliği güçlendirir. Ancak dış ödeme sağlayıcısı veya başka bir HTTP servisi çağrısını uzun süreli veritabanı transaction'ı içinde tutmak kilit beklemelerini, bağlantı havuzu tüketimini ve deadlock riskini artırır.
Dış çağrılar için çoğu sistemde outbox yaklaşımı daha güvenlidir:
- Yerel iş kaydı ve gönderilecek outbox mesajı aynı transaction içinde oluşturulur.
- Worker mesajı alır ve dış servise çağrı yapar.
- Başarı, geçici hata ve sonucu belirsiz durumlar ayrı işlenir.
- Mesajın yeniden teslim edilebileceği varsayılır.
Isolation seviyesini yükseltmek her sorunu çözmez. Önce korunacak invariant'ı yazın: “aynı sipariş iki kez oluşturulamaz”, “stok sıfırın altına inemez” veya “aynı ödeme iki kez kesinleşemez”. Ardından bunu UNIQUE kısıtı, atomik koşullu UPDATE, uygun satır kilidi ya da gerektiğinde retry ile uygulayın. Daha yüksek isolation seçiminin lock wait, deadlock ve transaction retry maliyetini ölçmeden yapılması performansı kötüleştirebilir.
Retry, Queue ve Belirsiz Sonuçlar
Hataları en az üç sınıfa ayırın:
- Geçici: bağlantı kopması, lock wait timeout veya kısa süreli servis hatası. Sınırlı sayıda exponential backoff ve jitter ile denenebilir.
- Kalıcı: doğrulama, yetki veya iş kuralı hatası. Otomatik retry yapılmamalıdır.
- Belirsiz: commit gerçekleşmiş olabilir ancak yanıt kaybolmuştur. Yeni işlem başlatmak yerine idempotency kaydı veya dış servis sonucu sorgulanmalıdır.
Ölçümleme ve Test Kontrolü
Başarıyı yalnızca ortalama gecikmeyle değerlendirmeyin. İşlem türü, tenant, hata sınıfı ve sonuç durumuna göre şu metrikleri izleyin:
- Önceki sonucun döndürüldüğü idempotent retry oranı
- Transaction retry, deadlock ve lock wait sayıları
- Queue redelivery, consumer başarısızlığı ve dead-letter mesajları
- İşlemin başlatılmasından kesinleşmesine kadar geçen süre ve yüzdelik dilimler
- Bağlantı havuzu doluluğu, sorgu gecikmesi ve benzersizlik ihlalleri
- Belirsiz sonuç, mutabakat ve veri tutarsızlığı alarm sayıları
Son kabul kriteri net olmalıdır: Aynı iş beş kez teslim edilse bile veritabanındaki benzersiz kısıtlar, transaction sınırları, worker davranışı ve dış servis mutabakatı birlikte tek bir geçerli iş sonucu oluşmasını nasıl garanti ediyor?