Sort keys composites dans DynamoDB
Une est une partition key plus une
sort key. Le truc qui la rend puissante est ce que tu mets dans la sort key :
encode une hiérarchie comme une string délimitée, et un seul Query lit tout un
sous-arbre en ordre de tri — pas de joins, pas de récursion, pas de second
round-trip.
Comment fonctionnent les sort keys composites dans DynamoDB ?
Une sort key composite emballe une hiérarchie dans une string délimitée —
root/photos/2026/ — que DynamoDB stocke en ordre d'octets UTF-8. Parce que le
layout correspond déjà à l'arbre, un seul Query avec
begins_with(SK, "root/photos/") lit tout un sous-arbre en ordre de chemin. Pas
de joins, pas de récursion, pas de second round-trip — juste un scan de préfixe
sur une contiguë.
- La sort key est une string triable, pas juste un ID. Emballe un chemin
dedans —
root/photos/2026/— et DynamoDB stocke les items de la partition en ordre d'octets UTF-8 automatiquement. - Un délimiteur transforme les matches de préfixe en lectures de sous-arbre.
begins_with(SK, "root/photos/")renvoie chaque descendant de ce dossier en une query. - Les sort keys supportent des conditions de plage, pas des filters
arbitraires. Tu as
begins_with,between,>,<— conçois la clé pour que la lecture dont tu as besoin soit un préfixe ou une plage, pas unScan. - Le délimiteur est structurel. Choisis-en un qui ne peut pas apparaître dans un segment de chemin, sinon deux branches sans rapport collisionnent.
Pourquoi la sort key est tout le jeu
Venant de SQL, tu modéliserais un arbre de dossiers avec un self-join
parent_id et le parcourrais récursivement — une query par niveau. Dans
DynamoDB c'est un footgun N+1 contre un store key-value qui n'a pas de joins.
DynamoDB stocke chaque item sous une partition key trié par sa sort key, en ordre d'octets UTF-8 pour les strings (AWS : Query key conditions). Donc si ta sort key est le chemin, le layout physique correspond déjà à l'arbre. Une lecture devient un scan de préfixe sur une tranche contiguë — pas une marche de graphe.
C'est le shift : la sort key n'est pas un identifiant que tu matches exactement. C'est une adresse triable. Conçois-la et la query tombe pour free.
Modélise un arbre de fichiers
Disons que tu stockes des arbres de fichiers per-account. Un drive par compte est la partition naturelle ; le chemin à l'intérieur est la sort key.
| PK | SK | node_type | bytes |
|---|---|---|---|
| DRIVE#a91 | root/ | folder | - |
| DRIVE#a91 | root/docs/ | folder | - |
| DRIVE#a91 | root/docs/taxes.pdf | file | 88210 |
| DRIVE#a91 | root/photos/ | folder | - |
| DRIVE#a91 | root/photos/2026/ | folder | - |
| DRIVE#a91 | root/photos/2026/beach.jpg | file | 284910 |
| DRIVE#a91 | root/photos/2026/sunset.jpg | file | 512004 |
Deux conventions originales font le travail ici :
PK = DRIVE#<account>garde tout l'arbre d'un compte dans une seule , donc toute lecture de sous-arbre est unQuerysingle-partition.SKest le chemin complet avec un/trailing sur les dossiers. Le slash trailing est délibéré — il fait trier un dossier avant ses propres enfants et garderoot/photos/distinct d'un fichier sibling nomméroot/photos.
Lis un sous-arbre en une query
Liste tout sous root/photos/ — dossier, sous-dossiers et fichiers,
récursivement :
Query
KeyConditionExpression = PK = :drive AND begins_with(SK, :prefix)
:drive = "DRIVE#a91"
:prefix = "root/photos/"
Ça renvoie root/photos/, root/photos/2026/, beach.jpg et sunset.jpg —
en ordre de chemin, en une lecture facturée. Tu paies seulement pour les items
dans cette tranche, pas tout le drive.
Dans DynoTable, tu lances exactement ce query begins_with contre la sort key
de chemin et le dossier plus ses descendants reviennent en ordre de chemin —
pas de syntaxe de placeholder à écrire à la main.
Besoin du KeyConditionExpression brut (names, values et begins_with) pour
ton propre code ? Construis-le et copie-le dans le
DynamoDB Expression Builder.

Liste un niveau, pas tout le sous-arbre
begins_with te donne la lecture récursive. Pour un listing de répertoire
non récursif — les enfants immédiats de root/photos/ et rien de plus profond —
stocke un attribut depth et ajoute une plage de sort key plus un filter, ou
découpe le chemin dans un GSI parent. La version la plus simple : garde un
attribut parent (root/photos/) et un GSI keyed dessus.
Une sort key répond bon marché aux questions de préfixe et de plage.
« Enfants directs seulement » est une question différente — modélise-la
explicitement plutôt que d'espérer qu'un FilterExpression la rende efficace.
Un filter tourne après la lecture et tu paies pour chaque item qu'il écarte.
Choisis le délimiteur avec soin
Le délimiteur fait partie de ton contrat de données. Deux règles :
- Il ne doit jamais apparaître dans un segment de chemin. Si les filenames
peuvent contenir
/,/est le mauvais délimiteur — un fichier nomméa/best indiscernable d'un dossieratenantb. Choisis un octet réservé (certaines équipes utilisent#ou un caractère de contrôle) et interdis-le dans les segments. - Fais attention à l'ordre de tri aux frontières.
/(0x2F) trie avant les chiffres et lettres, ce qui est généralement ce que tu veux pour l'ordre d'arbre. Change le délimiteur et tu changes l'ordre — vérifie contre de vraies données.
Sort key composite vs. un attribut de tri séparé
Sort key composite (root/photos/2026/x) | Sort key ID plain + attribut parent | |
|---|---|---|
| Lecture de sous-arbre | Un query begins_with | Queries récursives (N+1) ou marche GSI |
| Ordre | Ordre de chemin, free | Doit ajouter un attribut de tri explicite |
| Move / rename | Réécrire tous les descendants | Update un pointeur parent |
| Liste enfants directs | Besoin d'attr depth ou GSI | Naturel (parent = x) |
Les clés composites gagnent quand les lectures sont en forme de sous-arbre et que l'ordre compte ; le modèle flat-ID gagne quand l'arbre mute constamment. La plupart des hiérarchies read-heavy — arbres de fichiers, arbres de catégories, org charts — penchent composite.
Pièges et étapes suivantes
- Ne sur-remplis pas la clé. Tout ce que tu encodes est immuable et indexé par préfixe seulement. Les attributs que tu queries par égalité appartiennent dans leurs propres fields ou un GSI, pas fourrés dans la sort key.
- Une sort key ne peut pas faire de
WHEREarbitraire. Seulementbegins_with,betweenet comparaisons. Si tu te surprends à tendre la main vers unFilterExpression, tu as probablement mal modélisé la clé — vois Query vs. Scan. - Aller plus loin sur le key design vit dans single-table design ; pour quand une lecture de sous-arbre a besoin d'un index au lieu du table de base, vois GSI vs. LSI.
Construis la key condition begins_with avec l'
Expression Builder, puis
télécharge DynoTable pour lancer ces queries de préfixe contre tes
propres tables et regarder un sous-arbre revenir en ordre de chemin. (Et le
self-JOIN que tu as laissé derrière en SQL ?
Le SQL Workbench de DynoTable le lance encore quand tu
en as besoin.)


