Menengah6 menit baca

Condition Expression DynamoDB: Panduan Lengkap (dengan Contoh)

Condition expression adalah predikat yang dievaluasi DynamoDB pada item yang ada sebelum ia mengomit write Anda. Jika predikatnya salah, penulisan ditolak dan tak ada yang berubah. Ia adalah hal terdekat yang dimiliki DynamoDB dengan sebuah klausa WHERE pada sebuah penulisan — dan satu-satunya cara aman untuk menegakkan sebuah invarian.

Bagaimana condition expression DynamoDB bekerja?

Sebuah condition expression adalah sebuah predikat yang dievaluasi DynamoDB di sisi server terhadap item saat ini sebelum mengomit sebuah penulisan. Jika ia benar, penulisan berlanjut; jika salah, penulisan ditolak dengan ConditionalCheckFailedException dan tak ada yang berubah. Ia melipat pemeriksaan dan mutasi ke dalam satu operasi atomik, sehingga pemanggil yang bersamaan tak bisa berlomba dengan sebuah pembacaan basi.

  • Ia adalah penjaga, bukan filter. ConditionExpression berjalan di sisi server pada item saat ini; hasil salah menggagalkan penulisan dengan ConditionalCheckFailedException.
  • Ia menggantikan baca-lalu-tulis. Tanpa perjalanan bolak-balik SELECT lalu UPDATE — pemeriksaan dan mutasi adalah satu operasi atomik, sehingga dua pemanggil tak bisa berlomba.
  • Ia gratis untuk menolak, tak gratis untuk berjalan. Sebuah penulisan bersyarat yang gagal tetap menghabiskan write capacity. Sebuah penulisan yang ditolak menagih WCU untuk ukuran item yang ada yang diperiksa terhadapnya (minimum 1) — sebuah create-if-absent yang gagal berbiaya 1 WCU.

Datang dari SQL, Anda akan membaca baris itu, memeriksanya di kode aplikasi, lalu memperbarui. Di DynamoDB, celah antara pembacaan dan penulisan itu adalah sebuah bug perusakan data yang menunggu seorang pemanggil yang bersamaan. Condition expression menutup celah itu.

Di mana ia berlaku

Anda melampirkan sebuah ConditionExpression ke PutItem, UpdateItem, DeleteItem, dan setiap aksi di dalam TransactWriteItems. Ia bukan bagian dari Query atau Scan — itu menggunakan FilterExpression, yang adalah hal berbeda pada jalur pembacaan.

Perbedaan itu menjegal orang, jadi jadilah tepat:

ConditionExpressionFilterExpression
JalurPenulisan (Put/Update/Delete)Pembacaan (Query/Scan)
Efek saat gagalMenolak seluruh penulisanMembuang item dari hasil
MelihatItem saat ini, pra-penulisanSetiap item kandidat, pasca-pembacaan
BiayaPenulisan yang gagal tetap ditagihItem yang difilter tetap ditagih untuk pembacaan

Keduanya berjalan di sisi server. Perbedaannya adalah apa yang "salah" lakukan: sebuah kondisi membatalkan sebuah mutasi; sebuah filter hanya menyembunyikan sebuah baris yang sudah Anda bayar untuk dibaca. (AWS: Ekspresi kondisi)

Fungsi yang akan benar-benar Anda gunakan

Bahasa kondisinya kecil. Para pekerja kerasnya:

  • attribute_exists(path) / attribute_not_exists(path) — apakah 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 — pemeriksaan tipe dan string/set.
  • BETWEEN … AND …, IN (…) — rentang dan keanggotaan.
  • AND, OR, NOT, tanda kurung — untuk menggabungkan yang di atas.

attribute_not_exists pada adalah cara kanonis untuk membuat PutItem berperilaku seperti sebuah insert yang tak akan menimpa sebuah item yang ada — DynamoDB tak punya operasi "insert" terpisah, jadi kondisi adalah semantik insert-nya. (AWS: Referensi operator perbandingan dan fungsi)

Sebuah contoh yang dikerjakan: menjaga sebuah buku besar dari overdraft

Ambil sebuah buku besar perbankan. Setiap akun adalah satu item:

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

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

