Cloudflare
Durable Objects
Arquitetura
Trade-offs
Decisão Técnica

Wenn langlebige Gegenstände die falsche Antwort sind

Eine ehrliche Analyse, wann man langlebige Objekte nicht verwenden sollte, mit den richtigen Alternativen für jeden Anwendungsfall und der Diagnosefrage, die die Entscheidung vereinfacht.

Langlebige Objekte gewinnen an Bedeutung, weil sie etwas Schwieriges lösen – einen konsistenten Zustand mit serialisiertem Zugriff am Rand – und dies führt zu einer Voreingenommenheit: Ingenieure, die das Tool gerade erst erlernt haben, neigen dazu, es auf Probleme anzuwenden, die es nicht lösen muss. Das Ergebnis ist kein schlechter Code. Es ist korrekter, funktionierender Code, teurer und komplexer als er sein muss. Für ein einfaches CRUD, das auf DOs basiert, wenn D1 ausreichen würde, könnte die Rechnung zwischen zehn und fünfzig Mal höher ausfallen, ohne den Konsistenzvorteil, den der Anwendungsfall erfordern würde.

Die Frage, die bestimmt, ob Sie DO benötigen

Bevor Sie sich für Durable Objects entscheiden, stellt sich eine konkrete Frage: Erfordert der Status, den Sie verwalten müssen, einen serialisierten Zugriff von mehreren gleichzeitigen Clients?

Serialisiert bedeutet, dass die Reihenfolge der Vorgänge wichtig ist und dass sich zwei gleichzeitige Vorgänge für dieselben Daten gegenseitig ausschließen müssen. Mehrere konkurrierende Clients bedeuten, dass mehr als ein Agent gleichzeitig versuchen kann, dieselben Daten zu ändern, und beide Agenten müssen ein konsistentes Ergebnis sehen.

Wenn die Antwort „Nein“ lautet – wenn die Daten von vielen gelesen, aber selten geschrieben werden, oder wenn gleichzeitige Schreibvorgänge in denselben Datensatz unwahrscheinlich sind oder von einer anderen Ebene verarbeitet werden – erhöht ein DO die Komplexität und die Kosten, ohne eine nützliche Sicherheit zu bieten.

Wobei D1 die richtige Antwort ist

Für relationale Daten ist D1 der natürliche Ort innerhalb des Cloudflare-Stacks. Es verfügt über vollständiges SQL, unterstützt JOINs, Indizes, Transaktionen, Ad-hoc-Abfragen – alles, was D1 hat und DO nicht hat. Ein DO ist ein linearisierbarer Schlüsselwertspeicher mit prozeduraler API. Die Antwort „Alle Benutzer auflisten, die sich in den letzten 7 Tagen angemeldet haben und immer noch einen positiven Kontostand haben“ kann nicht beantwortet werden, ohne dass Sie diese Daten explizit vorab berechnet und gespeichert haben.

D1 hat viel geringere Kosten für Leselasten: 0,001 $/Million gelesene Zeilen, 1,00 $/Million geschriebene Zeilen. Die zusätzliche Schreiblatenz zur Primärbankregion, der am häufigsten genannte Nachteil von D1, betrifft nur Schreibvorgänge. Bei Anwendungen, bei denen Lesevorgänge dominieren, spricht dieser Kompromiss weitgehend für D1.

Der verwirrende Fall: „Aber ich brauche, dass die Schreibvorgänge atomar sind“. D1 hat Transaktionen. BEGIN; UPDATE a...; UPDATE b...; COMMIT; garantiert Atomizität. Der Unterschied besteht darin, dass D1 Datenbank Sperren verwendet – mit der Gefahr von Konflikten bei hoher Parallelität –, während ein DO die Serialisierung durch Design verwendet. Für die überwiegende Mehrheit der Anwendungen mit normaler Schreiblast ist D1 mit Transaktionen ausreichend und deutlich günstiger.

Wobei KV die richtige Antwort ist

KV ist für das Muster optimiert, das die meisten Edge-Anwendungsfälle dominiert: viele Lesevorgänge, gelegentliche Schreibvorgänge, keine Anforderung an eine starke Konsistenz zwischen verschiedenen PoPs. Konfigurationscache, vorberechnete Rechenergebnisse, Benutzersitzungen, bei denen die letztendliche Konsistenz akzeptabel ist – KV reagiert auf lokale Cache-Lesevorgänge im PoP mit einer Latenz von weniger als einer Millisekunde und einem Preismodell, das intensives Lesen begünstigt.

Der Fehler besteht darin, anzunehmen, dass Sie für alles, was sich ändert, DO benötigen, weil KV keine atomaren Lese-, Änderungs- und Schreiboperationen hat. Die überwiegende Mehrheit der Daten in Webanwendungen ändert sich nach Mustern, die keine strikte Atomizität erfordern: Das Benutzerprofil, das aktualisiert wird, wenn der Benutzer seine Einstellungen bearbeitet, muss sich nicht gegenseitig mit einem anderen konkurrierenden Skript ausschließen, da niemand anderes gleichzeitig dasselbe Profil bearbeitet.

DO beginnt sinnvoll zu sein, wenn die Häufigkeit gleichzeitiger Schreibvorgänge auf dieselben Daten hoch genug ist, dass die letztendliche Konsistenz von KV zu falschen Ergebnissen führt, die für den Benutzer sichtbar sind.

Ratenbegrenzung ist kein DO-Anwendungsfall

