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/SKdans ton code (par ex.CONFIG#GLOBAL) au lieu de templater un id d'utilisateur ou de commande. - Lis-le avec
GetItem, jamaisScan. 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 :
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_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.
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-
Putl'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 singleton | Item par entité | |
|---|---|---|
| Clé | Constante codée en dur (SETTINGS#APP) | Templatée avec un id (USER#42) |
| Combien | Exactement un | Un par utilisateur / commande / locataire |
| Lecture type | GetItem sur la clé connue | GetItem ou Query par entité |
| Portée | Toute l'application | Une seule entité |
| À utiliser pour | Flags globaux, config, version système | Profils, 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
Scanpour 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.