DynamoDB Condition Expression'ları
Bir condition expression, DynamoDB'nin yazmanı işlemeden önce mevcut öğe üzerinde
değerlendirdiği bir yüklemdir (predicate). Yüklem yanlışsa, yazma reddedilir ve
hiçbir şey değişmez. DynamoDB'nin bir yazma üzerindeki bir WHERE cümlesine en yakın
şeyidir — ve bir değişmezi (invariant) zorlamanın tek güvenli yoludur.
DynamoDB condition expression'ları nasıl çalışır?
Bir condition expression, DynamoDB'nin bir yazmayı işlemeden önce mevcut öğeye karşı sunucu tarafında değerlendirdiği bir yüklemdir (predicate). Doğruysa yazma devam eder; yanlışsa yazma ConditionalCheckFailedException ile reddedilir ve hiçbir şey değişmez. Kontrolü ve mutasyonu tek bir atomik işleme katlar, böylece eşzamanlı çağıranlar eski bir okuma üzerinden yarışamaz.
- Bu bir koruyucudur, bir filtre değil.
ConditionExpression, mevcut öğe üzerinde sunucu tarafında çalışır; yanlış bir sonuç yazmayıConditionalCheckFailedExceptionile başarısız kılar. - Oku-sonra-yaz'ın yerini alır. Bir
SELECTsonraUPDATEturu yok — kontrol ve mutasyon tek bir atomik işlemdir, bu yüzden iki çağıran (caller) yarışamaz. - Reddetmesi bedava, çalışması değil. Başarısız bir koşullu yazma yine de yazma kapasitesi tüketir. Garanti, engellediği yazmayla aynıya mal olur.
SQL'den gelince, satırı okur, onu uygulama kodunda kontrol eder, sonra güncellerdin. DynamoDB'de okuma ile yazma arasındaki o boşluk, eşzamanlı bir çağıranı bekleyen bir veri-bozulması hatasıdır. Condition expression boşluğu kapatır.
Nerede geçerlidir
Bir ConditionExpression'ı PutItem, UpdateItem, DeleteItem ve
TransactWriteItems içindeki her eyleme eklersin. Query veya Scan'in bir parçası
değildir — bunlar okuma yolunda farklı bir şey olan FilterExpression'ı kullanır.
O ayrım insanları yanıltır, bu yüzden net ol:
ConditionExpression | FilterExpression | |
|---|---|---|
| Yol | Yazmalar (Put/Update/Delete) | Okumalar (Query/Scan) |
| Başarısızlıkta etkisi | Tüm yazmayı reddeder | Öğeyi sonuçlardan düşürür |
| Gördüğü | Mevcut öğe, yazma-öncesi | Her aday öğe, okuma-sonrası |
| Maliyet | Başarısız yazma yine faturalanır | Filtrelenen öğeler okuma için yine faturalanır |
İkisi de sunucu tarafında çalışır. Fark, "yanlış"ın ne yaptığıdır: bir koşul bir mutasyonu iptal eder; bir filtre, okumak için zaten ödediğin bir satırı sadece gizler. (AWS: Condition Expressions)
Gerçekten kullanacağın fonksiyonlar
Koşul dili küçüktür. İş atları:
attribute_exists(path)/attribute_not_exists(path)— bu attribute öğede var mı? "Yalnızca yoksa oluştur" / "yalnızca varsa güncelle" için klasik deyim.- Karşılaştırıcılar —
=,<>,<,<=,>,>=— bir değere veya başka bir attribute'a karşı. attribute_type,begins_with,contains,size— tür ve string/set kontrolleri.BETWEEN … AND …,IN (…)— aralık ve üyelik.AND,OR,NOT, parantezler — yukarıdakileri birleştirmek için.
Partition key üzerindeki attribute_not_exists, PutItem'ın mevcut bir öğeyi ezmeyen
bir insert gibi davranmasını sağlamanın kanonik yoludur — DynamoDB'nin ayrı bir
"insert" işlemi yoktur, bu yüzden koşul zaten insert semantiğidir.
(AWS: Comparison Operator and Function Reference)
İşlenmiş bir örnek: bir defteri borca düşmeye karşı koruma
Bir bankacılık defteri (ledger) al. Her hesap tek bir öğedir:
PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents = 0Değişmez: bir borçlandırma (debit) kullanılabilir bakiyeyi asla sıfırın altına itmemeli ve var olmayan bir hesabı asla borçlandırmamalısın. İki kural, ikisi de yazmanın içinde zorlanabilir.
Yanlış yol (ayak tuzağı)
GetItem ACCT#a7f3 / BALANCE → clearedCents = 50000
if (50000 >= 30000) ... ← uygulama tarafı kontrol
UpdateItem SET clearedCents = 20000
GetItem ile UpdateItem arasında, ikinci bir borçlandırma aynı 50000'i okuyabilir,
kendi kontrolünü geçebilir ve o da yazabilir. İkisi de başarılı olur; hesap negatife
düşer. Bu bir oku-değiştir-yaz yarışıdır ve hiçbir miktar uygulama tarafı doğrulama
bunu düzeltmez — kontrol ve yazma ayrı işlemlerdir.
Doğru yol
Kontrolü yazmaya katla. Hesabın var olmasına ve yeterli tutmasına koşullu olarak 30000 cent borçlandır:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = clearedCents - :amt
ConditionExpression:
attribute_exists(PK) AND clearedCents >= :amt:amt = 30000 ile. Bakiye çok düşükse ya da öğe hiç oluşturulmamışsa, DynamoDB yazmayı
ConditionalCheckFailedException ile reddeder ve bakiye dokunulmamış kalır. Eşzamanlı
borçlandırma ya orijinal bakiyeyi görür ve ona karşı kontrol edilir ya da güncellenmiş
olanı görür — asla üzerine işlem yaptığı eski bir okumayı görmez.
Tam ifadeyi — adlar, değerler, hepsi — ExpressionAttributeValues eşlemesini elle
birleştirmek yerine
DynamoDB expression builder ile oluşturup
kopyalayabilirsin.
Koruyucuyu DynoTable'da inceleme
Bir koşullu yazma başarısız olduğunda, öğenin gerçek durumunu tahmin etmek yerine
görmek istersin. Hesap öğesini çek ve clearedCents'i doğrudan oku.

