← Alle Beiträge

Database scaling-Lektionen aus dem Design von Cloudflare D1

Database scaling-Lektionen aus dem Design von Cloudflare D1

Database scaling ist die zentrale Lektion in Cloudflares Beschreibung von D1, seinem SQL-Datenbankdienst, der auf vielen kleinen Datenbanken statt auf einer großen gemeinsam genutzten Datenbank aufbaut. Das Design trennt die Control plane, also die Verwaltungsseite, die Datenbanken erstellt, löscht und nachverfolgt, vom Hot path, also dem Live-Weg, den eine Nutzerabfrage nimmt, wenn eine Anwendung auf eine Antwort wartet.

Wenn ein Nutzer eine D1-Datenbank erstellt, passieren laut Cloudflare mehrere Schritte, bevor Abfragen laufen können. Das System prüft, ob das Konto unter seinem Free-Tarif oder seinen Kontolimits eine weitere Datenbank erstellen darf. Dann erzeugt es eine eindeutige D1-Datenbank-UUID, also einen eindeutigen Identifier, erstellt mit newUniqueId() eine eindeutige Durable Object ID, initialisiert das zugrunde liegende Durable Object, speichert eine Metadatenzeile in Postgres und schreibt ein Mapping von der Datenbank-ID zur Durable Object ID in Workers KV. Durable Objects sind Cloudflares zustandsbehaftete Compute-Objekte, die hier als Ausführungspunkt pro Datenbank verwendet werden. Workers KV ist ein global verteilter Key-Value Store, der für Routing-Daten verwendet wird.

Die Platzierung dieser Schritte ist wichtig. Cloudflare sagt, dass der anfängliche Edge API Worker nahe an der eingehenden Anfrage läuft, Erstellungsanfragen mit mehreren Roundtrips zu Postgres jedoch an Rechenzentren in der Nähe des Postgres-Clusters weitergeleitet werden. Der Grund ist Latenz: Ein Roundtrip aus großer Entfernung kann Hunderte Millisekunden dauern, während einer in der Nähe nur wenige Millisekunden dauern kann. Standardmäßig wird eine D1-Datenbank nahe am Standort der Anfrage erstellt, obwohl ein Nutzer einen location hint oder eine jurisdiction angeben kann, mit denselben Optionen, die von Durable Objects unterstützt werden. Um die Datenbank nahe beim Nutzer zu halten und zugleich die Postgres-Latenz zu verringern, leitet Cloudflare die Ausführung intern in die Nähe von Postgres weiter und ruft dann das Durable Object aus einem Rechenzentrum nahe am ursprünglichen Anfrageort auf.

Cloudflare behandelt den Postgres-Schreibvorgang als Checkpoint der Erstellung. Nachdem das Durable Object initialisiert wurde, werden die Datenbankmetadaten über Hyperdrive, Cloudflares Datenbank-Konnektivitätsschicht, in den zentralen Postgres Primary geschrieben. Wenn die Initialisierung des Durable Object oder der Postgres-Schreibvorgang fehlschlägt, versucht der worker es mit anderen Identifiern erneut. Sobald die Zeile in Postgres steht, gilt die Datenbank als erstellt, selbst wenn die endgültige Antwort den Nutzer nicht erreicht. Ein abschließender Bestätigungsaufruf an das Durable Object läuft in waitUntil(), sodass er fehlschlagen kann, ohne die Erstellung zu blockieren. Dieser Aufruf teilt dem Durable Object mit, dass die Datenbank aktiv ist, und ermöglicht ihm, Workers KV zu aktualisieren, damit spätere Abfragen Routing-Metadaten schnell finden können.

Eine naheliegende Alternative wäre, den gesamten Erstellungsablauf in eine einzige Postgres transaction zu legen, also eine Datenbankoperation, die entweder vollständig abgeschlossen oder vollständig zurückgerollt wird. Cloudflare sagt, dass dies nicht skalieren würde, weil Datenbankverbindungen offengehalten würden, während ein Durable Object initialisiert wird, möglicherweise auf der anderen Seite der Welt. Die allgemeinere Lektion ist, dass eine zentrale Datenbank nicht gezwungen werden sollte, auf externe Arbeit über große Entfernungen zu warten, wenn die Plattform große Mengen an Ressourcen erstellen muss.

D1 verwendet außerdem heartbeats, um den Zustand nach Teilfehlern zu reparieren. Jedes D1 Durable Object kontaktiert regelmäßig die Control plane, um den neuesten Datenbankzustand, aktiv oder gelöscht, sowie Kontolimits abzurufen. Cloudflare sagt, das Intervall betrage 30 Sekunden, mit jitter pro Datenbank, und es gebe Steuerungsmöglichkeiten, um das Intervall pro Datenbank oder Konto zu ändern oder heartbeats vollständig zu deaktivieren. Weil heartbeats leicht veraltete Daten tolerieren können, können sie aus Hyperdrive-Caches oder Postgres Read replicas bedient werden, also schreibgeschützten Kopien der Hauptdatenbank.

