API Tasarımı & Dağıtık Sistemler 4 dk okuma 11 okunma

Idempotency Key Tasarımı: Güvenli Tekrarlanan İstekler

Idempotency Key, ağ hataları ve retry'lar nedeniyle aynı isteğin birden fazla kez işlenmesini engelleyen istemci üretimli benzersiz bir anahtardır. Anahtar, işlem sonucuyla birlikte TTL'li olarak saklanır; tekrarlanan istekte işlem yeniden yapılmaz, önbellekteki yanıt döner. Scope, atomiklik, eşzamanlı istekler ve hata durumları doğru tasarlanmazsa, desen güvenlik açığına dönüşür.

Idempotency Key Tasarımı: Güvenli Tekrarlanan İstekler

Idempotency Key Tasarımı: Güvenli Tekrarlanan İstekler

Dağıtık sistemlerde, ağ zaman aşımları, istemci retry'ları veya kullanıcıların çift tıklaması nedeniyle aynı istek birden fazla kez gönderilebilir. Ödeme, sipariş oluşturma, bakiye transferi gibi yan etkili işlemlerde bu, çift işlem (double charge, duplicate order) anlamına gelir. Idempotency Key, bu sorunu çözen, istemci tarafından üretilen benzersiz bir anahtardır; sunucu bu anahtar sayesinde isteğin daha önce işlenip işlenmediğini anlar.


1. Idempotency Key Nedir ve Nasıl Çalışır?

İstemci, her mantıksal işlem için benzersiz bir anahtar (genellikle UUID v4) üretir ve istekle birlikte gönderir. Sunucu bu anahtarı bir depoda arar:

  1. Anahtar yoksa: İşlem gerçekleştirilir, sonuç (durum kodu + yanıt gövdesi) anahtarla birlikte saklanır.

  2. Anahtar varsa (işlem tamamlanmış): İşlem tekrarlanmaz; saklanan yanıt aynen döner.

  3. Anahtar varsa (işlem devam ediyor): Genellikle 409 Conflict döner; istemci beklemelidir.

Kritik Kural: Anahtar, istek gövdesinden bağımsız olarak yalnızca o mantıksal işlemi temsil etmelidir. Aynı anahtar, farklı bir gövdeyle gelirse bu bir hata durumudur ve reddedilmelidir.


2. Tasarım Kararları: Scope, Format ve TTL

A. Scope (Kapsam) ve Benzersizlik

Anahtar, kiracı (tenant) ve endpoint bazında benzersiz olmalıdır. Aynı anahtarın farklı kullanıcılar veya farklı API'ler arasında çakışması, veri sızıntısına yol açar. Depolama anahtarı genellikle şu şekilde birleştirilir:

text

{userId}:{endpoint}:{idempotencyKey}

B. Anahtar Formatı

  • UUID v4: En yaygın ve önerilen yöntem. İstemci üretir; çakışma olasılığı ihmal edilebilir.

  • İstek Hash'i: Gövdenin hash'i. Ancak zaman damgası gibi değişkenler hash'i bozar; dikkatli kullanılmalıdır.

  • İşlem Kimliği: İş mantığında zaten var olan benzersiz bir kimlik (ör. sipariş numarası).

C. TTL (Yaşam Süresi)

Anahtarlar sonsuza kadar saklanmamalıdır. Tipik TTL 24-72 saat arasındadır. Ödeme gibi uzun vadeli işlemlerde daha uzun, oturum bazlı işlemlerde daha kısa tutulabilir.


3. En Kritik Konu: Atomiklik ve Eşzamanlı İstekler

Aynı anahtarla aynı anda iki istek gelirse ne olur? Bu, tasarımın en zorlu kısmıdır.

"First-Wins" (İlk Kazanır) Yaklaşımı:
İlk istek işleme alınırken anahtar "işlemde" (processing) olarak işaretlenir; aynı anahtarla gelen diğer istekler ya bekletilir ya da 409 Conflict alır.

Atomik İşlem Nasıl Sağlanır?

  • Veritabanı: Anahtar üzerinde UNIQUE kısıtlaması ile tek bir transaction içinde işlem + kayıt yapılır. İkinci istek unique constraint violation alır.

  • Redis: SET key value NX PX ttl komutu ile atomik olarak "yoksa yaz" işlemi yapılır. NX başarısız olursa, istek zaten işlemdedir.

