İleri5 dakikalık okuma

DynamoDB'de Birden Çok Attribute'ta Benzersizlik

DynamoDB tam olarak tek bir şey için benzersizliği garanti eder: . UNIQUE (email) kısıtı, UNIQUE (username) kısıtı yoktur ve iki attribute'ı kapsayan hiçbir şey yoktur. SQL'den gelince, bu yokluk ilk sürprizdir — ve insanların sessizce bir yarış koşulu (race condition) gönderdiği ilk yerdir.

DynamoDB'de birden çok attribute'ta benzersizlik kısıtı nasıl uygulanır?

DynamoDB'nin ötesinde bir UNIQUE kısıtı yoktur, bu yüzden benzersizliği kendiniz zorlarsınız: korunan her değeri, anahtarı o değer olan kendi işaretçi item'ı olarak modelleyin, ardından kaydı ve her işaretçiyi tek bir TransactWriteItems içinde birlikte yazın, her put'u attribute_not_exists ile koruyun. Motorun zaten zorladığı çakışma, sizin kısıtınız olur.

  • Benzersizlik kısıtı yoktur — motor tarafından yalnızca birincil anahtar benzersiz olarak zorlanır. "Benzersiz olmalı" denen diğer her attribute sizin işinizdir.
  • Her benzersizlik kuralını kendi item'ı olarak modelleyin. Anahtarı, koruduğunuz değer olan özel bir işaretçi item, "bu e-posta alınmış mı?" sorusunu motorun zaten zorladığı bir anahtar çakışmasına dönüştürür.
  • Onları TransactWriteItems ile atomik olarak yazın. Tek bir , her put attribute_not_exists ile korunur, böylece tüm işaretçiler ve gerçek kayıt birlikte commit edilir ya da hiçbiri edilmez.
  • Önce kontrol edip sonra yazmayın. Ekleme öncesi bir okuma, ders kitabı bir yarış koşuludur; iki eşzamanlı kayıt her ikisi de "boş" okur ve her ikisi de yazar.

Bariz yaklaşım neden yanlış

İçgüdü, e-posta için Query (ya da daha kötüsü Scan) yapmak, hiçbir şey görmemek, ardından yeni hesabı PutItem etmektir. Bu, önce-kontrol-et-sonra-eyle yarışıdır.

İki kişi aynı milisaniyede ada@lovelace.io ile kayıt oluyor. Her iki okuma da boş döner. Her iki yazma da başarılı olur. Şimdi tek bir e-posta üzerinde iki hesabınız var — ve tabloda bunu işaretleyen hiçbir şey yok.

email üzerinde bir de sizi kurtarmaz. GSI'ler nihai tutarlıdır, bu yüzden yazmanızı geçitleyen okuma, tasarım gereği bayat olabilir. Çözüm daha hızlı bir kontrol değil; yazmanın kendisini alınmış bir değere düşmeyi reddetmesini sağlamaktır.

Her kısıtı bir işaretçi item olarak modelleyin

Motor zaten bir benzersizlik kuralını bedava zorluyor: aynı anahtarla iki item yazamazsınız. Öyleyse her benzersizlik kuralını bir anahtar olarak kodlayın.

Gerçek hesap item'ının yanında, korunan her attribute için bir işaretçi item yazın. İşaretçinin partition key'i, ad alanı verilmiş (namespaced) değerin kendisidir. Değer alınmışsa, anahtar vardır ve korumalı bir put onun üzerine yazamaz.

Hem email hem username'i benzersiz tutması gereken bir kayıt için üç item birlikte hareket eder — bir single-table düzeninde anahtarlanmış (bkz. single-table tasarımı):

ItemPKSKAmaç
Hesap kaydıACCT#a1f9c3PROFILEGerçek hesap
E-posta kilidiUNIQ#EMAIL#ada@lovelace.ioLOCKE-postayı rezerve eder
Kullanıcı adı kilidiUNIQ#HANDLE#adaLOCKKullanıcı adını rezerve eder

Hesabın kendi PK'si üretilmiş bir kimliktir (ACCT#a1f9c3) — asla e-posta değil — böylece kullanıcı birincil anahtarı yeniden yazmadan e-postasını sonradan değiştirebilir. Kilit item'ları hiç profil verisi taşımaz; yalnızca anahtarları dolu olsun diye vardırlar.

Üçünü de atomik olarak yazın

TransactWriteItems en fazla 100 yazmayı tek bir hep-ya-hiç birimi olarak uygular. Her put'u attribute_not_exists(PK) ile koruyun, böylece o anahtar zaten mevcutsa başarısız olsun.

Herhangi bir koşul başarısız olursa — e-posta kilidi, kullanıcı-adı kilidi ya da hesabın kendisi — DynamoDB tüm transaction'ı geri alır ve TransactionCanceledException fırlatır. Kısmi kayıt yok, öksüz kilit yok.

