Comment modéliser des données dans DynamoDB
En SQL, tu modélises d'abord les entités et les relations, puis tu fais confiance au planificateur de requêtes pour assembler plus tard tout ce que tu demandes. DynamoDB inverse cela. Tu modélises les lectures que tu sais déjà que tu feras, et les clés existent pour les servir.
Il n'y a pas de moteur de jointure ni de planificateur choisissant une stratégie à l'exécution. Un Query lit une seule partition le long d'une clé, et c'est là tout le contrat de performance. Tu conçois donc les clés pour des modes d'accès connus, pas pour un schéma bien rangé.
AWS le dit sans détour dans son guide des bonnes pratiques : « tu ne devrais pas commencer à concevoir ton schéma tant que tu ne connais pas les questions auxquelles il devra répondre ».
Ce guide déroule tout le processus sur un seul domaine : un classement de jeu multijoueur qui suit les joueurs, les parties qu'ils jouent et leur rang par saison. On part d'une liste de questions pour arriver à un schéma de clés fonctionnel.
Comment modéliser des données dans DynamoDB ?
Modélise d'abord les lectures, pas les tables. Liste chaque requête que fait l'application, puis conçois une et une pour que chaque question se résolve en un seul Query ou GetItem. Regroupe les items lus ensemble, parcours des plages de valeurs dans la clé de tri, et ajoute un GSI pour tout mode d'accès que la table de base ne peut pas servir.
- Liste d'abord les lectures, pas les tables. Les questions sont la spécification ; les noms communs sont une distraction.
- Chaque question doit être un seul
QueryouGetItem. Si une question nécessite unScan, le modèle est mauvais. - Les items regroupés partagent une ; tout ce que tu parcours en plage va dans la .
- Une question à laquelle la table de base ne peut pas répondre obtient un — jamais un
Scanavec un filtre.
Étape 1 — Formule le problème en questions, pas en tables
Résiste à l'envie de dessiner des tables players, matches et scores. Cet instinct est l'habitude SQL, et ici il est mauvais. Écris plutôt chaque lecture que l'application effectue réellement. Pour notre classement :
- Récupérer le profil d'un joueur par son id.
- Lister les parties récentes d'un joueur, de la plus récente à la plus ancienne.
- Afficher les N meilleurs joueurs pour une saison donnée, classés par note.
- Rechercher un joueur par son pseudo public (par ex. pour une URL de profil).
Ces quatre questions — pas les noms communs — sont la spécification. Chacune doit se résoudre en un seul Query (ou GetItem), car c'est la seule forme d'accès que DynamoDB sert à bas coût à grande échelle.
Si on ne peut répondre à une question qu'en scannant la table, le modèle est mauvais, et tu le sentiras dans la latence et le coût — voir Query vs Scan pour comprendre pourquoi un Scan est le piège à éviter.
Toute la méthode est un pipeline court et ordonné que tu exécutes une fois par domaine :
Chaque étape ci-dessous correspond à une case : lister, énumérer, concevoir les clés, ajouter des index pour le reste, puis valider.
Étape 2 — Comprends les primitives avec lesquelles tu modélises
Une table a une clé de partition (PK) qui choisit sur quelle partition physique un item vit, et une clé de tri (SK) optionnelle qui ordonne les items au sein de cette partition.
La documentation des composants de base d'AWS appelle la paire la clé primaire de l'item. Un Query cible toujours exactement une valeur de PK et peut parcourir en plage ou filtrer la SK — c'est là toute la boîte à outils.
Cette conception à partition unique est ce qui permet à DynamoDB de livrer les lectures prévisibles, à faible latence et partitionnées horizontalement décrites pour la première fois dans l'article Amazon Dynamo de 2007.
Deux conséquences guident chaque décision ci-dessous :
- Les items lus ensemble devraient partager une clé de partition pour qu'un seul
Queryles renvoie dans une seule requête facturée. - Tout ce que tu veux parcourir en plage (parties récentes, meilleures notes) doit vivre dans la clé de tri, car c'est le seul attribut qu'un
Querypeut ordonner et borner.
Quand une question a besoin d'une forme d'accès différente de celle que fournit la table de base, tu ajoutes un index secondaire global — une re-projection de la table sous une PK/SK différente.
(Pour GSI versus index secondaire local, voir GSI vs LSI.)
Étape 3 — Conçois les clés, une question à la fois
On utilise une seule table avec des attributs de clé génériques et surchargés — l'approche à table unique — parce qu'un joueur et ses parties sont lus ensemble.
Invente tes propres préfixes ; ici PLAYER#, MATCH# et SEASON# étiquettent le type d'entité à l'intérieur de clés par ailleurs génériques.
Les questions 1 et 2 (profil + parties récentes) partagent une partition, elles s'accrochent donc toutes deux à la même PK :
| partitionId | rangeId | attributes |
|---|---|---|
| PLAYER#u8231 | PROFILE | handle, region, createdAt |
| PLAYER#u8231 | MATCH#2026-06-23T14 | result=win, ratingDelta=+18, mapId |
| PLAYER#u8231 | MATCH#2026-06-23T11 | result=loss, ratingDelta=-15, mapId |
Query partitionId = "PLAYER#u8231" renvoie le profil et chaque partie en une seule lecture. Pour le profil seul, GetItem.
Pour les parties récentes, rangeId begins_with "MATCH#" avec ScanIndexForward = false les parcourt de la plus récente à la plus ancienne — l'horodatage dans la clé de tri fait le tri gratuitement.
Les questions 3 et 4 ne peuvent pas trouver de réponse dans cette partition — elles pivotent sur le rang de saison et sur le pseudo, dont aucun n'est la PK de base. Chacune obtient un GSI.
On ajoute deux paires d'attributs d'index génériques — seasonPartition / seasonSort pour l'index de rang et handlePartition / handleSort pour l'index de pseudo — renseignés sur le même item de profil (celui écrit à l'étape 3, montré à présent avec ses attributs d'index remplis) :
| partitionId | rangeId | seasonPartition | seasonSort | handlePartition | handleSort |
|---|---|---|---|---|---|
| PLAYER#u8231 | PROFILE | SEASON#2026-Q2 | RATING#1842 | HANDLE#nighthawk | PLAYER#u8231 |
Maintenant, un Query sur l'index de saison WHERE seasonPartition = "SEASON#2026-Q2" avec ScanIndexForward = false renvoie les joueurs classés par note — c'est le classement.
Un second index indexé sur handlePartition = "HANDLE#…" résout un pseudo public en un id de joueur en une seule lecture. Une seule table physique, quatre modes d'accès à Query unique.
Une note de sur
RATING#1842: DynamoDB trie les clés de tri lexicographiquement, pas numériquement, donc une note doit être complétée de zéros sur une largeur fixe (RATING#01842) sinon9trierait après1000. C'est un piège de modélisation classique qu'il vaut mieux régler d'emblée.
Étape 4 — Valide le modèle dans DynoTable
Un schéma de clés ne gagne ta confiance que lorsque tu regardes un vrai Query renvoyer exactement les items que tu attendais et rien de plus.
Ouvre la table dans DynoTable, lance la requête de classement contre l'index de saison, et confirme que la partition revient classée et bornée — pas de Scan, pas de tri côté client.

