Fortgeschritten7 Min. Lesezeit

Many-to-Many-Relationships in DynamoDB

Ein Student schreibt sich in viele Courses ein; ein Course hält viele Students. In SQL greifst du nach einer Join-Tabelle und einem Three-Table-JOIN.

DynamoDB hat keine Joins, also muss die Relationship in den Keys leben — und der Trick ist, jede Enrollment-Edge in einer Form zu speichern, die beide Seiten direkt Queryen können.

Dieser Guide läuft das Students ↔ Courses-Problem end to end: die Access Patterns, das -Pattern, das sie löst, ein originales Key Schema zum Kopieren und wie du beide Richtungen zurückliest, ohne je die Tabelle zu scannen.

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

DynamoDB hat keine Joins, also modellierst du Many-to-Many mit dem -Pattern: speichere jeden Link als eigenes Edge- Item, keyed by einer Seite, und füge dann einen invertierten GSI hinzu, der die Keys tauscht. Eine einzelne Edge, einmal geschrieben, beantwortet dann Queries aus beiden Richtungen günstig.

  • Speichere jede Enrollment als eigenes Edge-Item, nicht als List-Attribut auf einer Seite.
  • Keye die Edge nach dem Student (PK = STU#…, SK = ENROLL#CRS#…), sodass eine Query die ganze Course-Liste eines Students zurückgibt.
  • Füge einen invertierten hinzu, der die Rollen tauscht (GSI1PK = CRS#…), sodass dieselbe Edge auch „wer ist in diesem Course?“ beantwortet.
  • Eine Edge, einmal geschrieben, liest günstig beide Wege — das ist das ganze Spiel.

Zuerst die Access Patterns framen

DynamoDB-Modeling ist Access-Pattern-first: du entscheidest die Reads, bevor du einen einzigen Attributnamen wählst. Eine Many-to-Many-Relationship hat fast immer zwei symmetrische Reads plus die Entity-Lookups:

  • Das Profil eines Students holen und jeden Course listen, in dem der Student enrolled ist.
  • Die Metadata eines Course holen und jeden Student listen, der in dem Course enrolled ist.
  • Eine einzelne Enrollment-Edge nachschlagen — um eine Grade zu updaten oder den Course zu droppen.

Die zwei List-Reads zeigen in entgegengesetzte Richtungen über dieselbe Menge Edges. Ein naives Design bedient eine günstig und erzwingt einen Scan für die andere — genau der Fallstrick in Query vs Scan.

Der Job ist, beide Richtungen zu einer einzelnen Query zu machen.

Das Adjacency-List-Pattern nutzen

DynamoDBs eigene Guidance für Relationships ist die Adjacency List: modelliere jede Relationship als Item, dessen Partition Key ein Endpoint ist und dessen Sort Key der andere.

AWS dokumentiert das auf der Seite Best Practices for Managing Many-to-Many Relationships des DynamoDB Developer Guide.

Warum Keys und keine zweite Tabelle? Weil das Primitive, das DynamoDB dir gibt, eine Query gegen eine einzelne Partition ist.

Eine Query liest eine contiguous Range von Sort-Key-Werten unter einem Partition Key in einer abgerechneten Operation — das ist der einzige „Join“, den die Engine bietet.

Um eine Relationship zu bekommen, die von beiden Seiten günstig liest, duplizierst du die Edge: schreibe sie einmal keyed by dem Student, dann nutze einen Secondary Index, um dieselbe Edge keyed by dem Course zu projeizieren.

Das ist das Overloaded-Key-Thinking aus Single-Table Design, angewandt auf eine Relationship statt einer Parent-Child-Hierarchie.

Die Form sind zwei gestapelte Views derselben Edge — die Base Table keyed by Student, der invertierte GSI keyed by Course:

dieselbe Edge, Keys getauschtdieselbe Edge, Keys getauschtInvertierter GSI1 keyed by CourseGSI1PK CRS#math204GSI1SK STU#a91GSI1PK CRS#cs101GSI1SK STU#a91Base Table keyed by StudentPK STU#a91SK ENROLL#CRS#math204PK STU#a91SK ENROLL#CRS#cs101

Jede Edge wird einmal auf der Base Table geschrieben und mit getauschten Keys in den GSI projeziert, sodass eine Query gegen entweder Partition die Relationship günstig liest.

Die Lineage geht zurück zum 2007 Amazon Dynamo Paper: der Partition Key ist die Einheit der Distribution, und Single-Key-Access ist der Fast Path.

Relationships in DynamoDB sind eine Übung darin, Many-to-Many-Reads in diesen Fast Path zu biegen.

Das Beispiel durcharbeiten: Students ↔ Courses

Nutze eine Tabelle mit generischen Keys, PK und SK, und kodiere den Entity-Typ im Wert. Die Enrollment-Edge ist das Herz davon:

PKSKattributes
STU#a91PROFILEname, year, major
STU#a91ENROLL#CRS#math204 enrolledOn, grade
STU#a91ENROLL#CRS#cs101enrolledOn, grade
CRS#math204METADATAtitle, credits, term
CRS#cs101METADATAtitle, credits, term

Eine einzelne Query PK = "STU#a91" gibt das Profil des Students und jede Enrollment in einem Read zurück. Verenge sie mit SK begins_with "ENROLL#", um nur die Course-Edges zu bekommen. Das löst „liste die Courses eines Students“.

Aber „liste die Students eines Course“ zeigt die andere Richtung — und die Base Table kann das nicht beantworten, weil die Student-Id im Partition Key steckt, nicht im Sort Key.

Füge einen invertierten Global Secondary Index hinzu, der die Rollen tauscht. Gib den Edge-Items ein generisches GSI1PK/GSI1SK-Paar, das den Course auf der Partition-Seite und den Student auf der Sort-Seite hält:

PKSKGSI1PKGSI1SK
STU#a91ENROLL#CRS#math204CRS#math204STU#a91
STU#b30ENROLL#CRS#math204CRS#math204STU#b30
STU#a91ENROLL#CRS#cs101CRS#cs101STU#a91

Jetzt listet Query GSI1 WHERE GSI1PK = "CRS#math204" jeden Student in diesem Course — den Read, den die Base Table nicht bedienen konnte. Ein Edge-Item, einmal geschrieben, beantwortet beide Richtungen.

Es muss ein GSI sein, kein LSI: die Course-Partition ist völlig anders als die Student-Partition, und ein LSI teilt den Partition Key der Base Table.

Der Index spannt mehrere Partitionen, also muss er global sein — siehe GSI vs LSI.

GSIs in DynamoDB werden asynchron befüllt. Eine brandneue Enrollment kann einen Moment brauchen, um in der CRS#…-Richtung zu erscheinen.

Behandle den Course-Roster-Read als — was der Developer Guide für Global Secondary Indexes explizit nennt.

In DynoTable schreiben und lesen

Die Enrollment zu schreiben heißt, vier Key-Attribute plus die eigenen Daten der Edge zu setzen. Die Condition, die stoppt, dass ein Student sich zweimal in denselben Course einschreibt, ist ein attribute_not_exists(PK)-Guard auf dem Composite Key.

Genau diese Art Condition kannst du visuell mit dem DynamoDB Expression Builder zusammenbauen, statt ExpressionAttributeNames und Placeholder-Values von Hand zu schreiben.

In DynoTable zeigst du eine Query auf GSI1, setzt GSI1PK = "CRS#math204", und der Roster kommt als Tabelle zurück, die du lesen, sortieren und in Place editieren kannst — beide Richtungen der Relationship browsable aus einem Schema.

Den invertierten GSI in DynoTable querien, um jeden Student zu listen, der in einem Course enrolled ist.
Den invertierten GSI in DynoTable querien, um jeden Student zu listen, der in einem Course enrolled ist.

Fallstricke und nächste Schritte

  • Speichere eine Seite nicht als List-Attribut. Ein courseIds-Array auf dem Student-Item fühlt sich tidy an, bis ein Course seinen Roster braucht, das Array die 400-KB-Item-Decke trifft oder zwei Enrollments racen und einander clobbern. Diskrete Edge-Items skalieren und updaten unabhängig.
  • Halte Edge-Daten auf der Edge. grade und enrolledOn der Enrollment gehören auf das Edge-Item, nicht dupliziert auf Student oder Course — es gibt genau eine Row pro (Student, Course)-Paar zum Updaten.
  • Achte auf GSI-Propagation. Die Invertierte-Index-Richtung ist eventually consistent, also kann ein Read unmittelbar nach einer Enrollment um einen Bruchteil einer Sekunde nachhinken.
  • Projeziere nur, was der Roster braucht. Eine KEYS_ONLY- oder enge Projektion hält den GSI klein, wenn die Roster-View nur Ids braucht.

Um tiefer in die umgebenden Patterns zu gehen, lies Single-Table Design für overloaded Keys und GSI vs LSI dafür, wann der invertierte Index global sein muss. Und um von deinen eigenen Relationships zu starten, verwandelt das freie Single-Table-Design-Tool eine Access-Pattern- Liste wie „liste die Courses eines Students / liste die Students eines Course“ in einen PK/SK/GSI-Plan mit Example Items.

Dann lade DynoTable herunter, um das Students ↔ Courses-Schema für real zu modellieren — schreibe die Edges, baue die Condition mit dem Expression Builder und query beide Richtungen der Relationship ohne einen einzigen Scan. Und wenn du die klassische Three-Table-JOIN-View trotzdem willst, führt DynoTables SQL Workbench sie über deine Live-Tabellen aus.

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