Profi6 Min. Lesezeit

Key Overloading in DynamoDB

Aus SQL kommend bedeutet eine Spalte für immer eine Sache: orders.created_at ist immer ein Datum, users.email ist immer eine E-Mail. Key Overloading wirft das über Bord. Du gibst dem Partition und dem Sort Key generische Namen — pk, sk — und lässt jeden Item-Typ eine andere Bedeutung hineingießen. Eine Tabelle, viele Entitäten, eine Form.

Was ist Key Overloading in DynamoDB?

Key Overloading bedeutet, viele Entitätstypen unter generischen Key-Namen wie pk/sk in einer einzigen Tabelle zu speichern, wobei der Typ im Wert kodiert wird (USER#u_3001, INVOICE#2026-0014). Der Attributname bleibt neutral, sodass Benutzer, Rechnungen und Events dieselbe Partition teilen; der Wert trägt den Typ, und ein Sort-Key-Präfix erlaubt es, mit einem einzigen Query und begins_with jede Entität gezielt abzuschneiden.

  • Generische Key-Namen, typisierte Werte. Benenne deine Keys pk/sk und packe den Entitätstyp in den Wert: pk = "TENANT#acme", sk = "USER#u_3001". Der Name ist dumm; der Wert trägt den Typ.
  • Es ist das, was Single-Table-Design funktionieren lässt. Ohne Overloading ist eine geteilte Tabelle nur eine Kramschublade. Mit ihm sitzt jede Entität in einer Partition, die du per Query abfragen kannst.
  • begins_with ist der Lohn. Ein Typ-Präfix auf dem Sort Key lässt einen Query eine ganze Entität oder eine Scheibe davon ziehen, ohne Scan und ohne Filter.
  • Der Preis: Lesbarkeit. Ein roher pk/sk-Dump sagt dir nichts. Du brauchst einen Viewer, der die Präfixe dekodiert, sonst schielst du auf Strings.

Warum generische Namen echte schlagen

DynamoDB hat genau zwei Key-Attribute pro Tabelle, und ein Query kann nur einen einzigen Partition Key anvisieren. Wenn du also deinen Key userId nennst, können nur User-Items sauber in dieser Tabelle leben — alles andere muss eine userId vortäuschen oder in eine eigene Tabelle umziehen.

Overloading umgeht das. Ein neutraler Name wie pk legt sich auf keine Entität fest, also können ein User, eine Rechnung und ein Audit-Event sich alle dasselbe Key-Attribut und dieselbe Tabelle teilen. Der Wert, nicht der Attributname, sagt, was das Item ist.

Das ist der Zug, der Single-Table-Design von der Theorie in etwas verwandelt, das du tatsächlich abfragen kannst. Die geteilte Tabelle ist der Container; Overloading ist, was verschiedenen Entitäten erlaubt, darin zu koexistieren.

Ein Multi-Tenant-Beispiel

Angenommen, du betreibst ein SaaS-Billing-Produkt. Jeder Mandant hat Mitglieder, Rechnungen und einen Audit-Trail. Statt drei Tabellen packe alles in eine und überlade die Keys:

pkskattributes
TENANT#acmeMETAname="Acme Inc", plan="team"
TENANT#acmeUSER#u_3001email, role="admin"
TENANT#acmeUSER#u_3002email, role="member"
TENANT#acmeINVOICE#2026-0014amount_cents, status="paid"
TENANT#acmeINVOICE#2026-0015amount_cents, status="open"
TENANT#acmeEVENT#2026-06-23T09:12Zactor="u_3001", action="invite"

Jede Zeile teilt pk = "TENANT#acme", also bilden sie eine Item Collection — alle ko-lokalisiert, alle in einem Partitions-Lesevorgang erreichbar.

Partition: TENANT#acmesk: METAsk: USER#u_3001sk: INVOICE#2026-0015sk: EVENT#2026-06-23T09:12ZEin Query

Das Sort-Key-Präfix leistet die eigentliche Arbeit. Es gruppiert Entitäten und ordnet sie.

Frage die überladene Collection ab

Weil der Typ im Sort-Key-Präfix liegt, zerschneidet begins_with die Partition nach Entität, ohne irgendetwas zu scannen:

Query pk = "TENANT#acme"  -- der ganze Mandant, jeder Typ
Query pk = "TENANT#acme" AND begins_with(sk, "USER#")  -- nur Mitglieder
Query pk = "TENANT#acme" AND begins_with(sk, "INVOICE#")  -- nur Rechnungen

Du zahlst nur für die Items, die die Bedingung trifft, nicht für die ganze Partition — das Gegenteil eines gefilterten Scan, bei dem du dafür zahlst, Zeilen zu lesen, die du dann wegwirfst. AWS nennt das eine Key- Condition; sie läuft auf den Keys, bevor irgendwelche Daten die Partition verlassen.

Wenn du diese begins_with-Bedingung von Hand baust, mach die Typ-Tags richtig — ein verirrtes USERS# statt USER# liefert nichts zurück, stillschweigend. Der Expression Builder generiert die KeyConditionExpression und die ExpressionAttributeValues-Map, sodass die Präfixe zu dem passen, was du tatsächlich geschrieben hast.

Überlade auch den Index

Derselbe Kniff gilt für einen GSI. Gib ihm generische Key-Namen — gsi1pk, gsi1sk — und lass jede Entität schreiben, was sie braucht. Ein Index beantwortet dann Muster, die die Basistabelle nicht kann.

pkskgsi1pkgsi1sk
TENANT#acmeINVOICE#2026-0015STATUS#open2026-06-30
TENANT#acmeINVOICE#2026-0014STATUS#paid2026-06-12
TENANT#betaINVOICE#2026-0099STATUS#open2026-06-25

Jetzt listet Query gsi1 WHERE gsi1pk = "STATUS#open" jede offene Rechnung über alle Mandanten hinweg auf, nach Fälligkeitsdatum geordnet — eine partitionsübergreifende Ansicht, die die mandanten-bezogenen Keys der Basistabelle nie bedienen könnten. Eine andere Entität kann gsi1 mit ihrer eigenen Bedeutung wiederverwenden (etwa gsi1pk = "ROLE#admin"), sodass ein Index mehrere Lesevorgänge abdeckt. Denk nur daran, dass ein GSI letztendlich konsistent ist — seine Schreibvorgänge hinken der Basistabelle hinterher.

Mach es in DynoTable

Rohe überladene Keys sind feindlich zu lesen: INVOICE#2026-0015 und EVENT#2026-06-23T09:12Z verschwimmen in einer flachen Liste. Ein Viewer, der nach Partition gruppiert und die Präfixe hervorhebt, verwandelt die Kramschublade zurück in Entitäten.

DynoTable beim Durchsuchen der Item Collection eines Mandanten — META-, USER-, INVOICE- und EVENT-Items, gruppiert unter einem einzigen überladenen Partition Key.
DynoTable beim Durchsuchen der Item Collection eines Mandanten — META-, USER-, INVOICE- und EVENT-Items, gruppiert unter einem einzigen überladenen Partition Key.

Fallstricke

  • Wähle Trennzeichen einmal und ändere sie nie. # ist die Konvention. # und : über Entitäten zu mischen bricht begins_with auf Weisen, vor denen dich nichts warnt.
  • Überlade keine Werte, die Bereichsmathematik brauchen. Ein Sort Key von INVOICE#2026-0015 sortiert lexikalisch, nicht numerisch — zero-padde IDs und verwende ISO-8601-Daten, sodass die String-Reihenfolge zur gemeinten Reihenfolge passt.
  • Reserviere den Präfix-Namensraum. Zwei Entitätstypen, die beide mit USER beginnen (etwa USER# und USERGROUP#), kollidieren unter begins_with(sk, "USER"). Mache Präfixe vom ersten Zeichen an eindeutig.
  • Plane den Lesevorgang vor den Keys. Overloading bedient Zugriffsmuster, die du aufgezählt hast. Wenn du deine Lesevorgänge noch nicht kennst, lies zuerst Single-Table-Design — die Keys sind den Queries nachgelagert.

Kartiere eine Partition, dann lade DynoTable herunter, um deine eigenen überladenen Keys zu durchsuchen und zu beobachten, wie ein Query einen ganzen Mandanten auf einmal zurückzieht.

Aktualisiert