Cara yang salah (jebakannya)

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

Di antara GetItem dan UpdateItem, sebuah debit kedua bisa membaca 50000 yang sama, lolos pemeriksaannya sendiri, dan menulis juga. Keduanya berhasil; akunnya menjadi negatif. Ini adalah sebuah lomba baca-modifikasi-tulis, dan tak ada validasi sisi-aplikasi seberapa pun yang memperbaikinya — pemeriksaan dan penulisan adalah operasi terpisah.

Cara yang benar

Lipat pemeriksaan ke dalam penulisan. Debit 30000 sen, bersyarat 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 saldonya tak tersentuh. Debit yang bersamaan entah melihat saldo asli dan diperiksa terhadapnya, atau melihat yang sudah diperbarui — tak pernah sebuah pembacaan basi yang ia beraksi atasnya.

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

Coba di sini juga — builder ini di-preset ke sebuah PutItem yang dijaga (attribute_not_exists) sehingga Anda bisa membaca ConditionExpression yang dihasilkan:

Bangun request Anda
Kode yang dihasilkan
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"
  }
})

Memeriksa penjaga di DynoTable

Saat sebuah penulisan bersyarat gagal, Anda ingin melihat keadaan nyata item-nya, bukan menebaknya. Tarik item akunnya dan baca clearedCents secara langsung.

Koleksi buku besar di DynoTable — item BALANCE menampilkan clearedCents di atas item-item transaksi akun.
Koleksi buku besar di DynoTable — item BALANCE menampilkan clearedCents di atas item-item transaksi akun.

Baca penolakannya, jangan mencoba ulang secara buta

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

Dua hal membuat kegagalan bisa di-debug:

  • ReturnValuesOnConditionCheckFailure: ALL_OLD — DynamoDB mengembalikan item saat ini bersama kegagalannya, sehingga Anda bisa menampilkan "saldo tadinya 20000, Anda meminta 30000" tanpa sebuah pembacaan kedua. (AWS: Bekerja dengan item)
  • Membedakan dua alasan kegagalan. attribute_exists(PK) AND clearedCents >= :amt meruntuhkan "tak ada akun" dan "tak ada dana" ke dalam satu eksepsi. Jika pemanggil perlu membedakan keduanya, pisahkan menjadi dua penulisan atau periksa item yang dikembalikan.

Optimistic locking adalah trik yang sama

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

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

Jika seorang penulis lain bergerak lebih dulu, version = :seen salah, penulisan ditolak, dan Anda membaca ulang serta mencoba lagi. Beginilah cara DynamoDB melakukan kontrol konkurensi tanpa kunci — tegaskan apa yang Anda lihat, gagal jika ia bergerak. (AWS: Optimistic Locking with Version Number) Staging area DynoTable menjalankan pola ini untuk Anda — sebuah suntingan bersamaan muncul sebagai konflik untuk diselesaikan, bukan penulisan yang hilang.

Jebakan dan langkah selanjutnya

  • Nama yang bertabrakan dengan kata cadangan. status, size, name, dan ~570 lainnya adalah cadangan. Beri alias dengan ExpressionAttributeNames (#s = status) atau permintaannya ditolak dengan sebuah ValidationException ('Attribute name is a reserved keyword'). Pemeriksa reserved word menerima nama atribut Anda dan mengembalikan map alias siap tempel.
  • Sebuah kondisi tak bisa mereferensikan item lain. Ia hanya melihat item yang ditulis. Invarian lintas-item membutuhkan TransactWriteItems dengan sebuah ConditionExpression per-aksi, atau sebuah ConditionCheck terhadap sebuah item sentinel.
  • Penulisan yang gagal tetap berbiaya WCU. Sebuah penjaga yang menolak 90% dari waktunya tetap menagih untuk penolakan itu. Asuransi murah, tapi tak gratis.

Untuk memodelkan key yang dijalankan oleh penjaga ini, lihat single-table design dan Query vs Scan. Saat Anda siap mengeluarkan penulisan bersyarat terhadap data nyata, unduh DynoTable dan jalankan ia terhadap tabel Anda sendiri.

Diperbarui