Intermédiaire8 min de lecture

Les items singleton dans DynamoDB

Un item singleton est un enregistrement unique avec une clé fixe codée en dur qui détient l'état de toute ton application — pas un enregistrement par utilisateur ou par commande, mais un enregistrement, point. Des feature flags, un blob de config, un coupe-circuit global : le genre de chose qu'une appli relationnelle garderait dans une table de réglages à une seule ligne.

En venant de SQL, tu prendrais une table config avec id = 1 et un SELECT * FROM config. Dans DynamoDB tu fais la même chose avec une clé de partition codée en dur — et comme tu connais toujours cette clé, tu la lis avec un GetItem, pas un Queryni unScan.

Qu'est-ce qu'un item singleton dans DynamoDB ?

Un item singleton est un enregistrement DynamoDB unique stocké sous une clé fixe codée en dur qui détient l'état global de toute ton application — des feature flags, un blob de config, une version système — plutôt qu'un enregistrement par utilisateur ou par commande. Comme tu connais toujours la clé, tu le lis avec un GetItem et tu le mets à jour avec des update expressions associées à des condition expressions.

  • Un singleton est un item avec une clé constante. Tu codes en dur le PK/SK dans ton code (par ex. CONFIG#GLOBAL) au lieu de templater un id d'utilisateur ou de commande.
  • Lis-le avec GetItem, jamais Scan. Tu connais toujours la clé complète, donc une lecture ponctuelle est une RCU cohérente et prévisible — pas de filtre, pas de parcours de table.
  • C'est une clé chaude par définition. Chaque requête peut toucher la même partition, donc mets-la en cache et garde l'item petit ; n'en fais pas un goulot d'étranglement en écriture.
  • Mute-le en toute sécurité avec des update + condition expressions, pas avec du read-modify-write dans ton appli — c'est là que vit la course de mise à jour perdue.

Reconnaître le pattern

Tu as un état global quand les données ne sont rattachées à aucune entité particulière. Quelques indices :

  • Un flag identique pour tout le monde (signup_enabled = false).
  • Un blob de réglages que ton appli lit au démarrage (limites de débit, quotas par défaut).
  • Un compteur ou un numéro de version pour tout le système, pas par enregistrement.

Tout ce qui est rattaché à un utilisateur, un locataire ou une commande n'est pas un singleton — c'est un item ordinaire clé par l'id de cette entité. Le singleton est la part globale restante qui n'a nulle part ailleurs où vivre.

Donne-lui une clé constante

Tout le pattern repose sur une décision : la clé est un littéral, pas un template. Pour un item de feature flags global dans une table unique surchargée, choisis un préfixe fixe et une valeur fixe :

PKSKattributes
SETTINGS#APPFLAGS#V1signup_enabled, maintenance_mode, ai_search_enabled

PK = "SETTINGS#APP" et SK = "FLAGS#V1" sont gravés dans le code. Pas d'id utilisateur, pas d'id de locataire — l'application demande exactement cet item à chaque fois. Cette prévisibilité est le but : une clé connue est un GetItem, et un GetItem est la lecture la moins chère et la plus cohérente que DynamoDB propose.

Le suffixe V1 est délibéré. Si le schéma du flag change de forme plus tard, tu écris un item FLAGS#V2 et tu bascules les lecteurs dessus, au lieu de muter le vivant sur place. Versionner la clé du singleton t'achète une couture de migration propre.

Lis-le avec GetItem

Comme la clé est entièrement connue, tu ne fais jamais de Query ni de Scan pour un singleton. Un Scan lit toute la table et filtre côté client — le classique piège du Scan — et c'est de la démesure absurde pour récupérer un enregistrement que tu peux adresser directement.

Un GetItem contre SETTINGS#APP / FLAGS#V1 renvoie les flags en une seule lecture fortement cohérente ou à cohérence à terme. AWS facture un GetItem d'un item ≤ 4 Ko à 0,5 RCU en cohérence à terme ou 1 RCU en cohérence forte (doc AWS sur la capacité de lecture/écriture). Garde le singleton petit et ce coût reste plat à jamais.

Le chemin de lecture est simplement : l'appli démarre ou une requête arrive, tu fais GetItem sur la clé fixe, tu mets le résultat en cache. Voici le flux.

ouinonAppli / requêteGetItem PK=SETTINGS#APPSK=FLAGS#V1Item trouvé ?Utiliser les flags, cacherlocalementRevenir à des valeurs par défautsûres

La clé fixe transforme une recherche globale en une seule lecture ponctuelle avec un chemin par défaut intégré.

Note la branche non : un singleton manquant ne devrait jamais te faire planter. Reviens à la valeur sûre (feature désactivée, maintenance activée) pour qu'un trou de premier déploiement ou une mauvaise clé échoue en mode fermé, pas ouvert.

Mets-le à jour sans course

Le piège est de mettre à jour un singleton avec du read-modify-write dans ton appli : tu fais GetItem sur les flags, tu en bascules un en mémoire, puis tu fais PutItem du tout en retour. Deux écrivains concurrents lisent tous deux l'ancien item et le second Put écrase le changement du premier. Mise à jour perdue.

Deux fonctionnalités DynamoDB tuent la course sans verrouillage côté appli :

  • Les update expressions mutent un attribut côté serveur, laissant le reste intact. Pas besoin de re-Put l'item entier.
  • Les condition expressions font que l'écriture ne réussit que si l'item ressemble toujours à ce que tu attends, donc une écriture périmée est rejetée avec ConditionalCheckFailedException (doc AWS sur les condition expressions).

Pour basculer un flag, cible uniquement cet attribut avec un SET et protège-le avec une incrémentation de version pour que les écrivains concurrents ne se piétinent pas :

# UpdateItem
Key                  PK=SETTINGS#APP  SK=FLAGS#V1
UpdateExpression     SET signup_enabled = :on, schema_version = :next
ConditionExpression  schema_version = :current

Si deux écrivains entrent en course, le contrôle schema_version = :current du second échoue et il réessaie contre la valeur fraîche. Tu peux échafauder les noms, les valeurs et cette forme exacte d'expression dans le constructeur d'expressions DynamoDB avant de le câbler dans le code. Pour un regard plus approfondi sur les opérateurs, vois le guide des idiomes d'update expression.

Attention à la clé chaude

Un singleton est, par construction, une clé chaude — chaque partie de ton appli peut lire la même partition. C'est bien pour les lectures si tu caches, mais c'est le seul vrai risque du pattern.

  • Cache agressivement. Lis les flags une fois par processus (ou toutes les N secondes), pas à chaque requête. La valeur du singleton est la chose la moins chère à mémoïser.
  • N'en fais pas un point chaud en écriture. Un flag basculé par un admin quelques fois par jour n'est rien. Un singleton que tu incrémentes à chaque requête est un goulot de débit de partition — c'est un problème de compteur, pas un singleton.
  • Garde-le petit. Le coût de lecture évolue avec la taille de l'item par blocs de 4 Ko. Un blob de config gonflé rend chaque démarrage plus cher qu'il ne le devrait.

Si tu as réellement besoin d'un compteur global à fort taux d'écriture, le singleton est la mauvaise forme — shard-le sur N items et somme à la lecture. C'est un pattern différent.

Singleton vs item par entité

La ligne de démarcation est simplement ce à quoi les données sont rattachées.

Item singletonItem par entité
CléConstante codée en dur (SETTINGS#APP)Templatée avec un id (USER#42)
CombienExactement unUn par utilisateur / commande / locataire
Lecture typeGetItem sur la clé connueGetItem ou Query par entité
PortéeToute l'applicationUne seule entité
À utiliser pourFlags globaux, config, version systèmeProfils, commandes, tout ce qui est par-id

Si tu te surprends à vouloir deux singletons du même genre, tu n'as pas un singleton — tu as un item par entité et l'entité est la chose par laquelle tu as oublié de clé (de la config par locataire, disons).

Pièges et étapes suivantes

  • Ne fais pas de Scan pour le trouver. Tu connais la clé ; adresse-la directement.
  • Ne fais pas de read-modify-write. Utilise des update + condition expressions.
  • Ne le laisse pas disparaître silencieusement. Reviens à la valeur sûre sur un cache miss.
  • Ne le surcharge pas d'écritures à haute fréquence. C'est un travail de compteur shardé.

Le singleton vit confortablement dans un single-table design — c'est juste une collection d'items de plus avec une clé fixe à côté de tes enregistrements d'entité.

Essaie DynoTable pour parcourir ta table, trouver l'enregistrement singleton par sa clé fixe et éditer les flags à la main pendant que tu construis le chemin d'écriture.

Mis à jour