DynamoDB'de birden fazla nitelikte benzersizliği uygulama
DynamoDB tam olarak tek bir şey için benzersizlik garanti eder: birincil anahtar.
UNIQUE (email) kısıtı yok, UNIQUE (username) yok ve iki niteliği kapsayan
hiçbir şey yok. SQL'den geliyorsanız, bu yokluk ilk sürprizdir — ve insanların
sessizce bir yarış durumu yayınladığı ilk yerdir.
DynamoDB'de birden fazla nitelik üzerinde benzersizlik kısıtı nasıl uygulanır?
DynamoDB'nin dışında UNIQUE kısıtı yoktur; bu yüzden benzersizliği kendiniz uygularsınız: korunan her değeri, anahtarı o değer olan kendi işaretçi (marker) öğesi olarak modelleyin, ardından kaydı ve tüm işaretçileri tek bir TransactWriteItems içinde, her put'u attribute_not_exists ile koruyarak yazın. Motorun zaten uyguladığı çakışma sizin kısıtınız olur.
- Benzersizlik kısıtı yoktur — yalnızca birincil anahtar motor tarafından benzersiz uygulanır. Diğer her "benzersiz olmalı" nitelik sizin işinizdir.
- Her benzersizlik kuralını kendi öğesi olarak modelleyin. Anahtarı korumakta olduğunuz değer olan özel bir işaretçi (marker) öğesi, "bu e-posta alınmış mı?" sorusunu motorun zaten uyguladığı bir anahtar çakışmasına çevirir.
- Onları
TransactWriteItemsile atomik yazın. Tek bir işlem, her putattribute_not_existsile korunmuş, böylece tüm işaretçiler ve gerçek kayıt ya birlikte işlenir ya da hiçbiri. - Kontrol-et-sonra-yaz yapmayın. Ekleme-öncesi okuma, ders kitabı bir yarıştır; iki eşzamanlı kayıt, ikisi de "boş" okur ve 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, sonra yeni hesabı PutItem etmektir. Bu bir kontrol-et-sonra-davran
yarışıdır.
İki kişi aynı milisaniyede ada@lovelace.io'yu kaydeder. İki okuma da boş döner.
İki yazma da başarılı olur. Artık bir e-postada iki hesabınız var — ve tabloda
bunu işaretleyen hiçbir şey yok.
email üzerinde bir GSI de sizi kurtarmaz. GSI'ler nihai
tutarlıdır, bu yüzden yazmanızı kapılayan (gate) okuma,
tasarımı gereği bayat olabilir. Çözüm daha hızlı bir kontrol değil; yazmanın
kendisini alınmış bir değere inmeyi reddettirmektir.
Her kısıtı bir işaretçi öğesi olarak modelleyin
Motor zaten bir benzersizlik kuralını bedava uygular: aynı anahtarla iki öğe yazamazsınız. Bu yüzden her benzersizlik kuralını bir anahtar olarak kodlayın.
Gerçek hesap öğesinin yanında, korunan nitelik başına bir işaretçi öğesi yazın. İşaretçinin bölüm anahtarı, ad alanlandırılmış 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, üç öğe
birlikte hareket eder — tek tablo düzeninde anahtarlanmış (bkz. tek tablo
tasarımı):
| Öğe | 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 öğeleri profil verisi taşımaz; yalnızca anahtarları dolu
olsun diye vardırlar.
Üçünü de atomik 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 varsa 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 işlemi 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, tüm mekanizmadır. attribute_not_exists olmadan, aynı e-postayla ikinci bir
kayıt sessizce ilk kilidin üzerine yazar. Onunla, put reddeder, işlem iptal olur ve
uygulamanız "e-posta zaten kullanımda" yüzeye çıkarır.
ConditionExpression'ı ve değer map'ini elle oluşturmak, yazım hatalarının sızdığı
yerdir. DynamoDB Expression Builder, her put
için koşulu ve türlü Item'ı üretir, böylece doğru bir işlemi doğrudan SDK
çağrınıza yapıştırabilirsiniz.
Başarısızlığı okuyun, tahmin etmeyin
İşlem iptal edildiğinde, DynamoDB bir CancellationReasons dizisini konumsal olarak
döndürür — istek sırasında, öğe başına bir giriş. Slot 1'deki bir
ConditionalCheckFailed, e-postanın alındığı anlamına gelir; slot 2, kullanıcı
adının. Genel bir "kayıt başarısız" yerine slotu kesin, alan düzeyinde bir hataya
geri eşleyin.
Kilitleri DynoTable'da inceleyin
İşaretçi öğeleri uygulamanızın arayüzünde görünmezdir — onlar tesisattır. Bir kayıt gizemli biçimde 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 onun iki kilit
öğesi birlikte oturur, böylece sıkışmış bir kayıt (bozuk bir silme tarafından geride
bırakılan bir kilit) bir bakışta belli olur.

Değişiklik ve silmede kilitleri dürüst tutun
Kilitler bir kez yaz değildir. Canlı değeri yansıtırlar, bu yüzden yaşam döngüsü onları senkronda tutmalıdır — korunan bir niteliğe dokunan her işlem de bir işlemdir.
- E-postayı değiştirme. Tek işlem: yeni
UNIQ#EMAIL#…kilidiniattribute_not_existsile put edin, eski kilidi silin, hesabı güncelleyin. Aynı hep-ya-hiç garantisi. - Hesabı silme. Hesap öğesini ve her iki kilit öğesini tek bir işlemde silin, yoksa değeri sonsuza dek engelleyen bir kilidi öksüz bırakırsınız.
- Güvenli yeniden deneme. Bir
ClientRequestTokengeçirin, böylece yeniden gönderilen bir işlem (bir ağ kesintisinden sonra) bir çift yazma değil, idempotent olsun.
Tuzak, kiliti gönder-ve-unut olarak ele almaktır. Kayıtta oluşturulan ama hesap kaldırılırken hiç silinmeyen bir kilit, kimsenin asla yeniden kullanamayacağı bir değerdir — ve gerçek bir kullanıcı kendi eski kullanıcı adını talep edemeyene kadar ortaya çıkmaz.
Sonraki adımlar
Benzersizlik işaretçileri bir tek tablo desenidir, bu yüzden diğer öğelerinizin
yanında doğal olarak otururlar — anahtar düzeni için tek tablo
tasarımı'nı ve bir kiliti kontrol etmek için asla bir
Scan'e uzanmamanız için Query vs Scan'i okuyun. Desen ilk
olarak AWS'nin re:Invent / AWS Summit 2018 DAT374 — DynamoDB Transactions
oturumunda adım adım anlatıldı.
Koşul korumalı put'ları DynamoDB Expression Builder ile taslaklayın, ardından kilit öğelerini kendi tablonuza karşı incelemek için DynoTable'ı deneyin.


