DynamoDB Atomic Counters: Wie ADD funktioniert — und wann nicht
Ein Atomic Counter ist ein numerisches Attribut, das du mit einem einzigen
UpdateItem-Aufruf an Ort und Stelle hochzählst — kein vorheriger Read, kein
Read-Modify-Write-Rennen. DynamoDB wendet jedes Inkrement in der Reihenfolge des
Eintreffens an und lässt nie zu, dass zwei Writer den Stand des anderen überschreiben.
Was ist ein Atomic Counter in DynamoDB?
Ein Atomic Counter in DynamoDB ist ein numerisches Attribut, das du mit einem einzigen UpdateItem-Aufruf über eine ADD-Update-Expression (oder SET x = x + :n) an Ort und Stelle hochzählst. DynamoDB liest, addiert und schreibt den Wert serverseitig, gleichzeitige Writer werden also ohne verlorene Updates serialisiert — idempotent ist das aber nicht, ein wiederholter Aufruf zählt zweimal hoch.
- Nimm
ADD(oderSET x = x + :n), um in einem Aufruf hochzuzählen. DynamoDB liest, addiert und schreibt serverseitig — gleichzeitige Aufrufer werden serialisiert, keine verlorenen Updates. - Kein vorheriger Read. Aus SQL kommend würdest du erst
SELECTund dannUPDATE; hier lässt du den Read komplett weg, und der Vorgang ist unter Nebenläufigkeit trotzdem sicher. - Atomic Counters sind nicht idempotent. Ein wiederholtes
UpdateItemzählt erneut hoch. Wenn du weder Über- noch Unterzählung verträgst, nimm ein . ADDauf einem fehlenden Attribut startet bei 0, das allererste Inkrement funktioniert also einfach — kein Seed-Write nötig.
Das Problem mit Read-Modify-Write
Sagen wir, du zählst Aufrufe eines Videos. Der naive Reflex, direkt aus SQL:
GetItem, in deiner App eins draufrechnen, die neue Summe per PutItem zurückschreiben.
Zwei Zuschauer drücken gleichzeitig auf Play. Beide lesen views = 41. Beide schreiben 42. Du
hast einen Aufruf gezählt, nicht zwei. Das ist ein Lost Update — der klassische
Nebenläufigkeits-Fallstrick, und er zeigt sich erst, wenn du Traffic hast.
In SQL würdest du ihm mit UPDATE videos SET views = views + 1 entgehen und die
Arithmetik in die Datenbank schieben. DynamoDB hat denselben Zug, und genau darum
geht es beim Atomic Counter.
In einem Aufruf hochzählen
Modelliere ein Statistik-Item pro Video. Partitionsschlüssel VID#<id>, Sortierschlüssel STATS#TOTAL,
mit einem numerischen play_count:
| PK | SK | play_count |
|---|---|---|
| "VID#9f3a" | "STATS#TOTAL" | 41 |
Um einen Abruf zu registrieren, schickst du ein UpdateItem mit einer ADD-Klausel:
# UpdateItem
Key PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression ADD play_count :one
Values :one = 1
DynamoDB liest play_count, addiert 1 und schreibt das Ergebnis innerhalb einer einzigen
serverseitigen Operation. Es gibt kein Fenster, in das sich ein anderer Writer schieben könnte. Zehn
gleichzeitige Abrufe ergeben +10, jedes Mal — dafür steht „atomar".
Genau diese Expression — Namen, Werte und alle vier Klauseltypen — kannst du dir im DynamoDB Expression Builder zusammenbauen und kopieren.
ADD funktioniert auch, wenn play_count noch gar nicht existiert: DynamoDB behandelt ein fehlendes
numerisches Attribut als 0, der erste Abruf legt es also mit 1 an. Kein separater
Seed-Write. (AWS: Using update expressions)
ADD vs. SET +: entscheide dich
Zwei Expressions rechnen dasselbe. AWS empfiehlt SET für den allgemeinen Gebrauch,
weil es sich mit anderen SET-Aktionen kombinieren lässt und expliziter zu lesen ist. (AWS:
Using update expressions)
ADD play_count :one | SET play_count = play_count + :one | |
|---|---|---|
| Fehlendes Attribut | Legt es an, beginnend bei 0 | Fehler — braucht if_not_exists |
| Datentypen | Nur Zahlen und Sets | Zahlen (und mehr) über SET |
Mit SET kombinierbar | Eigene Klausel | Eine SET-Klausel, kommagetrennt |
| AWS-Empfehlung | Für Counter völlig in Ordnung | Empfohlene Standardwahl |
Wenn das Attribut vielleicht nicht existiert und du SET willst, sichere es ab:
SET play_count = if_not_exists(play_count, :zero) + :one. Mit ADD sparst du dir
das — es startet gratis bei 0.
Schreibkosten pro Inkrement
Auf On-Demand in us-east-1 rechnet jedes UpdateItem mit ADD 1 WCU pro
KB Item-Größe nach dem Write ab (aufgerundet). Eine 900 Byte große Statistikzeile
kostet 1 WCU pro registriertem Abruf; zehn gleichzeitige Abrufe landen weiterhin bei 10
WCU insgesamt, nicht bei einer. Den Counter über Partitionen zu sharden verschiebt die
Durchsatzgrenze, ändert aber nichts an der WCU-Rechnung pro Item. Bemiss die Zeile mit dem
Item-Size-Rechner und kalkuliere heiße Pfade im
Preisrechner durch.
In DynoTable umsetzen
Öffne das Statistik-Item, um den laufenden Counter zu prüfen, und roll dann einen gesharden
Counter mit SUM und GROUP BY in der SQL Workbench auf, um die Summe über alle
STATS#TOTAL#0..N-Zeilen zu sehen. Für das Inkrement selbst nimmst du den
DynamoDB Expression Builder im Web und setzt die
ADD-UpdateItem-Expression samt Namen und Werten zusammen.
Die Falle: Counter sind nicht idempotent
Ein Atomic Counter zählt jedes Mal hoch, wenn UpdateItem läuft. (AWS: Working
with items)
Stell dir einen Netzwerkaussetzer vor: Du schickst das Inkrement, die Verbindung bricht ab, bevor die Antwort zurückkommt, und du weißt nicht, ob es angekommen ist. Du wiederholst. Wenn der erste Aufruf doch durchging, hast du diesen Abruf jetzt doppelt gezählt.
Bei Videoaufrufen ist das egal — ein paar Doppelzählungen auf eine Million Abrufe tun niemandem weh, und AWS nennt genau diesen „Besucher zählen"-Fall als kanonische Verwendung von Atomic Counters. (AWS: Working with items)
Es ist nicht egal bei allem, was exakt sein muss: Bestand, den du überverkaufen kannst, Guthaben, das du doppelt ausgeben kannst, ein Saldo, den du korrumpieren kannst. Dort greifst du zum Conditional Update.
Wenn es exakt sein muss: Conditional Updates
Ein Conditional Update ist idempotent, wenn du auf genau das Attribut bedingst, das du
änderst. Zähl play_count auf 42 hoch, aber nur, wenn er gerade 41 ist:
# UpdateItem
Key PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression SET play_count = :next
ConditionExpression play_count = :current
Values :next = 42, :current = 41
Jetzt ist ein erneuter Versuch sicher: Hat der erste Write play_count bereits auf 42 gesetzt,
schlägt die Bedingung play_count = 41 beim zweiten Mal fehl, und nichts ändert sich. (AWS:
Working with items)
Der Preis ist Nebenläufigkeit. Zwei Writer, die um dieselbe Bedingung rennen, heißt: einer gewinnt,
einer bekommt eine ConditionalCheckFailedException und muss es erneut versuchen — du hast den
Durchsatz des unbedingten Counters gegen Korrektheit getauscht. Für exakte, umkämpfte
Counter ist das der richtige Handel. Für Aufrufzahlen ist es übertrieben.
Fallstricke
- Ein . Eine einzelne Counter-Zeile ist ein Partitionsschlüssel. Ein virales Video,
das auf
VID#9f3a/STATS#TOTALeinhämmert, kann die Schreibgrenze pro Partition reißen. Shard sie: Verteile die Writes überSTATS#TOTAL#0..Nund summiere beim Lesen. - Kein Batch-Inkrement.
BatchWriteItemkann nur Put und Delete — keine . Counter laufen überUpdateItem, ein Item pro Aufruf. Musst du mehrere Counter atomar hochzählen, führtTransactWriteItemsUpdate-Aktionen auf bis zu 100 Items in einem Request aus, zu etwa den doppelten Schreibkosten. ADDkann nur Zahlen und Sets. Strings oder Booleans rührt es nicht an; dafür istSETda. Das vollständige Attributmodell steht unter DynamoDB Datentypen.
Nächste Schritte
Atomic Counters sind ein Schreibmuster; wie du Aggregate wieder liest, ist eine
Modellierungsfrage — siehe Single-Table Design dazu, wie du
Statistik-Items neben ihrem Parent hältst, und Query vs. Scan, damit
das Aufrollen eines gesharden Counters ein Query bleibt.
Entwirf das Inkrement im DynamoDB Expression Builder, kopier es dir und probier DynoTable aus, um atomare Updates gegen deine eigenen Tabellen laufen zu lassen und den Zahlen beim Wandern zuzusehen.