DynamoDB-Reference-Counts
Ein Reference Count ist eine Zahl, die du auf einem Parent-Item speicherst und die trackt, wie viele Child-Items darauf zeigen — Likes auf einem Post, Members in einem Workspace, Replies auf einem Comment. Du hältst ihn, weil Children bei jedem Read zu zählen zu teuer ist.
Wie pflegst du einen Count in DynamoDB?
Speichere die laufende Summe als Number auf dem Parent-Item und update sie im selben Write, der das Child erstellt. Ein lässt beide landen oder keines, und eine Condition auf dem Child-Write stoppt Retries am Double-Counting — sodass ein einzelnes GetItem einen akkuraten Count zurückgibt.
- Zähle Children nicht zur Read-Zeit. Eine
Query, um Likes zu zählen, zahlt für jedes Like-Item, das sie scannt. Speichere die Summe auf dem Post und lies ein Item statt dessen. - Pflege den Count dort, wo das Child geschrieben wird, nicht danach. Bumpe ihn in derselben Operation, die das Child erstellt, damit die beiden nie driften.
- Nutze eine , wenn Write und Bump verschiedene Items
berühren. Ein Like ist ein Item, der Count lebt auf einem anderen —
TransactWriteItemslässt beide landen oder keines. - Der Fallstrick ist Double-Counting. Ein retried oder dupliziertes Like, das das Increment neu läuft, bläht die Zahl auf. Schütze den Child-Write mit einer Condition.
Warum überhaupt zählen
Aus SQL würdest du nie einen Like-Count speichern — du würdest
SELECT COUNT(*) FROM likes WHERE post_id = ? und einen Index günstig machen
lassen. DynamoDB hat kein COUNT(*), das das Lesen von Items skippt.
Eine Query über die Likes eines Posts liest — und rechnet ab — jedes Like-Item
in dieser Partition, selbst wenn du nur die Zahl willst. Auf einem viralen Post
sind das Tausende RCUs, um „wie viele Likes?“ zu beantworten. Das ist der Read-
Fallstrick, den Reference Counts töten sollen.
Also du: speichere die laufende Summe auf
dem Post selbst. Den Count zu lesen wird ein einzelnes GetItem. Die Kosten: du
besitzt jetzt, ihn akkurat zu halten.
Die Items modellieren
Zwei Item-Typen teilen eine Partition, sodass Post und Likes in einer Item Collection sitzen. Erfundene Keys:
| PK | SK | attributes |
|---|---|---|
| POST#a91f | META | likeTally (Number), body, authorId, createdAt |
| PK | SK | attributes |
|---|---|---|
| POST#a91f | LIKE#USER#7c20 | likedAt |
Das likeTally-Attribut auf dem META-Item ist der Reference Count. Jedes
LIKE#-Item ist ein Child. Beide unter PK = "POST#a91f" zu legen heißt, eine
einzelne Query kann Post und Likers zusammen holen, wenn du die Liste wirklich
willst.
Den Count atomar bumpen
DynamoDB inkrementiert eine Number mit einer ADD- (oder SET x = x + :n)
— das ist ein Atomic Counter: DynamoDB
wendet das Delta serverseitig an, ohne dass du den aktuellen Wert zuerst liest,
sodass concurrent Increments einander nicht clobbern.
(AWS: atomic counters)
Einen Post liken sind zwei Writes auf zwei Items — das LIKE#-Item
erstellen und 1 zu likeTally auf META addieren. Wenn das Like landet, der
Bump aber scheitert, ist der Tally forever falsch. Du brauchst beide oder keines.
Das garantiert TransactWriteItems — all-or-nothing über mehrere Items, und es
cancelt die ganze Transaction, wenn irgendein Item concurrent modifiziert wird
(AWS: pessimistic locking with transactions):
{
"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 und Update committen zusammen. Wenn eines scheitert, rollt DynamoDB beide
zurück und gibt TransactionCanceledException zurück.
Gegen Double-Counting schützen
Der echte Bug ist kein halbgeschriebenes Like — die Transaction verhindert das.
Es ist derselbe User, der zweimal liked, oder ein Client-Retry, der den
Request replayed. Jeder Replay addiert ein weiteres 1, und likeTally driftet
still über den echten Count.
Die ConditionExpression: attribute_not_exists(SK) auf dem Put ist der Guard.
Wenn das LIKE#-Item dieses Users schon existiert, scheitert die Condition des
Put, die ganze Transaction wird gecancelt und — kritisch — das ADD läuft nie.
Ein Like pro User, enforced durch den Key.
Baue und kopiere diese Update- und Condition-Expressions — mit den richtigen
ExpressionAttributeValues und dem attribute_not_exists-Guard — im
DynamoDB Expression Builder, statt das JSON
von Hand zusammenzusetzen.
Unlike, und die Kosten
Ein Like zu entfernen ist das Spiegelbild: Delete das LIKE#-Item mit
ConditionExpression: attribute_exists(SK) und ADD likeTally :minusOne in
derselben Transaction. Die Condition stoppt ein Double-Unlike daran, den Tally
negativ zu treiben.
Kenn den Preis. Ein transactional Write kostet 2 WCUs pro Item für Items bis 1 KB — eins zum Prepare, eins zum Commit — versus 1 WCU für einen plainen Write. Ein Like sind zwei Items, also grob vier WCUs pro Like. Günstig pro Action, aber wissenswert, bevor ein Celebrity-Post einen Like-Storm nimmt.
In DynoTable ansehen
Wenn du vermutest, dass ein Tally gedriftet ist, willst du den gespeicherten
likeTally gegen die tatsächliche Zahl der LIKE#-Children vergleichen — ohne
eine Count-Query in Prod zu fahren.

Für echte Reconciliation über eine begrenzte Menge Posts — „welche Tallies
matchen nicht ihre Child-Counts?“ — führt DynoTables SQL Workbench GROUP BY und
Join clientseitig über die Rows, die du geladen hast, was plaines PartiQL nicht
ausdrücken kann.
Fallstricke und nächste Schritte
- Pflege den Count nicht out-of-band (eine Lambda, die nachts recountet). Das ist ein Band-Aid über einen Write-Pfad, der von Anfang an transactional hätte sein sollen.
- Achte auf . Ein einzelner wild populärer Post konzentriert jedes Like — und jeden Tally-Bump — auf einen Partition Key. Der Count ist korrekt; die Partition kann trotzdem drosseln.
- Reconcile selten, repariere chirurgisch. Drift sollte near-zero sein, wenn jede Mutation conditioned ist. Behandle einen Mismatch als Bug zum Finden, nicht als Zahl zum Überschreiben.
Verwandte Lektüre: Single-Table Design dafür, warum Post und Likes eine Partition teilen, und Query vs Scan dafür, warum Children zur Read-Zeit zu zählen das Pattern ist, das du vermeidest.
Dann lade DynoTable herunter, um diese Item Collections zu inspizieren und deine Tallies gegen deine eigenen Tabellen zu verifizieren.


