Keunikan pada Beberapa Atribut di DynamoDB
DynamoDB menjamin keunikan untuk tepat satu hal: . Tidak ada
constraint UNIQUE (email), tidak ada UNIQUE (username), dan tidak ada apa pun yang membentang
dua atribut. Datang dari SQL, ketiadaan itu adalah kejutan pertama — dan
tempat pertama orang diam-diam mengirim sebuah race condition.
Bagaimana Anda menerapkan sebuah constraint unik pada beberapa atribut di DynamoDB?
DynamoDB tidak punya constraint UNIQUE di luar , jadi Anda menerapkan keunikan sendiri: modelkan setiap nilai yang dilindungi sebagai marker item-nya sendiri yang key-nya adalah nilai itu, lalu tulis record dan setiap marker bersama-sama dalam satu TransactWriteItems, masing-masing put ber-guard attribute_not_exists. Tabrakan yang sudah diterapkan engine menjadi constraint Anda.
- Tidak ada constraint unik — hanya primary key yang diterapkan unik oleh engine. Setiap atribut "harus unik" lainnya adalah tugas Anda.
- Modelkan setiap aturan keunikan sebagai item-nya sendiri. Sebuah marker item khusus yang key-nya adalah nilai yang Anda lindungi mengubah "apakah email ini sudah dipakai?" menjadi tabrakan key yang sudah diterapkan engine.
- Tulis secara atomik dengan
TransactWriteItems. Satu , setiap put ber-guardattribute_not_exists, sehingga semua marker dan record sungguhan commit bersama atau tidak sama sekali. - Jangan check-then-write. Sebuah read-before-insert adalah race klasik; dua signup bersamaan sama-sama membaca "kosong" dan sama-sama menulis.
Mengapa pendekatan yang jelas itu salah
Nalurinya adalah men-Query (atau lebih buruk, men-Scan) untuk email, tidak melihat apa pun, lalu
men-PutItem akun baru. Itu adalah race check-then-act.
Dua orang mendaftarkan ada@lovelace.io pada milidetik yang sama. Kedua pembacaan mengembalikan
kosong. Kedua penulisan berhasil. Kini Anda punya dua akun pada satu email — dan tidak ada apa pun
di tabel yang menandainya.
Sebuah pada email juga tidak menyelamatkan Anda. GSI bersifat
eventually consistent, jadi pembacaan yang menggerbang penulisan Anda
bisa basi secara desain. Perbaikannya bukan pemeriksaan yang lebih cepat; melainkan membuat penulisan
itu sendiri menolak mendarat pada nilai yang sudah dipakai.
Modelkan setiap constraint sebagai marker item
Engine sudah menerapkan satu aturan keunikan secara gratis: Anda tidak bisa menulis dua item dengan key yang sama. Jadi enkode setiap aturan keunikan sebagai sebuah key.
Di samping item akun sungguhan, tulis satu marker item per atribut yang dilindungi. Partition key marker adalah nilai bernamespace itu. Jika nilainya sudah dipakai, key-nya ada, dan sebuah put ber-guard tidak bisa menimpanya.
Untuk sebuah signup yang harus menjaga email dan username sama-sama unik, tiga item bergerak
bersama — ber-key dalam tata letak single-table (lihat
single-table design):
| Item | PK | SK | Tujuan |
|---|---|---|---|
| Record akun | ACCT#a1f9c3 | PROFILE | Akun sungguhan |
| Lock email | UNIQ#EMAIL#ada@lovelace.io | LOCK | Mereservasi email |
| Lock username | UNIQ#HANDLE#ada | LOCK | Mereservasi username |
PK akun itu sendiri adalah id yang dihasilkan (ACCT#a1f9c3) — jangan pernah email — sehingga
pengguna bisa mengubah email-nya nanti tanpa menulis ulang primary key. Item lock
tidak membawa data profil; mereka ada hanya agar key-nya terisi.
Tulis ketiganya secara atomik
TransactWriteItems
menerapkan hingga 100 penulisan sebagai satu unit all-or-nothing. Beri guard setiap put dengan
attribute_not_exists(PK) sehingga gagal jika key itu sudah ada.
Jika salah satu kondisi gagal — lock email, lock handle, atau akun
itu sendiri — DynamoDB menggulung balik seluruh transaksi dan melempar
TransactionCanceledException. Tanpa signup parsial, tanpa lock yatim.
{
"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)"
}
}
]
}Kondisi itulah seluruh mekanismenya. Tanpa attribute_not_exists, sebuah signup
kedua dengan email yang sama diam-diam menimpa lock pertama. Dengannya, put
menolak, transaksi dibatalkan, dan aplikasi Anda memunculkan "email sudah dipakai."
Membangun ConditionExpression dan value map dengan tangan adalah tempat typo menyelinap masuk.
DynamoDB Expression Builder memancarkan
kondisi dan Item bertipe untuk setiap put sehingga Anda bisa menempelkan transaksi yang benar
langsung ke pemanggilan SDK Anda.
Baca kegagalannya, jangan menebak-nebak
Ketika transaksi dibatalkan, DynamoDB mengembalikan sebuah array CancellationReasons
secara posisional — satu entri per item, dalam urutan permintaan. Sebuah ConditionalCheckFailed
di slot 1 berarti email sudah dipakai; slot 2 berarti username. Petakan slot
kembali ke error tingkat-field yang presisi alih-alih "signup gagal" yang generik.
Inspeksi lock di DynoTable
Marker item tak terlihat di UI aplikasi Anda — mereka adalah pipa. Ketika sebuah signup gagal misterius, Anda perlu melihat apakah lock benar-benar ada.
Buka tabel di DynoTable dan Query prefix UNIQ#. Akun dan kedua
item lock-nya duduk bersama, sehingga sebuah signup macet (sebuah lock yang tertinggal oleh
delete yang gagal) jelas terlihat sekilas.

