Fortgeschritten7 Min. Lesezeit

One-to-Many-Relationships in DynamoDB

Eine SaaS-Control-Plane hat fast immer eine Containment-Hierarchie: ein Workspace besitzt viele Projects. In SQL würdest du einen workspace_id- Foreign Key auf die Projects-Tabelle legen und JOINen.

DynamoDB hat keine Joins und keine Foreign Keys, also muss die Relationship im Key Schema selbst leben. Richtig gemacht wird „lade einen Workspace und jedes Project darin“ zu einer einzelnen Query statt einem Read plus Follow-up-Scan.

Wie modellierst du eine One-to-Many-Relationship in DynamoDB?

Gib dem Parent und allen seinen Children denselben , sodass sie eine teilen, und differenziere sie mit dem Sort Key. DynamoDB hat keine Joins oder Foreign Keys, also lebt die Relationship im Key Schema selbst. Einen Parent plus jedes Child zu laden wird dann eine einzelne Query statt eines Joins.

  • Modelliere die Reads, nicht die Entities. Die One-to-Many-Relationship existiert nur, um „liste die Projects eines Workspace“ zu bedienen — forme die Keys um diese Query.
  • Kodiere den Parent in den des Child. Gib dem Workspace und all seinen Projects denselben Partition-Key-Wert, sodass sie in einer landen.
  • Dann ist der List-Read eine Query. Parent plus seine Children kommen zusammen zurück — kein Join, kein zweiter Round Trip (eine Query gibt bis zu 1 MB pro Page zurück und paginiert danach via LastEvaluatedKey).
  • Achte auf die . Ein riesiger Tenant konzentriert all seinen Traffic auf eine Partition; ein gigantischer Workspace braucht vielleicht einen geshardeten Key und einen Fan-out-Read.

Zuerst das Access Pattern

DynamoDB-Modeling ist Access-Pattern-first, nicht Entity-first — dieselbe Disziplin hinter Single-Table Design. Bevor du irgendeinen Key wählst, schreib die Reads auf, die die App wirklich ausstellt:

  • Die Settings eines Workspace holen.
  • Jedes Project in einem Workspace listen, neueste zuerst.
  • Ein spezifisches Project per Id holen.

Die „ein Workspace, viele Projects“-Relationship zählt nur wegen Read #2. Wenn du nie die Projects eines Workspace zusammen listen müsstest, würdest du die Relationship gar nicht modellieren — du speicherst Projects unabhängig.

Die Frage ist also nie abstrakt „wie repräsentiere ich One-to-Many?“. Sie ist „welche Queries muss diese Relationship bedienen?“ Beantworte das, dann forme die Keys darum.

Warum ein Foreign Key hier nicht hilft

In DynamoDB targetet jedes GetItem und jede Query einen Partition Key, und der Service hasht diesen Key, um die Partition zu lokalisieren, die das Item hält.

AWS sagt das direkt in den Core Components-Docs: der Partition-Key-Wert ist Input einer internen Hash-Funktion, die entscheidet, wo Daten leben.

Diese Hash-basierte Platzierung ist die Erbschaft aus dem originalen 2007-Dynamo: Amazon's Highly Available Key-value Store-Paper, wo Consistent Hashing Keys über Nodes verteilt.

Ein nacktes workspace_id-Attribut auf einem Project-Item ist für diese Maschinerie unsichtbar — DynamoDB kann ihm nicht „folgen“.

Um related Items in einem Request zu fetchen, muss die Identität des Parent in den Partition Key des Project kodiert sein, sodass alle Items eines Workspace zur selben Partition hashen und eine Query sie fegen kann.

Praxisbeispiel: Workspaces und Projects

Nutze ein generisches, überladenes Key Schema. Nenne den Partition Key EntityRef und den Sort Key Detail. Die Workspace-Identität geht für beide — das Workspace-Item und jedes Project darunter — in EntityRef:

EntityRefDetailattributes
WS#acmeMETAdisplayName, region, seatLimit
WS#acmePROJ#2026-0007title, status, createdBy
WS#acmePROJ#2026-0042title, status, createdBy
WS#acmePROJ#2026-0118title, status, createdBy
WS#globexMETAdisplayName, region, seatLimit
WS#globexPROJ#2026-0009title, status, createdBy

Der Workspace und all seine Projects teilen EntityRef = "WS#acme", also bilden sie eine einzelne Item Collection, die zusammen auf einer Partition lebt.

Der Detail-Sort-Key trennt sie: META ist der Workspace-Record, und jedes Project trägt ein PROJ#-Prefix mit einer zero-padded, zeitgeordneten Id, sodass Projects natürlich sortieren.

Visuell stapeln Parent und Children in einer Partition, geordnet nach Sort Key:

