Relations un-à-plusieurs dans DynamoDB
Un plan de contrôle SaaS a presque toujours une hiérarchie de conteneurs : un
workspace possède de nombreux projets. En SQL, tu poserais une clé
étrangère workspace_id sur la table des projets, puis un JOIN.
DynamoDB n'a ni jointures ni clés étrangères, donc la relation doit vivre dans le
schéma de clés lui-même. Bien fait, « charger un workspace et chaque projet
qu'il contient » devient un seul Query au lieu d'une lecture suivie d'un scan.
Comment modéliser une relation un-à-plusieurs dans DynamoDB ?
Donne au parent et à tous ses enfants la même pour qu'ils partagent une seule , puis différencie-les avec la clé de tri. DynamoDB n'a ni jointures ni clés étrangères, donc la relation vit dans le schéma de clés lui-même. Charger un parent plus chacun de ses enfants devient alors un seul Query au lieu d'une jointure.
- Modélise les lectures, pas les entités. La relation un-à-plusieurs n'existe que pour servir « lister les projets d'un workspace » — façonne les clés autour de cette requête.
- Encode le parent dans la de l'enfant. Donne au workspace et à tous ses projets la même valeur de clé de partition pour qu'ils atterrissent dans une seule .
- La lecture de liste devient alors un seul
Query. Le parent et ses enfants reviennent ensemble — pas de jointure, pas de second aller-retour (unQueryrenvoie jusqu'à 1 Mo par page, en paginant viaLastEvaluatedKeyau-delà). - Surveille la . Un seul locataire énorme concentre tout son trafic sur une seule partition ; un workspace gigantesque peut nécessiter une clé partitionnée et une lecture en éventail.
Le modèle d'accès, d'abord
La modélisation DynamoDB commence par le modèle d'accès, pas par les entités — la même discipline derrière la conception à table unique. Avant de choisir la moindre clé, écris les lectures que l'application émet réellement :
- Obtenir les paramètres d'un workspace.
- Lister chaque projet d'un workspace, du plus récent au plus ancien.
- Obtenir un projet précis par son id.
La relation « un workspace, plusieurs projets » ne compte que grâce à la lecture nº 2. Si tu n'avais jamais besoin de lister ensemble les projets d'un workspace, tu ne modéliserais pas la relation du tout — tu stockerais les projets indépendamment.
Donc la question n'est jamais « comment représenter le un-à-plusieurs ? » dans l'abstrait. C'est « quelles requêtes cette relation doit-elle servir ? » Réponds à ça, puis façonne les clés autour.
Pourquoi une clé étrangère n'aidera pas ici
Dans DynamoDB, chaque GetItem et chaque Query cible une clé de partition, et le
service hache cette clé pour localiser la partition qui contient l'item.
AWS le dit directement dans la doc Core Components : la valeur de la clé de partition est l'entrée d'une fonction de hachage interne qui décide où vivent les données.
Ce placement basé sur le hachage est l'héritage du papier original de 2007 Dynamo: Amazon's Highly Available Key-value Store, où le hachage cohérent distribue les clés entre les nœuds.
Un simple attribut workspace_id sur un item projet est invisible à cette
machinerie — DynamoDB ne peut pas le « suivre ».
Pour récupérer les items liés en une seule requête, l'identité du parent doit être encodée dans
la clé de partition du projet, afin que tous les items d'un workspace hachent vers la même
partition et qu'un seul Query puisse les balayer.
Exemple concret : workspaces et projets
Utilise un schéma de clés générique et surchargé. Appelle la clé de partition EntityRef et la
clé de tri Detail. L'identité du workspace va dans EntityRef pour à la fois
l'item workspace et chaque projet qu'il contient :
| EntityRef | Detail | attributes |
|---|---|---|
| WS#acme | META | displayName, region, seatLimit |
| WS#acme | PROJ#2026-0007 | title, status, createdBy |
| WS#acme | PROJ#2026-0042 | title, status, createdBy |
| WS#acme | PROJ#2026-0118 | title, status, createdBy |
| WS#globex | META | displayName, region, seatLimit |
| WS#globex | PROJ#2026-0009 | title, status, createdBy |
Le workspace et tous ses projets partagent EntityRef = "WS#acme", formant ainsi une
seule collection d'items qui vit ensemble sur une même partition.
La clé de tri Detail les sépare : META est l'enregistrement du workspace, et chaque
projet porte un préfixe PROJ# avec un id complété par des zéros et ordonné dans le temps, si bien que les projets
se trient naturellement.
Visuellement, le parent et ses enfants s'empilent à l'intérieur d'une partition, ordonnés par la clé de tri :
Un seul Query sur EntityRef = "WS#acme" balaie toute la pile — le parent plus chaque
enfant — en une seule lecture.
Maintenant, chacun des trois modèles d'accès se réduit à un seul appel :
- Paramètres du workspace —
GetItem(EntityRef="WS#acme", Detail="META"). - Lister les projets du plus récent au plus ancien —
Query(EntityRef="WS#acme")avecDetail begins_with "PROJ#", exécuté en ordre décroissant (ScanIndexForward = false). - Un projet —
GetItem(EntityRef="WS#acme", Detail="PROJ#2026-0042").
Le deuxième est tout l'intérêt : le parent et ses enfants reviennent d'un
seul Query, pas de jointure ni de second aller-retour — DynamoDB renvoie jusqu'à 1 Mo
par page et te remet un LastEvaluatedKey pour récupérer le reste. C'est le coup
que tu ne peux pas jouer avec un attribut de clé étrangère et un Scan.
Écrire cette condition begins_with à la main est fastidieux — la syntaxe de la condition de clé et de
l'expression de projection mord.
Le DynamoDB Expression Builder génère la
KeyConditionExpression, les tables de placeholders #name/:value, et un
snippet SDK prêt à l'emploi pour que tu n'aies pas à lutter contre la grammaire :
KeyConditionExpression "#er = :er AND begins_with(#d, :p)"
ExpressionAttributeNames { "#er": "EntityRef", "#d": "Detail" }
ExpressionAttributeValues { ":er": "WS#acme", ":p": "PROJ#" }
Inspecter la collection d'items dans DynoTable
Le bénéfice de cette disposition est visuel : chaque ligne partageant un EntityRef est le
workspace plus ses enfants, côte à côte.
DynoTable les regroupe pour que tu voies la relation un-à-plusieurs comme un seul bloc contigu au lieu de la deviner à travers des tables séparées.

Pièges et la forme alternative
Quelques points à surveiller :
- Partitions à chaud. Chaque item d'un workspace vit sur une seule partition, donc un
seul locataire très gros ou très actif concentre le trafic. Le comportement de
capacité adaptative
décrit par AWS absorbe un déséquilibre modéré, mais un workspace avec des millions de
projets peut nécessiter une clé partitionnée (par ex.
WS#acme#01 … #10) et une lecture en éventail. - Taille de la collection d'items. Avec un index secondaire local, la collection d'items d'une seule partition est plafonnée à 10 Go ; sans LSI, il n'y a pas de telle limite. Si tu pèses les types d'index ici, vois GSI vs LSI.
- Vise le
Query, jamais leScan. Toute la conception existe pour que tu puissesQueryune seule partition. Retomber sur unScanfiltré pour « trouver les projets d'un workspace » jette le modèle à la poubelle et lit la table entière — le piège couvert dans Query vs Scan.
Si tu as réellement besoin de lister les projets à travers les workspaces (disons, tous les
projets status = ACTIVE globalement), la table de base ne peut pas répondre à ça — sa
clé de partition est à portée de workspace.
C'est le travail d'un index secondaire qui re-partitionne les projets sur un autre attribut, pas d'un remodelage de cette relation.
Étapes suivantes
Modélise les modèles d'accès, encode le parent dans la clé de partition de l'enfant, et
la lecture un-à-plusieurs est un seul Query. Construis et valide la condition de clé
avec le DynamoDB Expression Builder — et si tu
préfères partir des modèles d'accès eux-mêmes, l'outil de Single-Table
Design gratuit ébauche le plan PK/SK/GSI avec
des items d'exemple.
Ensuite, télécharge DynoTable pour charger ce schéma, parcourir en direct la
collection d'items workspace→projets, et confirmer que chaque requête fait exactement une
lecture. Si tu préfères voir les workspaces et les projets comme une vue relationnelle
jointe, le SQL Workbench de DynoTable exécute ce JOIN aussi.


