Compteurs atomiques DynamoDB : comment ADD fonctionne, et quand il échoue
Un compteur atomique est un attribut numérique que tu incrémentes sur place avec un seul
appel UpdateItem — sans lecture préalable, sans course de lecture-modification-écriture.
DynamoDB applique chaque incrément dans l'ordre d'arrivée et ne laisse jamais deux
écrivains écraser le compte l'un de l'autre.
Qu'est-ce qu'un compteur atomique DynamoDB ?
Un compteur atomique DynamoDB est un attribut numérique que tu incrémentes sur place avec un seul appel UpdateItem en utilisant une expression de mise à jour ADD (ou SET x = x + :n). DynamoDB lit, ajoute et écrit la valeur côté serveur, donc les écrivains concurrents se sérialisent sans mises à jour perdues — mais ce n'est pas idempotent, donc un appel réessayé incrémente deux fois.
- Utilise
ADD(ouSET x = x + :n) pour incrémenter en un appel. DynamoDB lit, ajoute et écrit côté serveur — les appelants concurrents se sérialisent, aucune mise à jour perdue. - Pas de lecture préalable. Venant de SQL, tu ferais
SELECTpuisUPDATE; ici tu sautes entièrement la lecture et l'opération reste sûre en concurrence. - Les compteurs atomiques ne sont pas idempotents. Un
UpdateItemréessayé incrémente à nouveau. Si tu ne peux pas tolérer un sur- ou sous-comptage, utilise une . ADDsur un attribut manquant démarre à 0, donc le tout premier incrément marche du premier coup — pas d'écriture d'amorçage nécessaire.
Le problème de la lecture-modification-écriture
Disons que tu suis les vues d'une vidéo. L'instinct naïf, tout droit venu de SQL, est :
GetItem, ajoute un dans ton appli, PutItem le nouveau total.
Deux spectateurs lancent la lecture en même temps. Les deux lisent views = 41. Les deux
écrivent 42. Tu as compté une vue, pas deux. C'est une mise à jour perdue — le footgun
classique de la concurrence, et il n'apparaît pas tant que tu n'as pas de trafic.
En SQL tu l'esquiverais avec UPDATE videos SET views = views + 1, en poussant
l'arithmétique dans la base. DynamoDB a le même geste, et c'est tout l'intérêt d'un compteur
atomique.
Incrémenter en un appel
Modélise un élément de stats par vidéo. Clé de partition VID#<id>, clé de tri
STATS#TOTAL, avec un play_count numérique :
| PK | SK | play_count |
|---|---|---|
| "VID#9f3a" | "STATS#TOTAL" | 41 |
Pour enregistrer une lecture, envoie un UpdateItem avec une clause ADD :
# UpdateItem
Key PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression ADD play_count :one
Values :one = 1
DynamoDB lit play_count, ajoute 1, et écrit le résultat dans une seule opération côté
serveur. Il n'y a aucune fenêtre pour qu'un autre écrivain se glisse. Dix lectures
concurrentes produisent +10, à chaque fois — c'est ce que « atomique » t'offre.
Tu peux construire et copier cette expression exacte — noms, valeurs et les quatre types de clause — avec le DynamoDB Expression Builder.
ADD fonctionne même quand play_count n'existe pas encore : DynamoDB traite un attribut
numérique manquant comme 0, donc la première lecture le crée à 1. Pas d'écriture
d'amorçage séparée. (AWS : Using update expressions)
ADD vs SET + : choisis-en un
Deux expressions font la même arithmétique. AWS recommande SET pour l'usage général parce
qu'il se compose avec d'autres actions SET et se lit plus explicitement. (AWS :
Using update expressions)
ADD play_count :one | SET play_count = play_count + :one | |
|---|---|---|
| Attribut manquant | Le crée, en partant de 0 | Erreur — nécessite if_not_exists |
| Types de données | Nombres et ensembles seulement | Nombres (et plus) via SET |
Combiner avec SET | Clause séparée | Une clause SET, séparée par virgule |
| Recommandation AWS | Convient aux compteurs | Défaut recommandé |
Si l'attribut peut ne pas exister et que tu veux SET, protège-le :
SET play_count = if_not_exists(play_count, :zero) + :one. Avec ADD tu sautes ça — il
amorce depuis 0 gratuitement.
Coût d'écriture de chaque incrément
En on-demand dans us-east-1, chaque UpdateItem avec un ADD facture 1 WCU
par Ko de la taille de l'élément après l'écriture (arrondie au supérieur). Une
ligne de stats de 900 octets coûte 1 WCU par lecture enregistrée ; dix
lectures concurrentes se soldent quand même par 10 WCU au total, pas une.
Répartir le compteur sur plusieurs partitions déplace le plafond de débit sans
changer le calcul de WCU par élément. Dimensionne la ligne avec le
calculateur de taille d'élément et chiffre
les chemins chauds dans le
calculateur de tarifs.
Fais-le dans DynoTable
Ouvre l'élément de stats pour inspecter le compteur en direct, puis agrège un compteur
shardé avec SUM et GROUP BY dans le SQL Workbench pour voir le total sur chaque ligne
STATS#TOTAL#0..N. Pour rédiger l'incrément lui-même, utilise le
DynamoDB Expression Builder web pour composer
l'expression ADD de l'UpdateItem, noms et valeurs compris.
Le piège : les compteurs ne sont pas idempotents
Voici la partie qui mord les équipes en production. Un compteur atomique incrémente
à chaque fois que UpdateItem s'exécute. (AWS : Working with items)
Imagine une coupure réseau : tu envoies l'incrément, la connexion tombe avant le retour de la réponse, et tu ne sais pas si ça a abouti. Tu réessaies. Si le premier appel a réussi, tu viens de compter cette lecture deux fois.
Pour des vues de vidéo, c'est acceptable — quelques doubles comptages sur un million de lectures ne feront de mal à personne, et AWS cite ce cas exact « suivi de visiteurs » comme l'usage canonique des compteurs atomiques. (AWS : Working with items)
Ce n'est pas acceptable pour quoi que ce soit qui doit être exact : un stock que tu peux survendre, des crédits que tu peux dépenser deux fois, un solde que tu peux corrompre. Là, privilégie une mise à jour conditionnelle.
Quand tu as besoin d'exactitude : les mises à jour conditionnelles
Une mise à jour conditionnelle est idempotente si tu conditionnes sur l'attribut même que
tu modifies. Incrémente play_count à 42, mais seulement s'il vaut actuellement 41 :
# UpdateItem
Key PK = "VID#9f3a", SK = "STATS#TOTAL"
UpdateExpression SET play_count = :next
ConditionExpression play_count = :current
Values :next = 42, :current = 41
Maintenant un réessai est sûr : si la première écriture a déjà déplacé play_count à 42,
la condition play_count = 41 échoue la seconde fois et rien ne change. (AWS :
Working with items)
Le coût est la concurrence. Deux écrivains en course sur la même condition signifient que
l'un gagne et que l'autre reçoit un ConditionalCheckFailedException à réessayer — tu as
échangé le débit du compteur inconditionnel contre la correction. Pour des compteurs exacts
et disputés, c'est le bon compromis. Pour des compteurs de vues, c'est superflu.
Écueils
- Un seul . Une seule ligne de compteur est une seule
clé de partition. Une vidéo virale qui martèle
VID#9f3a/STATS#TOTALpeut atteindre un plafond d'écriture par partition. Shard-le : répartis les écritures surSTATS#TOTAL#0..Net somme à la lecture. - Pas d'incrément par lot.
BatchWriteItemest put/delete uniquement — il ne peut pas exécuter d'. Les compteurs passent parUpdateItem, un élément par appel. Si tu dois incrémenter plusieurs compteurs de façon atomique,TransactWriteItemsexécute des actions Update sur jusqu'à 100 éléments en une requête, à environ le double du coût d'écriture. ADDest nombres et ensembles uniquement. Il ne touche pas aux chaînes ni aux booléens ; ça, c'estSET. Voir types de données DynamoDB pour le modèle d'attributs complet.
Étapes suivantes
Les compteurs atomiques sont un pattern d'écriture ; la façon dont tu relis les agrégats
est une question de modélisation — voir single-table design
pour garder les éléments de stats à côté de leur parent, et
Query vs Scan pour que l'agrégation d'un compteur shardé reste un
Query.
Rédige et copie l'incrément dans le DynamoDB Expression Builder, puis essaie DynoTable pour exécuter des mises à jour atomiques contre tes propres tables et regarder les comptes bouger.