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ı
TransactWriteItemsile atomik olarak yazın. Tek bir , her putattribute_not_existsile 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ı):
| Item | PK | SK | Amaç |
|---|---|---|---|
| Hesap kaydı | ACCT#a1f9c3 | PROFILE | Gerçek hesap |
| E-posta kilidi | UNIQ#EMAIL#ada@lovelace.io | LOCK | E-postayı rezerve eder |
| Kullanıcı adı kilidi | UNIQ#HANDLE#ada | LOCK | Kullanı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.

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#…kilidiniattribute_not_existsile 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
ClientRequestTokengeç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.