Partition: EntityRef = WS#acmeMETA Workspace-SettingsPROJ#2026-0007PROJ#2026-0042PROJ#2026-0118

Eine Query auf EntityRef = "WS#acme" fegt den ganzen Stack — Parent plus jedes Child — in einem einzelnen Read.

Jetzt kollabiert jedes der drei Access Patterns zu einem Call:

  • Workspace-SettingsGetItem(EntityRef="WS#acme", Detail="META").
  • Projects neueste zuerst listenQuery(EntityRef="WS#acme") mit Detail begins_with "PROJ#", in absteigender Ordnung (ScanIndexForward = false).
  • Ein ProjectGetItem(EntityRef="WS#acme", Detail="PROJ#2026-0042").

Das zweite ist der Punkt. Parent und Children kommen aus einer Query zurück, kein Join und kein zweiter Round Trip — DynamoDB gibt bis zu 1 MB pro Page zurück und reicht dir einen LastEvaluatedKey für den Rest. Das ist der Move, den du mit einem Foreign-Key-Attribut und einem Scan nicht machen kannst.

Diese begins_with-Condition von Hand zu schreiben ist fiddly — Key-Condition- und Projection-Expression-Syntax beißen.

Der DynamoDB Expression Builder generiert die KeyConditionExpression, die #name/:value-Placeholder-Maps und ein ready-to- run SDK-Snippet, sodass du nicht mit der Grammar kämpfst:

KeyConditionExpression     "#er = :er AND begins_with(#d, :p)"
ExpressionAttributeNames   { "#er": "EntityRef", "#d": "Detail" }
ExpressionAttributeValues  { ":er": "WS#acme", ":p": "PROJ#" }

Die Item Collection in DynoTable inspizieren

Jede Row, die einen EntityRef teilt, ist der Workspace plus seine Children, nebeneinander.

DynoTable gruppiert sie, sodass du die One-to-Many-Relationship als einen zusammenhängenden Block siehst, statt sie über separate Tabellen zu erraten.

Das Workspace-META-Item und seine PROJ#-Children als eine Item Collection in DynoTables Table-View gruppiert.
Das Workspace-META-Item und seine PROJ#-Children als eine Item Collection in DynoTables Table-View gruppiert.

Fallstricke und die alternative Form

Ein paar Dinge zum Beobachten:

  • Hot Partitions. Jedes Item für einen Workspace lebt auf einer Partition, also konzentriert ein einzelner sehr großer oder sehr busy Tenant Traffic. Das Adaptive Capacity-Verhalten, das AWS beschreibt, absorbiert moderate Skew, aber ein Workspace mit Millionen Projects braucht vielleicht einen geshardeten Key (z. B. WS#acme#01 … #10) und einen Fan-out-Read.
  • Item-Collection-Größe. Mit einem Local Secondary Index ist die Item Collection einer einzelnen Partition bei 10 GB capped; ohne LSI gibt es kein solches Limit. Wenn du hier Index-Typen abwägst, siehe GSI vs LSI.
  • Greif zu Query, nie Scan. Das ganze Design existiert, damit du eine Partition Queryen kannst. Auf einen gefilterten Scan zurückzufallen, um „Projects eines Workspace zu finden“, wirft das Modell weg und liest die ganze Tabelle — die Falle in Query vs Scan.

Wenn du Projects wirklich über Workspaces listen musst (z. B. alle status = ACTIVE Projects global), kann die Base Table das nicht beantworten — ihr Partition Key ist workspace-scoped.

Das ist ein Job für einen Secondary Index, der Projects auf einem anderen Attribut re-partitioniert, nicht dafür, diese Relationship umzuformen.

Nächste Schritte

Modelliere die Access Patterns, kodiere den Parent in den Partition Key des Child, und der One-to-Many-Read ist eine einzelne Query. Baue und validiere die Key Condition mit dem DynamoDB Expression Builder — und wenn du lieber von den Access Patterns selbst starten willst, draftet das freie Single-Table-Design-Tool den PK/SK/GSI-Plan mit Example Items.

Dann lade DynoTable herunter, um dieses Schema zu laden, die Workspace→Projects-Item-Collection live zu browsen und zu bestätigen, dass jede Query genau einen Read macht. Wenn du Workspaces und Projects lieber als joined, relationale View sehen willst, führt DynoTables SQL Workbench diesen JOIN auch.

Aktualisiert

Probiere dieses Design interaktiv aus

Skizziere deine Entitäten und Zugriffsmuster im kostenlosen DynamoDB Single-Table Design Tool — es schlägt PK/SK-Schlüsselvorlagen vor, zeigt eine Vorschau der Item-Collections und zeigt, welche Muster einen GSI brauchen.

Single-Table Design Tool öffnen