Intermédiaire6 min de lecture

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 (ou SET 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 SELECT puis UPDATE ; ici tu sautes entièrement la lecture et l'opération reste sûre en concurrence.
  • Les compteurs atomiques ne sont pas idempotents. Un UpdateItem réessayé incrémente à nouveau. Si tu ne peux pas tolérer un sur- ou sous-comptage, utilise une .
  • ADD sur 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 :

PKSKplay_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 :oneSET play_count = play_count + :one
Attribut manquantLe crée, en partant de 0Erreur — nécessite if_not_exists
Types de donnéesNombres et ensembles seulementNombres (et plus) via SET
Combiner avec SETClause séparéeUne clause SET, séparée par virgule
Recommandation AWSConvient aux compteursDé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#TOTAL peut atteindre un plafond d'écriture par partition. Shard-le : répartis les écritures sur STATS#TOTAL#0..N et somme à la lecture.
  • Pas d'incrément par lot. BatchWriteItem est put/delete uniquement — il ne peut pas exécuter d'. Les compteurs passent par UpdateItem, un élément par appel. Si tu dois incrémenter plusieurs compteurs de façon atomique, TransactWriteItems exécute des actions Update sur jusqu'à 100 éléments en une requête, à environ le double du coût d'écriture.
  • ADD est nombres et ensembles uniquement. Il ne touche pas aux chaînes ni aux booléens ; ça, c'est SET. 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.

Mis à jour