Fortgeschritten6 Min. Lesezeit

Denormalisierung in DynamoDB

Aus SQL kommend klingt Denormalisierung wie eine Sünde — duplizierte Daten, keine einzige Quelle der Wahrheit. In DynamoDB ist sie der ganze Sinn. Es gibt keine Joins, also kopierst du verwandte Daten auf das Item, das sie braucht und liest sie in einem Rutsch zurück.

Was ist Denormalisierung in DynamoDB?

Denormalisierung in DynamoDB bedeutet, verwandte Daten auf das Item zu kopieren, das sie liest, sodass ein einzelner Query alles in einem Rutsch zurückgibt. Weil DynamoDB keine Joins hat, joinst du zur Schreibzeit vor, statt Tabellen zur Lesezeit zusammenzunähen. Der Tausch ist Staleness — dupliziere nur Werte, die sich selten ändern.

  • Keine Joins bedeutet, du joinst zur Schreibzeit vor. Speichere den verwandten Wert auf dem Item, das ihn liest, sodass ein Query nie einen zweiten Lookup braucht.
  • Zwei Spielarten. Bette verschachtelte Daten in einem komplexen Attribut auf einem Item ein, oder dupliziere einen Wert über viele Items.
  • Der Footgun ist Staleness. Wenn sich die Quelle ändert, ist jede Kopie falsch, bis du das Update auffächerst. Dupliziere nur Werte, die sich selten ändern.
  • Es erkauft Lesevorgänge, keine Schreibvorgänge. Du tauschst mehr (und sorgfältigere) Schreibvorgänge gegen günstige Lesevorgänge mit einer einzigen Anfrage.

Warum es keine Joins gibt, auf die du zurückgreifen kannst

Ein relationaler JOIN setzt normalisierte Zeilen zur Lesezeit wieder zusammen. DynamoDB hat keinen Join — ein Query liest eine Item Collection und gibt genau das zurück, was dort gespeichert ist. Nichts näht zwei Tabellen für dich zusammen.

Die Daten müssen also bereits für den Lesevorgang geformt sein. Wenn ein Screen einen Beitrag und den Namen seines Autors braucht, muss dieser Name irgendwo liegen, wo der Beitrags-Lesevorgang ohnehin hinkommt. Das Amazon-Dynamo-Paper von 2007 machte diesen Tausch explizit: Verzichte auf relationale Features, um vorhersehbare Lesevorgänge im einstelligen Millisekundenbereich bei großer Skalierung zu bekommen.

Muster 1 — einbetten mit einem komplexen Attribut

DynamoDB-Attribute können verschachtelte Maps und Listen halten, nicht nur Skalare. Eine gängige Form der Denormalisierung ist also, ein Kind-Objekt direkt in sein Eltern-Item zu stopfen, statt ihm ein eigenes Item zu geben.

Ein Beitrag mit seinen Tags und einem kleinen Autoren-Snapshot, alles auf einem Item:

PKSKauthortags
POST#9f3META{id: U#12, name: "Mara Vance"}["dynamodb","aws"]

Ein GetItem liefert den Beitrag, die Tags und den Autorenblock zusammen. Kein zweiter Lesevorgang. Das ist großartig für Daten, die der Eltern-Entität gehören und in der Größe begrenzt sind — eine Handvoll Tags, ein Autoren-Snapshot.

Die Grenze, die du beachten musst: Ein einzelnes DynamoDB-Item ist auf 400 KB gedeckelt, Attributnamen und -werte inklusive (Service Quotas). Bette eine unbegrenzte Liste ein (jeden Kommentar zu einem viralen Beitrag), und du überschreitest sie.

Muster 2 — einen Wert über Items duplizieren

Der Blog-Fall ist der Lehrbuchfall. Du listest Beiträge und willst, dass jede Zeile den Anzeigenamen des Autors zeigt — aber du willst keinen zweiten Lesevorgang pro Beitrag, um ihn zu holen.

Also schreibst du den Namen des Autors auf jedes Beitrags-Item, wenn der Beitrag erstellt wird:

