Menengah5 menit baca

Condition Expression DynamoDB

Sebuah condition expression adalah predikat yang dievaluasi DynamoDB pada Item yang ada sebelum ia mengcommit penulisan Anda. Jika predikatnya false, penulisan ditolak dan tak ada yang berubah. Ia adalah hal terdekat yang DynamoDB punya dengan klausa WHERE pada sebuah penulisan — dan satu-satunya cara aman menegakkan sebuah invarian.

Bagaimana cara kerja condition expression DynamoDB?

Sebuah condition expression adalah predikat yang dievaluasi DynamoDB di sisi server terhadap Item saat ini sebelum mengcommit sebuah penulisan. Jika ia true, penulisan dilanjutkan; jika false, penulisan ditolak dengan ConditionalCheckFailedException dan tak ada yang berubah. Ia melipat pengecekan dan mutasi menjadi satu operasi atomik, sehingga caller bersamaan tak bisa berlomba dengan pembacaan basi.

  • Ia adalah guard, bukan filter. ConditionExpression berjalan di sisi server pada Item saat ini; hasil false menggagalkan penulisan dengan ConditionalCheckFailedException.
  • Ia menggantikan read-then-write. Tanpa round trip SELECT lalu UPDATE — pengecekan dan mutasi adalah satu operasi atomik, jadi dua caller tak bisa berlomba.
  • Gratis untuk menolak, tidak gratis untuk berjalan. Conditional write yang gagal tetap mengonsumsi write capacity. Jaminannya berharga sama dengan penulisan yang diblokirnya.

Datang dari SQL, Anda akan membaca barisnya, memeriksanya di kode aplikasi, lalu memperbarui. Di DynamoDB celah antara baca dan tulis itu adalah bug korupsi-data yang menunggu caller bersamaan. Condition expression menutup celahnya.

Di mana mereka berlaku

Anda melampirkan ConditionExpression ke PutItem, UpdateItem, DeleteItem, dan setiap aksi di dalam TransactWriteItems. Ia bukan bagian dari Query atau Scan — keduanya menggunakan FilterExpression, hal berbeda di jalur baca.

Perbedaan itu sering membingungkan orang, jadi tepatlah:

ConditionExpressionFilterExpression
JalurPenulisan (Put/Update/Delete)Pembacaan (Query/Scan)
Efek saat gagalMenolak seluruh penulisanMenjatuhkan Item dari hasil
MelihatItem saat ini, pra-penulisanTiap Item kandidat, pasca-pembacaan
BiayaPenulisan gagal tetap ditagihItem terfilter tetap ditagih untuk pembacaan

Keduanya berjalan di sisi server. Bedanya adalah apa yang dilakukan "false": sebuah kondisi membatalkan mutasi; sebuah filter sekadar menyembunyikan baris yang sudah Anda bayar untuk dibaca. (AWS: Condition Expressions)

Fungsi yang benar-benar akan Anda pakai

Bahasa kondisinya kecil. Para pekerja kerasnya:

  • attribute_exists(path) / attribute_not_exists(path) — apakah atribut ini ada pada Item? Idiom klasik untuk "buat hanya jika absen" / "perbarui hanya jika ada".
  • Komparator — =, <>, <, <=, >, >= — terhadap sebuah nilai atau atribut lain.
  • attribute_type, begins_with, contains, size — pengecekan tipe dan string/set.
  • BETWEEN … AND …, IN (…) — rentang dan keanggotaan.
  • AND, OR, NOT, tanda kurung — untuk menggabungkan yang di atas.

attribute_not_exists pada partition key adalah cara kanonis membuat PutItem berperilaku seperti insert yang takkan menimpa Item yang ada — DynamoDB tak punya op "insert" terpisah, jadi kondisinya adalah semantik insert-nya. (AWS: Comparison Operator and Function Reference)

Contoh nyata: menjaga sebuah ledger dari overdraft

Ambil sebuah ledger bank. Setiap akun adalah satu Item:

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

Invariannya: sebuah debit tak boleh pernah mendorong saldo tersedia di bawah nol, dan Anda tak boleh pernah mendebit akun yang tak ada. Dua aturan, keduanya dapat ditegakkan di dalam penulisan itu sendiri.

