Single-Table-Design in DynamoDB
Aus SQL kommend ist der Instinkt eine Tabelle pro Entität: customers, orders,
order_items. In DynamoDB ist dieser Instinkt meist falsch. Eine einzelne Tabelle,
die jede Entität speichert, unterschieden durch überladene Schlüsselpräfixe, lässt
dich einen Elter und seine Kinder in einem Query holen — keine Joins, kein N+1.
Von den Zugriffsmustern ausgehen, nicht von den Entitäten
Single-Table-Design ist zugriffsmuster-zuerst. Bevor du einen einzelnen Schlüssel
wählst, schreibe jeden Lesevorgang auf, den deine App macht — „Profil eines Kunden
holen“, „Bestellungen eines Kunden, neueste zuerst, auflisten“, „alle offenen
Bestellungen finden“ — denn die Schlüssel existieren nur, um diese Liste zu bedienen.
Relationale Normalisierung optimiert Speicher; DynamoDB-Modellierung optimiert die
Abfragen, von denen du bereits weißt, dass du sie ausführen wirst. Zähle sie auf, und
gestalte dann Schlüssel so, dass jede ein einzelnes Query ist.
Die Idee
Wähle generische Schlüsselnamen (PK, SK) und kodiere den Entitätstyp in den Wert:
| PK | SK | attributes |
|---|---|---|
| CUSTOMER#42 | PROFILE | name, email, plan |
| CUSTOMER#42 | ORDER#2026-001 | total, status |
| CUSTOMER#42 | ORDER#2026-002 | total, status |
Jetzt liefert ein Query PK = "CUSTOMER#42" das Profil und jede Bestellung in
einem berechneten Lesevorgang. SK begins_with "ORDER#" verengt es auf nur die
Bestellungen.
Visuell stapeln sich die überladenen Items unter einem Partition Key als eine einzige Item-Collection:
Ein Lesevorgang der Partition gibt den Kunden und jede Bestellung zusammen zurück.
Überladene GSIs
Derselbe Trick funktioniert auf Indizes. Setze einen generischen GSI1PK/GSI1SK auf
Items, und ein einzelner GSI bedient mehrere Zugriffsmuster, je nachdem, was jedes Item
in diese Attribute schreibt:
| PK | SK | GSI1PK | GSI1SK |
|---|---|---|---|
| ORDER#001 | METADATA | STATUS#OPEN | 2026-01-04 |
| ORDER#002 | METADATA | STATUS#OPEN | 2026-01-05 |
Jetzt listet Query GSI1 WHERE GSI1PK = "STATUS#OPEN" offene Bestellungen nach Datum —
ein Muster, das die Basistabelle nicht beantworten kann. Eine andere Entität kann
GSI1 mit eigener Bedeutung wiederverwenden (z. B. CATEGORY#books). Ein Index, viele
Abfragen.
Many-to-Many: die Adjazenzliste
Für Beziehungen (ein Benutzer in vielen Teams, ein Team mit vielen Benutzern) schreibe
die Kante zweimal mit getauschten IDs: PK=USER#1, SK=TEAM#9 und
PK=TEAM#9, SK=USER#1. Eine der Seiten abzufragen listet die andere — der
DynamoDB-Ersatz für eine Join-Tabelle.
Wann nicht Single-Table
Es ist nicht gratis. Eine überladene Tabelle ist schwerer zu durchdenken, schwerer weiterzuentwickeln und analytikfeindlich. Wenn deine Zugriffsmuster wirklich unbekannt sind oder sich ständig ändern, oder die Daten überwiegend analytisch sind, können separate Tabellen (oder ein anderer Store) die vernünftigere Wahl sein. Single-Table gewinnt, wenn die Muster bekannt und hochvolumig sind.
Kosten der falschen Form
Als separate Tabellen zu modellieren erzwingt einen Scan oder clientseitigen Join,
um einen Kunden wieder zusammenzusetzen, und das ist die
Scan-Fußangel. Modelliere die Zugriffsmuster zuerst, und
gestalte dann Schlüssel so, dass jedes ein Query ist.
Schätze, was diese Items pro Lesevorgang kosten, mit dem Item-Größen- & Kapazitätsrechner, und probiere DynoTable, um ein Single-Table-Schema zu durchsuchen und die überladenen Collections nebeneinander zu sehen.