Orta5 dakikalık okuma

DynamoDB Condition Expression: Eksiksiz Rehber (Örneklerle)

Bir koşul ifadesi, DynamoDB'nin yazmanı işlemeden önce mevcut öğe üzerinde değerlendirdiği bir yüklemdir. Yüklem yanlışsa yazma reddedilir ve hiçbir şey değişmez. Bu, DynamoDB'nin bir yazma üzerinde bir WHERE cümlesine en yakın sahip olduğu şeydir — ve bir değişmezi uygulamanın tek güvenli yoludur.

DynamoDB koşul ifadeleri nasıl çalışır?

Bir koşul ifadesi, DynamoDB'nin bir yazmayı işlemeden önce mevcut öğeye karşı sunucu tarafında değerlendirdiği bir yüklemdir. 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şlemde birleştirir, böylece eş zamanlı çağıranlar eski bir okumada yarışamaz.

  • Bu bir koruma, bir filtre değil. ConditionExpression, mevcut öğe üzerinde sunucu tarafında çalışır; yanlış bir sonuç yazmayı ConditionalCheckFailedException ile başarısız kılar.
  • Oku-sonra-yaz'ın yerini alır. SELECT sonra UPDATE gidiş-dönüşü yok — kontrol ve mutasyon tek bir atomik işlemdir, böylece iki çağıran yarışamaz.
  • Reddetmesi bedava, çalışması değil. Başarısız bir koşullu yazma yine de yazma kapasitesi tüketir. Reddedilen bir yazma, kontrol edildiği mevcut öğenin boyutu için WCU faturalandırır (minimum 1) — başarısız bir yoksa-oluştur 1 WCU tutar.

SQL'den geliyorsan, satırı okur, uygulama kodunda kontrol eder, sonra güncellersin. DynamoDB'de okuma ile yazma arasındaki o boşluk, eş zamanlı bir çağıranı bekleyen bir veri bozulması hatasıdır. Koşul ifadesi bu boşluğu kapatır.

Nerede geçerli olurlar

Bir ConditionExpressionPutItem, UpdateItem, DeleteItem'a ve TransactWriteItems içindeki her eyleme iliştirirsin. Query veya Scan'in parçası değildir — onlar FilterExpression kullanır ki bu, okuma yolunda farklı bir şeydir.

Bu ayrım insanların ayağını kaydırır, o yüzden hassas ol:

ConditionExpressionFilterExpression
YolYazmalar (Put/Update/Delete)Okumalar (Query/Scan)
Başarısızlıkta etkiTüm yazmayı reddederÖğeyi sonuçlardan düşürür
GördüğüMevcut öğe, yazma öncesiHer aday öğe, okuma sonrası
MaliyetBaşarısız yazma yine faturalanırFiltrelenen öğ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 ise okumak için zaten ödediğin bir satırı sadece gizler. (AWS: Koşul ifadeleri)

Gerçekten kullanacağın fonksiyonlar

Koşul dili küçüktür. İş atları:

  • attribute_exists(path) / attribute_not_exists(path) — bu öğ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 özniteliğe karşı.
  • attribute_type, begins_with, contains, size — tür ve string/küme kontrolleri.
  • BETWEEN … AND …, IN (…) — aralık ve üyelik.
  • AND, OR, NOT, parantezler — yukarıdakileri birleştirmek için.

üzerinde attribute_not_exists, PutItem'ı mevcut bir öğenin üzerine yazmayacak bir insert gibi davrandırmanın kanonik yoludur — DynamoDB'nin ayrı bir "insert" işlemi yoktur, dolayısıyla koşul insert anlambilimidir. (AWS: Karşılaştırma operatörü ve fonksiyon referansı)

İşlenmiş bir örnek: bir defterin eksiye düşmesini önleme

Bir banka defteri düşün. Her hesap tek bir öğedir:

PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents    = 0

bir çekim, kullanılabilir bakiyeyi asla sıfırın altına itmemeli ve mevcut olmayan bir hesaptan asla çekim yapmamalısın. İki kural, ikisi de yazmanın kendisinde uygulanabilir.

Yanlış yol (tuzak)

GetItem ACCT#a7f3 / BALANCE     → clearedCents = 50000
if (50000 >= 30000) ...         ← app-side check
UpdateItem  SET clearedCents = 20000

GetItem ile UpdateItem arasında, ikinci bir çekim aynı 50000'i okuyabilir, kendi kontrolünü geçebilir ve o da yazabilir. İkisi de başarılı olur; hesap eksiye düşer. Bu bir oku-değiştir-yaz yarışıdır ve hiçbir miktar uygulama tarafı doğrulaması onu düzeltmez — kontrol ve yazma ayrı işlemlerdir.

Doğru yol

Kontrolü yazmanın içine katla. 30000 sent çek, hesabın var olmasına ve yeterli tutmasına koşullu olarak:

UpdateItem  ACCT#a7f3 / BALANCE
  SET clearedCents = clearedCents - :amt
  ConditionExpression:
    attribute_exists(PK) AND clearedCents >= :amt

