Fortgeschritten6 Min. Lesezeit

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 (oder SET 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 SELECT und dann UPDATE; hier lässt du den Read komplett weg, und der Vorgang ist unter Nebenläufigkeit trotzdem sicher.
  • Atomic Counters sind nicht idempotent. Ein wiederholtes UpdateItem zählt erneut hoch. Wenn du weder Über- noch Unterzählung verträgst, nimm ein .
  • ADD auf 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:

PKSKplay_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 :oneSET play_count = play_count + :one
Fehlendes AttributLegt es an, beginnend bei 0Fehler — braucht if_not_exists
DatentypenNur Zahlen und SetsZahlen (und mehr) über SET
Mit SET kombinierbarEigene KlauselEine SET-Klausel, kommagetrennt
AWS-EmpfehlungFür Counter völlig in OrdnungEmpfohlene 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#TOTAL einhämmert, kann die Schreibgrenze pro Partition reißen. Shard sie: Verteile die Writes über STATS#TOTAL#0..N und summiere beim Lesen.
  • Kein Batch-Inkrement. BatchWriteItem kann nur Put und Delete — keine . Counter laufen über UpdateItem, ein Item pro Aufruf. Musst du mehrere Counter atomar hochzählen, führt TransactWriteItems Update-Aktionen auf bis zu 100 Items in einem Request aus, zu etwa den doppelten Schreibkosten.
  • ADD kann nur Zahlen und Sets. Strings oder Booleans rührt es nicht an; dafür ist SET da. 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.

Aktualisiert