Quand tu construis les expressions de condition pour ces requêtes — le begins_with, le seasonPartition = :p, la liaison du placeholder :p — laisse le Générateur d'expressions DynamoDB le faire.
Il génère le KeyConditionExpression, les ExpressionAttributeNames et les ExpressionAttributeValues, pour qu'un mot réservé comme result ou un placeholder mal tapé ne casse jamais silencieusement une lecture.
Étape 5 — Pièges et prochaines étapes
Quelques pièges à vérifier avant de livrer le modèle :
- Ne modélise pas des relations que tu ne lis jamais ensemble. Un GSI par question est bon marché ; un GSI gaspillé est un coût récurrent. Ajoute des index à partir de la liste des questions, pas de façon spéculative.
- Surveille la chaleur des partitions. Si une PK (un joueur célèbre, une seule saison très active) absorbe la plupart du trafic, cette partition peut être limitée. Répartis les écritures avec un suffixe de partitionnement quand une clé est manifestement chaude — AWS couvre cela dans la conception de clé de partition.
- Complète de zéros et mets en ISO-8601 tout ce qui est numérique ou temporel dans une clé de tri, pour que l'ordre lexicographique corresponde à l'ordre que tu veux dire.
- Une nouvelle question = une nouvelle clé ou un nouvel index, jamais un
Scan. Quand un mode d'accès véritablement nouveau apparaît plus tard, étends les clés ; ne le masque pas avec un filtre.
Modélise d'abord les questions, conçois les clés pour que chacune soit un seul Query, puis prouve-le.
Pour prendre de l'avance sur l'étape du milieu, l'outil de Single-Table Design gratuit transforme une liste de modes d'accès comme celle-ci en plan PK/SK/GSI, avec des items d'exemple et des indications de coût.
Essaie DynoTable pour parcourir ta table, lancer ces requêtes contre la table de base et les GSI côte à côte, et voir les modes d'accès que tu as conçus renvoyer exactement ce que tu avais prévu. Et pour la question que tu n'as pas modélisée, son SQL Workbench exécute de vrais JOIN, GROUP BY et agrégats côté client.


