DynamoDB Atomic Counters
Ein Atomic Counter ist ein numerisches Attribut, das du mit einem einzigen
UpdateItem-Aufruf an Ort und Stelle hochzählst — kein Lesen zuerst, kein
Read-Modify-Write-Race. DynamoDB wendet jedes Inkrement in Ankunftsreihenfolge an
und lässt nie zwei Writer den Zähler des jeweils anderen überschreiben.
Was ist ein DynamoDB Atomic Counter?
Ein DynamoDB Atomic Counter ist ein numerisches Attribut, das du mit einem einzigen
UpdateItem-Aufruf an Ort und Stelle hochzählst — über eine ADD-Update-Expression
(oder SET x = x + :n). DynamoDB liest, addiert und schreibt den Wert serverseitig,
sodass gleichzeitige Writer ohne verlorene Updates serialisieren — aber es ist nicht
idempotent, also zählt ein wiederholter Aufruf zweimal hoch.
- Verwende
ADD(oderSET x = x + :n), um in einem Aufruf hochzuzählen. DynamoDB liest, addiert und schreibt serverseitig — gleichzeitige Aufrufer serialisieren, keine verlorenen Updates. - Kein Lesen zuerst. Aus SQL kommend würdest du
SELECTund dannUPDATE; hier überspringst du das Lesen ganz und die Operation ist trotzdem sicher unter Nebenläufigkeit. - Atomic Counters sind nicht idempotent. Ein wiederholtes
UpdateItemzählt erneut hoch. Wenn du Über- oder Unterzählung nicht tolerieren kannst, verwende ein bedingtes Update. ADDauf einem fehlenden Attribut startet bei 0, also funktioniert schon das allererste Inkrement — kein Seed-Schreibvorgang nötig.
Das Problem mit Read-Modify-Write
Angenommen, du verfolgst Views eines Videos. Der naive Instinkt, direkt aus SQL,
ist: GetItem, in deiner App eins addieren, den neuen Gesamtwert per PutItem
zurückschreiben.
Zwei Zuschauer drücken gleichzeitig Play. Beide lesen views = 41. Beide schreiben
42. Du hast einen View gezählt, nicht zwei. Das ist ein verlorenes Update — der
klassische Nebenläufigkeits-Footgun, und er taucht erst auf, wenn du Traffic hast.
In SQL würdest du ihm mit UPDATE videos SET views = views + 1 ausweichen und die
Arithmetik in die Datenbank schieben. DynamoDB hat denselben Zug, und er ist der
ganze Sinn eines Atomic Counters.
In einem Aufruf hochzählen
Modelliere ein Stats-Item pro Video. Partition Key VID#<id>, Sort Key
STATS#TOTAL, mit einem numerischen play_count:
| PK | SK | play_count |
|---|---|---|
| "VID#9f3a" | "STATS#TOTAL" | 41 |
Um einen Play zu registrieren, sende 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 dem sich ein anderer
Writer einschleichen könnte. Zehn gleichzeitige Plays ergeben +10, jedes Mal —
das ist es, was „atomar" dir einbringt.
Du kannst genau diese Expression — Namen, Werte und alle vier Klauseltypen — mit dem DynamoDB Expression Builder bauen und kopieren.
ADD funktioniert auch, wenn play_count noch nicht existiert: DynamoDB behandelt
ein fehlendes numerisches Attribut als 0, also erzeugt der erste Play es bei 1.
Kein separater Seed-Schreibvorgang. (AWS: Using update expressions)
ADD vs. SET +: wähle eines
Zwei Expressions tun dieselbe Arithmetik. AWS empfiehlt SET für den allgemeinen
Gebrauch, weil es sich mit anderen SET-Aktionen kombiniert und expliziter liest.
(AWS: Using update expressions)
ADD play_count :one | SET play_count = play_count + :one | |
|---|---|---|
| Fehlendes Attr | Erzeugt es, beginnend bei 0 | Fehler — braucht if_not_exists |
| Datentypen | Nur Zahlen und Sets | Zahlen (und mehr) via SET |
Mit SET kombinieren | Separate Klausel | Eine SET-Klausel, kommagetrennt |
| AWS-Empfehlung | Okay für Zähler | Empfohlener Standard |
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 überspringst
du das — es seedet kostenlos von 0.
Mach es in DynoTable
Öffne das Item, bearbeite play_count, und du kannst ein atomares Inkrement landen
sehen, ohne JSON von Hand zu schreiben — das Update-Panel gibt die ADD-Expression
für dich aus und zeigt den neuen Wert in dem Moment, in dem er committet.
Die Falle: Zähler sind nicht idempotent
Hier ist der Teil, der Teams in Produktion beißt. Ein Atomic Counter zählt jedes
Mal hoch, wenn UpdateItem läuft. (AWS: Working with items)
Stell dir einen Netzwerkausfall vor: Du sendest das Inkrement, die Verbindung bricht ab, bevor die Antwort zurückkommt, und du weißt nicht, ob es gelandet ist. Du versuchst es erneut. Wenn der erste Aufruf erfolgreich war, hast du diesen Play jetzt zweimal gezählt.
Für Video-Views ist das in Ordnung — ein paar Doppelzählungen bei einer Million Plays schaden niemandem, und AWS nennt genau diesen „Besucher verfolgen"-Fall den kanonischen Einsatz von Atomic Counters. (AWS: Working with items)
Es ist nicht in Ordnung für irgendetwas, das exakt sein muss: Bestand, den du überverkaufen kannst, Credits, die du doppelt ausgeben kannst, ein Saldo, den du korrumpieren kannst. Dort greife zu einem bedingten Update.
Wenn du Exaktheit brauchst: bedingte Updates
Ein bedingtes Update ist idempotent, wenn du auf dasselbe Attribut bedingst, das du
änderst. Erhöhe play_count auf 42, aber nur, wenn es aktuell 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 Retry sicher: Wenn der erste Schreibvorgang play_count bereits auf
42 bewegt hat, scheitert die Bedingung play_count = 41 beim zweiten Mal und
nichts ändert sich. (AWS: Working with items)
Der Preis ist Nebenläufigkeit. Zwei Writer, die auf derselben Bedingung racen,
bedeutet, einer gewinnt und einer bekommt eine ConditionalCheckFailedException zum
Wiederholen — du hast den Durchsatz des unbedingten Zählers gegen Korrektheit
getauscht. Für exakte, umkämpfte Zähler ist das der richtige Tausch. Für View-Counts
ist es Overkill.
Fallstricke
- Ein heißes Item. Eine einzelne Zähler-Zeile ist ein Partition Key. Ein virales
Video, das auf
VID#9f3a/STATS#TOTALhämmert, kann eine Pro-Partition-Schreibobergrenze treffen. Sharde es: Verteile Schreibvorgänge überSTATS#TOTAL#0..Nund summiere beim Lesen. - Kein Batch-Inkrement.
BatchWriteItemist nur Put/Delete — es kann keine Update-Expressions ausführen. Zähler gehen durchUpdateItem, ein Item pro Aufruf. ADDist nur Zahlen und Sets. Es fasst keine Strings oder Booleans an; das ist einSET. Siehe DynamoDB-Datentypen für das vollständige Attribut-Modell.
Nächste Schritte
Atomic Counters sind ein Schreibmuster; wie du Aggregate zurückliest, ist eine
Modellierungsfrage — siehe Single-Table-Design, um
Stats-Items neben ihrem Elternteil zu halten, und Query vs. Scan,
damit das Aufrollen eines gesharddeten Zählers ein Query bleibt.
Entwirf und kopiere das Inkrement im DynamoDB Expression Builder, dann probiere DynoTable aus, um atomare Updates gegen deine eigenen Tabellen auszuführen und zu beobachten, wie sich die Zähler bewegen.