:amt = 30000 ile. Bakiye çok düşükse veya öğe hiç oluşturulmadıysa, DynamoDB yazmayı ConditionalCheckFailedException ile reddeder ve bakiyeye dokunulmaz. Eş zamanlı çekim ya orijinal bakiyeyi görür ve ona karşı kontrol edilir ya da güncellenmişini görür — üzerine iş yaptığı eski bir okumayı asla görmez.

Tam ifadeyi — adlar, değerler ve her şey dahil — ExpressionAttributeValues haritasını elle birleştirmek yerine DynamoDB expression builder ile oluşturup kopyalayabilirsin.

Hemen burada dene — bu builder, korumalı bir PutItem (attribute_not_exists) için önceden ayarlıdır, böylece üretilen ConditionExpression'ı okuyabilirsin:

İsteğini oluştur
Oluşturulan kod
new PutItemCommand({
  "TableName": "AuditLog",
  "Item": {
    "pk": {
      "S": "TENANT#acme"
    },
    "sk": {
      "S": "EVENT#2026-06-24T10:00:00Z"
    },
    "action": {
      "S": "login"
    }
  },
  "ConditionExpression": "attribute_not_exists(#cond0)",
  "ExpressionAttributeNames": {
    "#cond0": "pk"
  }
})

DynoTable'da korumayı inceleme

Bir koşullu yazma başarısız olduğunda, öğenin gerçek durumunu tahmin etmek değil, görmek istersin. Hesap öğesini çek ve clearedCents'i doğrudan oku.

DynoTable'daki defter koleksiyonu — BALANCE öğesi, clearedCents'i hesabın işlem öğelerinin üstünde gösterir.
DynoTable'daki defter koleksiyonu — BALANCE öğesi, clearedCents'i hesabın işlem öğelerinin üstünde gösterir.

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 tetiklendiği anlamına gelir: yetersiz bakiye, mükerrer oluşturma, eski sürüm. Onu bir altyapı arızası olarak değil, bir alan sonucu olarak yüzeye çıkar.

İki şey başarısızlıkları hata ayıklanabilir kılar:

  • ReturnValuesOnConditionCheckFailure: ALL_OLD — DynamoDB, başarısızlıkla birlikte mevcut öğeyi döndürür, böylece ikinci bir okuma olmadan "bakiye 20000 idi, sen 30000 istedin" gösterebilirsin. (AWS: Öğelerle çalışma)
  • İki başarısızlık nedenini ayırt etme. attribute_exists(PK) AND clearedCents >= :amt, "hesap yok" ile "bakiye yok"u tek bir istisnaya çöker. Çağıranların bunları ayırması gerekiyorsa, iki yazmaya böl veya döndürülen öğeyi incele.

İyimser kilitleme aynı numaradır

Sürüm-numarası deseni, farklı bir şapka takmış bir koşul ifadesinden başka bir şey değildir. Bir version özniteliği sakla; her yazma, okuduğun sürümü ileri sürer ve onu artırır:

UpdateItem  ACCT#a7f3 / BALANCE
  SET clearedCents = :new, version = :next
  ConditionExpression: version = :seen

Başka bir yazan önce hamle yaptıysa, version = :seen yanlıştır, yazma reddedilir ve yeniden okuyup yeniden denersin. DynamoDB, kilit olmadan eş zamanlılık kontrolünü böyle yapar — gördüğünü ileri sür, değiştiyse başarısız ol. (AWS: Optimistic Locking with Version Number) DynoTable'ın hazırlama alanı bu deseni senin için çalıştırır — eş zamanlı bir düzenleme, kaybolan bir yazma olarak değil, çözülecek bir çakışma olarak yüzeye çıkar.

Tuzaklar ve sonraki adımlar

  • Ayrılmış sözcüklerle çakışan adlar. status, size, name ve ~570 başkası ayrılmıştır. Bunları ExpressionAttributeNames ile takma adlandır (#s = status), yoksa istek bir ValidationException ('Attribute name is a reserved keyword') ile reddedilir. Ayrılmış sözcükler denetleyicisi, öznitelik adlarını alır ve takma ad haritasını yapıştırmaya hazır geri verir.
  • Bir koşul başka bir öğeye başvuramaz. Yalnızca yazılan öğeyi görür. Öğeler-arası değişmezler, eylem başına bir ConditionExpression içeren TransactWriteItems'a ya da bir sentinel öğeye karşı bir ConditionCheck'e ihtiyaç duyar.
  • Başarısız yazmalar yine WCU tutar. Zamanın %90'ında reddeden bir koruma yine o reddedilenler için faturalandırır. Ucuz sigorta, ama bedava değil.

Bu korumaların üzerinde çalıştığı anahtarları modellemek için bkz. tek tablo tasarımı ve Query vs Scan. Gerçek veriye karşı koşullu yazmalar yapmaya hazır olduğunda, DynoTable'ı indir ve bunları kendi tablolarına karşı çalıştır.

Güncellendi