Jaga lock tetap jujur saat perubahan dan penghapusan
Lock bukan write-once. Mereka mencerminkan nilai yang hidup, jadi siklus hidupnya harus menjaga mereka tetap sinkron — setiap operasi yang menyentuh atribut yang dilindungi adalah sebuah transaksi juga.
- Ubah email. Satu transaksi: put lock
UNIQ#EMAIL#…baru denganattribute_not_exists, hapus lock lama, update akun. Jaminan all-or-nothing yang sama. - Hapus akun. Hapus item akun dan kedua item lock dalam satu transaksi, atau Anda akan menelantarkan sebuah lock yang memblokir nilai itu selamanya.
- Retry dengan aman. Sertakan sebuah
ClientRequestTokensehingga transaksi yang dikirim ulang (setelah gangguan jaringan) bersifat idempoten alih-alih penulisan ganda.
Jebakannya adalah memperlakukan lock sebagai fire-and-forget. Sebuah lock yang dibuat saat signup tetapi tidak pernah dihapus saat penghapusan akun adalah nilai yang tak seorang pun bisa gunakan ulang selamanya — dan itu tidak akan muncul sampai seorang pengguna sungguhan tidak bisa mengklaim handle lamanya sendiri.
Langkah selanjutnya
Marker keunikan adalah pola single-table, jadi mereka duduk secara alami di samping
item Anda yang lain — baca single-table design untuk tata letak
key, dan Query vs Scan agar Anda tidak pernah meraih sebuah
Scan untuk memeriksa sebuah lock. Pola ini pertama kali ditelusuri dalam sesi AWS
re:Invent / AWS Summit 2018 DAT374 — DynamoDB Transactions.
Draf put ber-guard kondisi dengan DynamoDB Expression Builder, lalu coba DynoTable untuk menginspeksi item lock terhadap tabel Anda sendiri.


