Intermedio5 min di lettura

Atomic Counter in DynamoDB

Un atomic counter è un attributo numerico che incrementi sul posto con una singola chiamata UpdateItem — niente lettura prima, nessuna race read-modify-write. DynamoDB applica ogni incremento in ordine di arrivo e non lascia mai che due writer si calpestino il conteggio a vicenda.

Cos'è un atomic counter di DynamoDB?

Un atomic counter di DynamoDB è un attributo numerico che incrementi sul posto con una singola chiamata UpdateItem usando un'update expression ADD (o SET x = x + :n). DynamoDB legge, somma e scrive il valore server-side, così i writer concorrenti si serializzano senza aggiornamenti persi — ma non è idempotente, quindi una chiamata ritentata incrementa due volte.

  • Usa ADD (o SET x = x + :n) per incrementare in una chiamata. DynamoDB legge, somma e scrive server-side — i chiamanti concorrenti si serializzano, nessun aggiornamento perso.
  • Niente lettura prima. Venendo da SQL faresti SELECT poi UPDATE; qui salti del tutto la lettura e l'operazione è comunque sicura sotto concorrenza.
  • Gli atomic counter non sono idempotenti. Un UpdateItem ritentato incrementa di nuovo. Se non puoi tollerare conteggi in eccesso o in difetto, usa un conditional update.
  • ADD su un attributo mancante parte da 0, quindi il primo incremento funziona e basta — nessuna scrittura di seed necessaria.

Il problema con read-modify-write

Diciamo che tracci le visualizzazioni su un video. L'istinto ingenuo, dritto da SQL, è: GetItem, aggiungi uno nella tua app, PutItem il nuovo totale.

Due spettatori premono play insieme. Entrambi leggono views = 41. Entrambi scrivono 42. Hai contato una visualizzazione, non due. È un aggiornamento perso — il classico footgun di concorrenza, e non si manifesta finché non hai traffico.

In SQL lo schiveresti con UPDATE videos SET views = views + 1, spingendo l'aritmetica nel database. DynamoDB ha la stessa mossa, ed è proprio il punto di un atomic counter.

Incrementa in una chiamata

Modella un Item di statistiche per video. Chiave di partizione VID#<id>, sort key STATS#TOTAL, con un play_count numerico:

PKSKplay_count
"VID#9f3a""STATS#TOTAL"41

Per registrare un play, invia un solo UpdateItem con una clausola ADD:

# UpdateItem
Key               PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression  ADD play_count :one
Values            :one = 1

DynamoDB legge play_count, aggiunge 1 e scrive il risultato dentro una singola operazione server-side. Non c'è finestra perché un altro writer si infili. Dieci play concorrenti producono +10, ogni volta — è ciò che "atomic" ti compra.

Puoi costruire e copiare questa esatta espressione — nomi, valori e tutti e quattro i tipi di clausola — con il DynamoDB Expression Builder.

ADD funziona anche quando play_count non esiste ancora: DynamoDB tratta un attributo numerico mancante come 0, quindi il primo play lo crea a 1. Nessuna scrittura di seed separata. (AWS: Using update expressions)

ADD vs SET +: scegline uno

Due espressioni fanno la stessa aritmetica. AWS raccomanda SET per uso generale perché si compone con altre azioni SET e si legge più esplicitamente. (AWS: Using update expressions)

ADD play_count :oneSET play_count = play_count + :one
Attr mancanteLo crea, partendo da 0Errore — serve if_not_exists
Tipi di datoSolo number e setNumber (e altro) via SET
Combinare con SETClausola separataUna clausola SET, separata da virgola
Indicazione AWSVa bene per i counterDefault raccomandato

Se l'attributo potrebbe non esistere e vuoi SET, proteggilo: SET play_count = if_not_exists(play_count, :zero) + :one. Con ADD lo salti — fa il seed da 0 gratis.

Fallo in DynoTable

Apri l'Item, modifica play_count, e puoi osservare un incremento atomico arrivare senza scrivere a mano JSON — il pannello di update emette l'espressione ADD per te e mostra il nuovo valore nel momento in cui fa il commit.

La trappola: i counter non sono idempotenti

Ecco la parte che morde i team in produzione. Un atomic counter incrementa ogni volta che UpdateItem gira. (AWS: Working with items)

Immagina un blip di rete: invii l'incremento, la connessione cade prima che la risposta torni indietro, e non sai se è atterrato. Ritenti. Se la prima chiamata è riuscita, hai ora contato quel play due volte.

Per le visualizzazioni video va bene — qualche doppio conteggio su un milione di play non farà male a nessuno, e AWS chiama questo esatto caso "track visitors" l'uso canonico degli atomic counter. (AWS: Working with items)

Non va bene per nulla che debba essere esatto: inventario che puoi vendere in eccesso, crediti che puoi spendere due volte, un saldo che puoi corrompere. Lì, reggiungi a un conditional update.

Quando ti serve l'esattezza: conditional update

Un conditional update è idempotente se condizioni sullo stesso attributo che stai cambiando. Incrementa play_count a 42, ma solo se è attualmente 41:

# UpdateItem
Key                  PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression     SET play_count = :next
ConditionExpression  play_count = :current
Values               :next = 42, :current = 41

Ora un retry è sicuro: se la prima scrittura ha già portato play_count a 42, la condizione play_count = 41 fallisce la seconda volta e nulla cambia. (AWS: Working with items)

Il costo è la concorrenza. Due writer che corrono sulla stessa condizione significa che uno vince e uno ottiene un ConditionalCheckFailedException da ritentare — hai scambiato il throughput del counter incondizionato per la correttezza. Per counter esatti e contesi è lo scambio giusto. Per i conteggi di visualizzazioni è eccessivo.

Errori

  • Un Item caldo. Una singola riga di counter è una chiave di partizione. Un video virale che martella VID#9f3a / STATS#TOTAL può colpire un tetto di scrittura per partizione. Fai lo shard: distribuisci le scritture su STATS#TOTAL#0..N e somma in lettura.
  • Niente incremento in batch. BatchWriteItem è solo put/delete — non può eseguire update expression. I counter passano da UpdateItem, un Item per chiamata.
  • ADD è solo number e set. Non tocca string o boolean; quello è un SET. Vedi tipi di dato DynamoDB per il modello completo degli attributi.

Prossimi passi

Gli atomic counter sono un pattern di scrittura; come leggi indietro gli aggregati è una domanda di modellazione — vedi single-table design per tenere gli Item di statistiche accanto al loro genitore, e Query vs Scan così che fare il roll-up di un counter con sharding resti una Query.

Abbozza e copia l'incremento nel DynamoDB Expression Builder, poi prova DynoTable per eseguire update atomici sulle tue tabelle e osservare i conteggi muoversi.

Aggiornato