Reddi oku, körlemesine yeniden deneme
ConditionalCheckFailedException geçici bir hata değildir — aynı yazmayı yeniden
denemek hiçbir şeyi değiştirmez. Bir iş kuralının ateşlendiği anlamına gelir: yetersiz
bakiye, çift oluşturma, eski versiyon. Onu bir altyapı arızası olarak değil, bir alan
sonucu olarak yüzeye çıkar.
İki şey başarısızlıkları ayıklanabilir yapar:
ReturnValuesOnConditionCheckFailure: ALL_OLD— DynamoDB başarısızlığın yanında mevcut öğeyi döndürür, böylece ikinci bir okuma olmadan "bakiye 20000'di, 30000 istedin" gösterebilirsin. (AWS: Working with Items)- İki başarısızlık nedenini ayırt etme.
attribute_exists(PK) AND clearedCents >= :amt, "hesap yok" ve "para yok"u tek bir istisnaya çökertir. Çağıranların onları ayırt etmesi gerekiyorsa, iki yazmaya böl veya döndürülen öğeyi incele.
İyimser kilitleme (optimistic locking) aynı püf noktasıdır
Versiyon-numarası deseni, farklı bir şapka takan bir condition expression'dan başka
bir şey değildir. Bir version attribute'u sakla; her yazma okuduğun versiyonu öne
sürer ve onu artırır:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = :new, version = :next
ConditionExpression: version = :seenBaşka bir yazıcı önce hareket ettiyse, version = :seen yanlıştır, yazma reddedilir ve
yeniden okur ve yeniden denersin. DynamoDB eşzamanlılık denetimini kilitler olmadan
böyle yapar — gördüğünü öne sür, hareket ettiyse başarısız ol. (AWS: Optimistic
Locking with Version Number)
Tuzaklar ve sonraki adımlar
- Rezerve kelimelerle çakışan adlar.
status,size,nameve ~570 tane daha rezervedir. OnlarıExpressionAttributeNamesile takma ad ver (#s = status) ya da ifaden sessizce ayrıştırılamaz. - Bir koşul başka bir öğeye referans veremez. Yalnızca yazılan öğeyi görür.
Öğeler arası değişmezler, eylem başına bir
ConditionExpressionileTransactWriteItemsya da bir sentinel öğeye karşı birConditionCheckgerektirir. - Başarısız yazmalar yine WCU'ya mal olur. Zamanın %90'ında reddeden bir koruyucu yine de o redler için fatura keser. Ucuz sigorta, ama bedava değil.
Bu koruyucuların üzerinde çalıştığı anahtarları modellemek için, bkz. single-table tasarım ve Query ve Scan. Gerçek veriye karşı koşullu yazmalar yapmaya hazır olduğunda, DynoTable'ı indir ve onları kendi tablolarına karşı çalıştır.