Ratenbegrenzung ist das häufigste Beispiel für „es sieht so aus, als ob es DO braucht, aber das ist nicht der Fall“. Die Intuition ist richtig: Für die Ratenbegrenzung ist ein Zähler pro Benutzer erforderlich, der bei jeder Anfrage atomar erhöht wird. Wenn zwei Worker gleichzeitig denselben Zähler erhöhen, liegt möglicherweise eine Race-Bedingung vor, die es einem Benutzer ermöglicht, mehr Anfragen als das Limit zu stellen.

Die Cloudflare Rate Limiting API löst dieses Problem nativ, ohne Code, ohne DO und ohne zusätzliche Kosten, die über den Plan hinausgehen. Es ist für alle kostenpflichtigen Pläne verfügbar und unterstützt Limits pro IP, pro authentifiziertem Benutzer, pro Route, pro Header und mit konfigurierbaren Zeiträumen. Für die Ratenbegrenzung am Edge ist die Verwendung der nativen API einfacher, kostengünstiger und zuverlässiger als die Implementierung eines Counter-DO.

Der legitime Fall für DO zur Ratenbegrenzung liegt dann vor, wenn Sie Anforderungen haben, die die native API nicht abdeckt: benutzerdefinierte Schiebefensterlogik mit exakter Präzision, Grenzwerte, die von Benutzersitzungsdaten abhängen, die sich nicht in Headern befinden, oder Ratenbegrenzung, die mit anderen Daten konsistent sein muss, die bereits in einem Anwendungs-DO vorhanden sind.

Worker-Warteschlangen für die asynchrone Verarbeitung

Ein weiteres Muster, das manchmal als Kandidat für DO erscheint: eine Arbeitswarteschlange, in der mehrere Produzenten Aufgaben in die Warteschlange stellen und mehrere Konsumenten verarbeiten. DO könnte dies als Objekt mit einer Aufgabenliste und einer Verarbeitungsschleife modellieren.

Workers Queues lösen dieses Problem nativ: Produzenten verdienen env.QUEUE.send(message), Konsumenten erhalten Batches über den Handler, mit automatischer Wiederholung, Warteschlange für unzustellbare Nachrichten und separater Gebühr von 0,40 $/Million Nachrichten. Für die asynchrone Verarbeitung mit Wiederholungsversuchen und Backoff sind Warteschlangen besser geeignet, kostengünstiger und haben nicht die serielle Durchsatzobergrenze eines DO.

DO für die asynchrone Verarbeitung ist sinnvoll, wenn der Verbrauch nach Identität serialisiert werden muss – alle Aktionen eines Benutzers der Reihe nach und ohne Parallelität verarbeiten – und wenn diese Verarbeitung ohnehin auf den lokalen Status des DO zugreifen muss.

Der Dateispeicher ist R2, kein DO-Speicher

DO-Speicher ist ein Schlüsselwertspeicher, der für kleine Objekte optimiert ist – Einstellungen, Zähler, Sitzungsstatus, Nachrichten. Es gibt keine dokumentierten Wertgrenzen, ist aber für Daten konzipiert, die angemessen in eine Anfrage passen. Für Dateien, Bilder, Videos und alle größeren Blobs ist R2 der richtige Ort: 0,015 $/GB-Monat Speicher (weniger als ein Zehntel der Kosten für DO-Speicher), keine Ausgangskosten für Worker, mit S3-kompatibler API.

DO-Speicher ist kein Ersatz für Objektspeicher – es handelt sich um einen Transaktionszustandsspeicher für DO selbst.

Diagnose vor der Auswahl

Vier Anwendungsfälle, in denen DO etwas löst, das Alternativen nicht mit den gleichen Garantien lösen: gemeinsame Bearbeitung mit mehreren Clients, die gleichzeitig dasselbe Dokument ändern, Echtzeit-Anwesenheitskoordination, bei der die Liste der Online-Benutzer konsistent sein muss, verteilte Sperren mit Zeitüberschreitung, bei denen der Ablauf auch bei einem Absturz des Inhabers garantiert werden muss, und nach Ankunft geordnete Ereignisprotokolle, bei denen die Einfügereihenfolge semantisch wichtig ist.

Für alles, was nicht in diese Liste passt, gibt es innerhalb der Plattform eine Alternative, die günstiger, einfacher oder beides ist. Die Entscheidung für DO beginnt mit der Frage nach der Serialisierung. Wenn Sie nicht artikulieren können, warum die Serialisierung des Datenzugriffs für den Anwendungsfall erforderlich ist, ist DO wahrscheinlich nicht das richtige Werkzeug.

Das Risiko, DO dort zu verwenden, wo es nicht notwendig ist, besteht nicht darin, dass dadurch irgendetwas kaputt geht – der Code wird funktionieren. Der Preis besteht darin, unnötige Komplexität mit sich zu bringen: Eine Abstraktion, die serielle Durchsatzgrenzen vorgibt, einen „Workers Paid“-Plan als Voraussetzung erfordert, eine ausgeprägte Lernkurve aufweist und mehr kostet als die Alternativen für die Fälle, die die Alternativen gut lösen. Für ein Team, das bereits an anderer Stelle im System DOs nutzt, sind die Grenzkosten für ein weiteres DO geringer. Für ein Team, das bei Cloudflare mit einem einfachen CRUD beginnt, ist es die richtige Reihenfolge der Komplexität, mit D1 und KV zu beginnen und DOs hinzuzufügen, bei denen eine Serialisierung wirklich erforderlich ist.

Lesen Sie auch