{
  "TransactItems": [
    {
      "Put": {
        "TableName": "accounts",
        "Item": {
          "PK": {"S": "ACCT#a1f9c3"},
          "SK": {"S": "PROFILE"},
          "email": {"S": "ada@lovelace.io"},
          "username": {"S": "ada"}
        },
        "ConditionExpression": "attribute_not_exists(PK)"
      }
    },
    {
      "Put": {
        "TableName": "accounts",
        "Item": {
          "PK": {"S": "UNIQ#EMAIL#ada@lovelace.io"},
          "SK": {"S": "LOCK"}
        },
        "ConditionExpression": "attribute_not_exists(PK)"
      }
    },
    {
      "Put": {
        "TableName": "accounts",
        "Item": {
          "PK": {"S": "UNIQ#HANDLE#ada"},
          "SK": {"S": "LOCK"}
        },
        "ConditionExpression": "attribute_not_exists(PK)"
      }
    }
  ]
}

Koşul, mekanizmanın tamamıdır. attribute_not_exists olmadan, aynı e-posta ile ikinci bir kayıt, ilk kilidin sessizce üzerine yazar. Onunla, put reddeder, transaction iptal olur ve uygulamanız "e-posta zaten kullanımda" mesajını gösterir.

ConditionExpression'ı ve değer haritasını elle kurmak, yazım hatalarının sızdığı yerdir. DynamoDB İfade Oluşturucu her put için koşulu ve türlü Item'ı üretir, böylece doğru bir transaction'ı doğrudan SDK çağrınıza yapıştırabilirsiniz.

Hatayı okuyun, tahmin etmeyin

Transaction iptal edildiğinde, DynamoDB bir CancellationReasons dizisini konumsal olarak döndürür — istek sırasına göre item başına bir giriş. 1. yuvadaki bir ConditionalCheckFailed e-postanın alındığı anlamına gelir; 2. yuva kullanıcı adının. Yuvayı, genel bir "kayıt başarısız" yerine kesin, alan düzeyinde bir hataya geri eşleyin.

Kilitleri DynoTable'da inceleyin

İşaretçi item'ları uygulamanızın arayüzünde görünmezdir — onlar tesisattır. Bir kayıt gizemli bir şekilde başarısız olduğunda, kilidin gerçekten var olup olmadığını görmeniz gerekir.

Tabloyu DynoTable'da açın ve UNIQ# önekini Query edin. Hesap ve iki kilit item'ı bir arada durur, böylece takılı bir kayıt (bozuk bir silme işleminin geride bıraktığı bir kilit) bir bakışta bellidir.

DynoTable tabloyu tarıyor — hesap item'ları UNIQ#EMAIL ve UNIQ#HANDLE kilit item'larıyla iç içe.
DynoTable tabloyu tarıyor — hesap item'ları UNIQ#EMAIL ve UNIQ#HANDLE kilit item'larıyla iç içe.

Değişiklik ve silmede kilitleri dürüst tutun

Kilitler tek-yazımlık değildir. Canlı değeri yansıtırlar, bu yüzden yaşam döngüsünün onları senkronize tutması gerekir — korunan bir attribute'a dokunan her işlem de bir transaction'dır.

  • E-posta değiştirme. Tek bir transaction: yeni UNIQ#EMAIL#… kilidini attribute_not_exists ile put edin, eski kilidi silin, hesabı güncelleyin. Aynı hep-ya-hiç garantisi.
  • Hesap silme. Hesap item'ını ve her iki kilit item'ını tek bir transaction içinde silin, yoksa değeri sonsuza dek engelleyen bir kilit yalnız bırakırsınız.
  • Güvenli yeniden deneme. Bir ClientRequestToken geçirin, böylece (bir ağ kesintisinden sonra) yeniden gönderilen bir transaction, çift yazma değil idempotent olsun.

Tuzak, kilidi gönder-ve-unut olarak ele almaktır. Kayıtta oluşturulup hesap kaldırılırken hiç silinmeyen bir kilit, kimsenin bir daha kullanamayacağı bir değerdir — ve gerçek bir kullanıcı kendi eski kullanıcı adını talep edemeyene dek ortaya çıkmaz.

Sonraki adımlar

Benzersizlik işaretçileri bir single-table modelidir, bu yüzden diğer item'larınızın yanında doğal olarak dururlar — anahtar düzeni için single-table tasarımı'nı ve bir kilidi kontrol etmek için asla bir Scan'e uzanmamak için Query vs Scan'i okuyun. Model, ilk kez AWS'nin re:Invent / AWS Summit 2018 DAT374 — DynamoDB Transactions oturumunda adım adım anlatıldı.

Koşul korumalı put'ları DynamoDB İfade Oluşturucu ile taslaklandırın, ardından kilit item'larını kendi tablonuza karşı incelemek için DynoTable'ı deneyin.

Güncellendi