Reference Count DynamoDB
Sebuah reference count adalah angka yang Anda simpan pada Item induk yang melacak berapa banyak Item anak yang menunjuk padanya — like pada sebuah post, anggota dalam sebuah workspace, balasan pada sebuah comment. Anda menyimpannya karena menghitung anak-anak pada setiap pembacaan terlalu mahal.
Bagaimana cara memelihara hitungan di DynamoDB?
Simpan total berjalan sebagai angka pada Item induk dan perbarui dalam tulisan yang sama yang membuat anak. Sebuah memastikan keduanya mendarat atau tidak sama sekali, dan sebuah condition pada tulisan anak mencegah retry dari double-counting — sehingga satu GetItem mengembalikan hitungan yang akurat.
- Jangan hitung anak-anak saat pembacaan. Sebuah
Queryuntuk menghitung like membayar setiap Item like yang dipindainya. Simpan totalnya pada post dan baca satu Item saja. - Pelihara hitungannya di tempat anak ditulis, bukan setelahnya. Naikkan dalam operasi yang sama yang membuat anak sehingga keduanya tidak pernah menyimpang.
- Gunakan transaction saat tulisan dan kenaikan menyentuh Item berbeda. Sebuah
like adalah satu Item, hitungannya ada di Item lain —
TransactWriteItemsmembuat keduanya mendarat atau tidak sama sekali. - Footgun-nya adalah double-counting. Sebuah like yang dicoba-ulang atau diduplikasi yang menjalankan-ulang increment menggelembungkan angkanya. Jaga tulisan anak dengan sebuah condition.
Mengapa menghitung sama sekali
Datang dari SQL, Anda tidak akan pernah menyimpan jumlah-like — Anda akan
SELECT COUNT(*) FROM likes WHERE post_id = ? dan membiarkan sebuah index
membuatnya murah. DynamoDB tidak punya COUNT(*) yang melewatkan pembacaan Item.
Sebuah Query atas like sebuah post membaca — dan menagih — setiap Item like di
partition itu, bahkan jika Anda hanya menginginkan angkanya. Pada sebuah post yang
viral itu ribuan RCU untuk menjawab "berapa banyak like?" Itulah footgun pembacaan
yang menjadi alasan keberadaan reference count.
Jadi Anda denormalisasi: simpan total berjalan pada post itu sendiri. Membaca
hitungannya menjadi satu GetItem. Biayanya adalah kini Anda yang memiliki tugas
menjaganya akurat.
Modelkan Item-nya
Dua tipe Item berbagi sebuah partition sehingga post dan like-nya berada dalam satu item collection. Key yang dikarang:
| PK | SK | attributes |
|---|---|---|
| POST#a91f | META | likeTally (Number), body, authorId, createdAt |
| PK | SK | attributes |
|---|---|---|
| POST#a91f | LIKE#USER#7c20 | likedAt |
Atribut likeTally pada Item META adalah reference count. Setiap Item LIKE#
adalah anak. Menempatkan keduanya di bawah PK = "POST#a91f" berarti satu Query
bisa mengambil post dan para peng-like-nya sekaligus saat Anda memang menginginkan
daftarnya.
Naikkan hitungannya secara atomik
DynamoDB meng-increment sebuah angka dengan update expression ADD (atau
SET x = x + :n) — ini adalah atomic counter: DynamoDB menerapkan delta di
sisi-server tanpa Anda membaca nilai saat ini terlebih dahulu, sehingga increment
bersamaan tidak saling menabrak.
(AWS: atomic counters)
Masalahnya: menyukai sebuah post adalah dua tulisan ke dua Item — membuat
Item LIKE#, dan menambahkan 1 ke likeTally pada META. Jika like mendarat
tapi kenaikan gagal, tally salah selamanya. Anda butuh keduanya atau tidak sama
sekali.
Itulah yang dijamin TransactWriteItems — all-or-nothing di banyak Item, dan ia
membatalkan seluruh transaction jika ada Item yang dimodifikasi secara bersamaan
(AWS: pessimistic locking dengan transaction):
{
"TransactItems": [
{
"Put": {
"TableName": "Social",
"Item": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "LIKE#USER#7c20"},
"likedAt": {"N": "1750636800"}
},
"ConditionExpression": "attribute_not_exists(SK)"
}
},
{
"Update": {
"TableName": "Social",
"Key": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "META"}
},
"UpdateExpression": "ADD likeTally :one",
"ExpressionAttributeValues": {":one": {"N": "1"}}
}
}
]
}Put dan Update di-commit bersama. Jika salah satunya gagal, DynamoDB me-rollback
keduanya dan mengembalikan sebuah TransactionCanceledException.
Jaga terhadap double-counting
Bug sebenarnya bukanlah like yang separuh-tertulis — transaction mencegah itu. Ia
adalah pengguna yang sama menyukai dua kali, atau sebuah retry klien yang
memutar-ulang request. Setiap pemutaran-ulang menambah 1 lagi, dan likeTally
diam-diam menyimpang di atas hitungan sebenarnya.
ConditionExpression: attribute_not_exists(SK) pada Put adalah penjaganya. Jika
Item LIKE# pengguna itu sudah ada, condition Put gagal, seluruh transaction
dibatalkan, dan — yang krusial — ADD tidak pernah berjalan. Satu like per
pengguna, ditegakkan oleh key.
Bangun dan salin update dan condition expression ini — dengan
ExpressionAttributeValues yang tepat dan penjaga attribute_not_exists — di
DynamoDB Expression Builder alih-alih merakit
JSON dengan tangan.
Unlike, dan biayanya
Menghapus sebuah like adalah bayangan cerminnya: Delete Item LIKE# dengan
ConditionExpression: attribute_exists(SK), dan ADD likeTally :minusOne dalam
transaction yang sama. Condition menghentikan double-unlike dari mendorong tally
menjadi negatif.
Ketahui harganya. Sebuah tulisan transaksional berbiaya 2 WCU per Item untuk Item hingga 1 KB — satu untuk menyiapkan, satu untuk meng-commit — versus 1 WCU untuk tulisan biasa. Sebuah like adalah dua Item, jadi setiap like sekitar empat WCU. Murah per aksi, tapi layak diketahui sebelum sebuah post selebritas terkena badai-like.
Lihat di DynoTable
Saat Anda mencurigai sebuah tally telah menyimpang, Anda ingin membandingkan
likeTally yang tersimpan dengan jumlah aktual anak LIKE# — tanpa menjalankan
query penghitungan di prod.