Das Retry-Design erklärt, warum nach einem fehlgeschlagenen Erstellungsversuch neue Identifier verwendet werden. Wenn Cloudflare dieselbe Datenbank-UUID mit einer frischen Durable Object ID wiederverwenden würde, könnten mehrere Durable Objects glauben, dieselbe Datenbank zu besitzen. In Cloudflares Beispiel kann Durable Object A erfolgreich initialisieren, aber vor dem Postgres-Schreibvorgang fehlschlagen. Der Retry initialisiert Durable Object B und schreibt B erfolgreich nach Postgres. Nutzer können nur B abfragen, weil Postgres die source of truth ist. Innerhalb von 30 Sekunden sendet A einen heartbeat, erfährt, dass seine Datenbank nicht existiert, und markiert sich selbst als gelöscht. Dasselbe selbstkorrigierende Muster gilt auch für Löschungen, und heartbeats reparieren Workers KV schließlich, wenn ein Best-effort-Update fehlschlägt.

Für Live-SQL-Abfragen lautet Cloudflares wichtigste Regel, Postgres oder jede andere zentralisierte Single-region-Komponente zu vermeiden. Eine über die HTTP API gestellte Abfrage erreicht zuerst den Edge API Worker, der authentication, authorization, rate limiting, request validation und entitlement checks übernimmt. Dann versucht er, das Datenbank-Mapping aus Workers KV zu laden. Wenn dieser Eintrag fehlt, fällt das System auf Hyperdrive gegen Postgres replicas zurück. Hyperdrive prüft zuerst einen Cache am Standort des Aufrufers, dann einen Cache in der Nähe des Postgres-Clusters und fragt erst danach die replicas ab.

Nachdem das Mapping gefunden wurde, ruft der Edge API Worker den Eyeball Worker über Worker Service Bindings auf, einen Mechanismus, mit dem ein Worker einen anderen aufrufen kann. Der Eyeball Worker validiert die Anfrage und leitet sie an das D1 Durable Object weiter, das die SQL-Abfrage ausführt und das Ergebnis zurückgibt. Abfragen von einem Nutzer-Worker mit einem D1 Binding müssen das D1-UUID-Mapping nicht auflösen, weil das Binding die benötigten Informationen bereits enthält, sodass sie direkt zum Eyeball Worker und Durable Object gehen.

Cloudflare erklärt auch, warum D1 Workers KV nutzt, obwohl Hyperdrive Caching hat. Workers KV wird als dauerhafter Datastore verwendet, nicht nur als temporärer Cache. Wenn ein Eintrag den Edge-Cache von KV verlässt, kommt der Lesevorgang aus KVs eigenem Storage statt aus Postgres. Cloudflare warnt, dass in einem katastrophalen Cache-purge-Szenario eine Million Datenbanken, die Routing-Informationen aus Postgres abrufen, metastable failures erzeugen könnten, bei denen die durch die Wiederherstellung verursachte Last verhindert, dass die Wiederherstellung gelingt.

Nutzungslimits werden asynchron gehandhabt. Cloudflare sagt, D1 habe zum Zeitpunkt der Veröffentlichung ein Speicherlimit von 5 GB pro Konto im Free-Plan und 1 TB bei Workers Paid, wobei das bezahlte Limit auf Anfrage erhöht werden könne. Jedes Durable Object veröffentlicht Updates zur Speichernutzung in eine Kafka-like queue, also ein System zum Puffern von Events, damit Consumer sie später verarbeiten können. Queue consumers laufen in Cloudflares zentralen Rechenzentren, während Durable Objects über den Edge verteilt sind. Das glättet und bündelt Schreibvorgänge, bevor sie Postgres erreichen.

Für die Durchsetzung benötigt D1 kontoweite Speichersummen, nicht nur Größen pro Datenbank. Cloudflare sagt, SELECT sum(storage) über jede Datenbank eines Kontos auszuführen würde linear skalieren und für Konten mit Millionen von D1-Datenbanken nicht funktionieren. Stattdessen schreibt D1 Größen pro Datenbank in database_stats, verwendet einen trigger, um deltas in account_database_stats_changes zu schreiben, und führt jede Sekunde einen scheduled job aus, um diese deltas in account_database_stats zu aggregieren. Die Abfrage verwendet DELETE ...

Auch verfügbar in: EnglishItalianoShqip