Fortgeschritten5 Min. Lesezeit

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 (oder SET 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 SELECT und dann UPDATE; hier überspringst du das Lesen ganz und die Operation ist trotzdem sicher unter Nebenläufigkeit.
  • Atomic Counters sind nicht idempotent. Ein wiederholtes UpdateItem zählt erneut hoch. Wenn du Über- oder Unterzählung nicht tolerieren kannst, verwende ein bedingtes Update.
  • ADD auf 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:

PKSKplay_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 :oneSET play_count = play_count + :one
Fehlendes AttrErzeugt es, beginnend bei 0Fehler — braucht if_not_exists
DatentypenNur Zahlen und SetsZahlen (und mehr) via SET
Mit SET kombinierenSeparate KlauselEine SET-Klausel, kommagetrennt
AWS-EmpfehlungOkay für ZählerEmpfohlener 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#TOTAL hämmert, kann eine Pro-Partition-Schreibobergrenze treffen. Sharde es: Verteile Schreibvorgänge über STATS#TOTAL#0..N und summiere beim Lesen.
  • Kein Batch-Inkrement. BatchWriteItem ist nur Put/Delete — es kann keine Update-Expressions ausführen. Zähler gehen durch UpdateItem, ein Item pro Aufruf.
  • ADD ist nur Zahlen und Sets. Es fasst keine Strings oder Booleans an; das ist ein SET. 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.

Aktualisiert