Untuk rekonsiliasi sejati di seluruh himpunan post yang terbatas — "tally mana yang
tidak cocok dengan jumlah anaknya?" — SQL Workbench DynoTable menjalankan GROUP BY
dan join di sisi-klien atas baris yang telah Anda muat, yang tidak bisa diekspresikan
oleh PartiQL biasa.
Jebakan dan langkah berikutnya
- Jangan pelihara hitungannya di luar-jalur (sebuah Lambda yang menghitung-ulang tiap malam). Itu plester di atas jalur tulisan yang seharusnya transaksional sejak awal.
- Awasi hot partition. Sebuah post tunggal yang amat populer mengonsentrasikan setiap like — dan setiap kenaikan tally — pada satu partition key. Hitungannya benar; partition mungkin tetap di-throttle.
- Rekonsiliasi jarang, perbaiki secara bedah. Penyimpangan seharusnya mendekati nol jika setiap mutasi dikondisikan. Perlakukan ketidakcocokan sebagai bug untuk ditemukan, bukan angka untuk ditimpa.
Bacaan terkait: single-table design untuk alasan post dan like berbagi sebuah partition, dan Query vs Scan untuk alasan menghitung anak saat pembacaan adalah pola yang Anda hindari.
Lalu unduh DynoTable untuk memeriksa item collection ini dan memverifikasi tally Anda terhadap tabel Anda sendiri.