Cara yang salah (footgun-nya)

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

Di antara GetItem dan UpdateItem, debit kedua bisa membaca 50000 yang sama, lolos pengecekannya sendiri, dan ikut menulis. Keduanya berhasil; akun menjadi negatif. Ini adalah read-modify-write race, dan validasi sisi-app sebanyak apa pun tak memperbaikinya — pengecekan dan penulisan adalah operasi terpisah.

Cara yang benar

Lipat pengecekan ke dalam penulisan. Debit 30000 sen, kondisional pada akun yang ada dan memegang cukup:

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

dengan :amt = 30000. Jika saldonya terlalu rendah, atau Item-nya tak pernah dibuat, DynamoDB menolak penulisan dengan ConditionalCheckFailedException dan saldo tak tersentuh. Debit bersamaan entah melihat saldo asli dan diperiksa terhadapnya, atau melihat yang sudah diperbarui — tak pernah pembacaan basi yang ia bertindak atasnya.

Anda bisa membangun dan menyalin expression persisnya — nama, nilai, dan semuanya — dengan DynamoDB expression builder alih-alih merakit tangan peta ExpressionAttributeValues.

Menginspeksi guard di DynoTable

Saat conditional write gagal, Anda ingin melihat keadaan nyata Item, bukan menebaknya. Tarik Item akun itu dan baca clearedCents langsung.

Koleksi ledger di DynoTable — item BALANCE menampilkan clearedCents di atas item transaksi akun.
Koleksi ledger di DynoTable — item BALANCE menampilkan clearedCents di atas item transaksi akun.

Baca penolakannya, jangan retry membabi buta

ConditionalCheckFailedException bukan error transien — meretry penulisan yang sama tak mengubah apa pun. Ia berarti sebuah aturan bisnis terpicu: dana tak cukup, create duplikat, versi basi. Munculkan ia sebagai hasil domain, bukan gangguan infra.

Dua hal membuat kegagalan dapat di-debug:

  • ReturnValuesOnConditionCheckFailure: ALL_OLD — DynamoDB mengembalikan Item saat ini berdampingan dengan kegagalan, jadi Anda bisa menampilkan "saldo 20000, Anda minta 30000" tanpa pembacaan kedua. (AWS: Working with Items)
  • Membedakan kedua alasan kegagalan. attribute_exists(PK) AND clearedCents >= :amt meruntuhkan "tak ada akun" dan "tak ada dana" menjadi satu exception. Jika caller perlu membedakannya, pecah menjadi dua penulisan atau inspeksi Item yang dikembalikan.

Optimistic locking adalah trik yang sama

Pola version-number hanyalah condition expression yang memakai topi berbeda. Simpan atribut version; setiap penulisan menegaskan versi yang Anda baca dan menaikkannya:

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

Jika penulis lain bergerak lebih dulu, version = :seen false, penulisan ditolak, dan Anda membaca ulang dan retry. Inilah cara DynamoDB melakukan concurrency control tanpa lock — tegaskan apa yang Anda lihat, gagal jika ia bergerak. (AWS: Optimistic Locking with Version Number)

Jebakan dan langkah berikutnya

  • Nama yang bentrok dengan reserved word. status, size, name, dan ~570 lainnya adalah reserved. Aliaskan mereka dengan ExpressionAttributeNames (#s = status) atau expression Anda diam-diam gagal di-parse.
  • Sebuah kondisi tak bisa mereferensikan Item lain. Ia hanya melihat Item yang sedang ditulis. Invarian lintas-Item butuh TransactWriteItems dengan ConditionExpression per-aksi, atau sebuah ConditionCheck terhadap Item sentinel.
  • Penulisan gagal tetap memakan WCU. Guard yang menolak 90% waktu tetap menagih penolakan itu. Asuransi murah, tetapi tidak gratis.

Untuk memodelkan key yang dijalani guard ini, lihat single-table design dan Query vs Scan. Saat Anda siap menerbitkan conditional write terhadap data nyata, unduh DynoTable dan jalankan mereka terhadap tabel Anda sendiri.

Diperbarui