Hata Durumları:

  • İşlem başarısız olduysa: Anahtar silinmeli veya "başarısız" olarak işaretlenmelidir; aksi halde istemci aynı anahtarla asla tekrar deneyemez.

  • İşlem başarılı olduysa: Yanıt saklanır ve tekrarlanan isteklerde döner.


4. Pratik Uygulama: .NET'te Idempotency Key

Örnek: Redis Tabanlı Idempotency Middleware

csharp

public async Task InvokeAsync(HttpContext context)
{
    if (!context.Request.Headers.TryGetValue("Idempotency-Key", out var key))
    {
        await _next(context);
        return;
    }

    var cacheKey = $"{context.User.Identity?.Name}:{context.Request.Path}:{key}";
    
    // Atomik "yoksa yaz" (NX)
    var acquired = await _redis.StringSetAsync(cacheKey, "processing", 
        TimeSpan.FromHours(24), When.NotExists);

    if (!acquired)
    {
        // Zaten işlenmiş veya işlemde
        var cached = await _redis.StringGetAsync(cacheKey);
        if (cached == "processing")
        {
            context.Response.StatusCode = 409;
            return;
        }
        // Saklanan yanıtı döndür
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync(cached);
        return;
    }

    // İşlemi çalıştır ve yanıtı yakala
    var originalBody = context.Response.Body;
    using var memStream = new MemoryStream();
    context.Response.Body = memStream;

    await _next(context);

    memStream.Position = 0;
    var responseBody = await new StreamReader(memStream).ReadToEndAsync();
    
    // Başarılıysa yanıtı sakla; değilse anahtarı sil
    if (context.Response.StatusCode < 400)
        await _redis.StringSetAsync(cacheKey, responseBody, TimeSpan.FromHours(24));
    else
        await _redis.KeyDeleteAsync(cacheKey);

    memStream.Position = 0;
    await memStream.CopyToAsync(originalBody);
}

Hazır Kütüphaneler: IdempotentAPI (NuGet), ASP.NET Core için hazır middleware ve Redis/SQL Server desteği sunar.


5. Ne Zaman Kullanılmalı?

Uygun Uygun Değil
POST ile kaynak oluşturma (sipariş, ödeme) GET istekleri (zaten idempotent)
Finansal işlemler (bakiye transferi, iade) PUT/DELETE (doğası gereği idempotent)
Harici API çağrıları (retry riski) Kesinlikle idempotent olmayan işlemler
Kullanıcı çift tıklaması riski olan formlar —

Önemli: Idempotency Key, yalnızca yan etkili (non-idempotent) işlemlerde anlamlıdır. GET, PUT, DELETE gibi doğası gereği idempotent metotlarda kullanmak gereksiz karmaşıklık yaratır.


6. En İyi Pratikler

Pratik Açıklama
Scope'u doğru belirleyin (kullanıcı, endpoint, anahtar) bazında benzersizlik sağlayın.
Atomik "yoksa yaz" kullanın Redis SET NX veya veritabanı UNIQUE kısıtlaması.
Hata durumunda anahtarı silin Aksi halde istemci aynı anahtarla tekrar deneyemez.
TTL belirleyin 24-72 saat; işlemin doğasına göre ayarlayın.
Anahtarı asla log'lamayın Anahtar, hassas bir kimlik bilgisi gibi davranmalıdır.

Sonuç

Idempotency Key, dağıtık sistemlerde güvenli retry'ların temelidir. Doğru tasarlandığında, ağ hataları ve kullanıcı hataları nedeniyle oluşan çift işlemleri engeller; yanlış tasarlandığında ise ya işlemleri kilitler ya da güvenlik açığı yaratır. Anahtarın scope'u, atomikliği, TTL'i ve hata durumları dikkatle ele alınmalıdır. Unutmayın: Idempotency Key bir "sihirli değnek" değil, bir sözleşmedir; istemci ve sunucu bu sözleşmeye aynı kurallarla uymalıdır.

Tüm yazılar Soru ya da yorumun mu var? Yaz