PKSKauthorIdauthorNametitle
POST#9f3METAU#12"Mara Vance""Modeling 1:N"
POST#a71METAU#12"Mara Vance""Sparse GSIs"
POST#b04METAU#88"Lio Tan""Query vs Scan"

Jetzt rendert Query PK begins_with "POST#" (oder ein GSI über Beiträge) die ganze Liste — Titel und Autor — ohne Lookup pro Zeile. Der Autorenname ist denormalisiert: Die kanonische Kopie liegt auf USER#12, und jeder Beitrag trägt seine eigene Kopie.

Der Tausch liegt direkt vor dir. Du hast einen N+1-Lesevorgang in einen Lesevorgang verwandelt, zum Preis, "Mara Vance" an N+1 Stellen zu halten.

Einbetten vs. duplizieren — welches

Einbetten (komplexes Attribut)Duplizieren (Kopie über Items)
FormKind verschachtelt in Elterngleicher Wert auf vielen Items
Am besten fürbegrenzte, eltern-eigene Datenein geteilter Wert, den viele Items zeigen
Lesevorgangein GetItemein Query
Update-Kostendas eine Eltern-Item neu schreibenauf jede Kopie auffächern
Größenrisiko400-KB-Item-Limitkeines pro Item

Greife zum Einbetten, wenn das Kind nur je mit seinem Elternteil auftaucht. Greife zum Duplizieren, wenn viele unabhängige Items denselben geteilten Wert zeigen müssen.

Der Footgun: veraltete Kopien

Hier ist der Teil, der beißt. Mara benennt sich in „Mara V." um. Du aktualisierst USER#12. Jedes Beitrags-Item sagt immer noch "Mara Vance", bis du sie reparierst.

Das Aktualisieren eines duplizierten Werts ist also ein Fan-out-Schreibvorgang, kein Einzeiler. Du fragst jedes betroffene Item ab und schreibst jedes neu — idealerweise abgesichert, sodass du nur Zeilen anfasst, die noch den alten Wert halten:

UPDATE POST#9f3
SET authorName = "Mara V."
WHERE authorName = "Mara Vance"

Du kannst dieses bedingte SET gegen authorName im Expression Builder zusammensetzen und die generierte UpdateExpression und ConditionExpression direkt in deinen Code kopieren.

Das Fan-out selbst ist ein Schreibvorgang pro Item: Frage die Beiträge des Autors ab, dann setze die Updates ab. Die Reihenfolge:

"DynamoDB"App"DynamoDB"App"USER"Beiträge des Autors abfragen""POST"Jeden authorName aktualisieren"

Die Kosten des Duplizierens von Daten: Jede Änderung an der Quelle ist ein Query plus ein Schreibvorgang pro Kopie.

Deshalb lautet die Regel: Dupliziere nur Werte, die sich selten ändern. Ein Anzeigename, eine Plan-Stufe, ein Kategorie-Label — okay. Ein Live-Counter oder ein häufig bearbeitetes Feld — nicht; das Fan-out frisst dich bei lebendigem Leibe.

Wann Normalisierung trotzdem gewinnt

Wenn sich ein Wert häufig ändert, oder ein Item nach wirklich unvorhersehbaren Mustern gelesen wird, halte ihn normalisiert und nimm den zusätzlichen Lesevorgang in Kauf. Denormalisierung ist eine Optimierung für bekannte, leselastige Zugriffsmuster — kein Standard, den man überall anwendet. Joine die Lesevorgänge vor, die du tatsächlich ausführst, und lass den Rest in Ruhe.

Um zu entscheiden, wo diese duplizierten Attribute liegen, modelliere zuerst die Zugriffsmuster — siehe Single-Table-Design und, für die Leseseite des Tauschs, Query vs. Scan.

Lade DynoTable herunter, um eine denormalisierte Tabelle zu inspizieren, zu erkennen, welche Kopien abgedriftet sind, und das Fan-out-Update gegen deine eigenen Daten auszuführen.

Aktualisiert