Cloudflare
KV
Cloudflare KV
Feature Flags
Rate Limiting
Configuração
Edge

KV für Ratenbegrenzung, Feature-Flags und verteilte Konfiguration: Wo es funktioniert und wo es kaputt geht

Technische Analyse von drei gängigen Cloudflare-KV-Anwendungsfällen: Ratenbegrenzung (funktioniert nicht), Feature-Flags (funktioniert mit Einschränkungen) und verteilte Konfiguration (bester Anwendungsfall).

KV für Ratenbegrenzung, Feature-Flags und verteilte Konfiguration: Wo es funktioniert und wo es kaputt geht

Die Versuchung, KV zur Ratenbegrenzung zu verwenden, ist verständlich. Sie haben den KV bereits in der Bindung, er ist global und die Ratenbegrenzung scheint einfach zu sein: Erhöhen Sie einen Zähler pro IP-Schlüssel und lehnen Sie ihn ab, wenn er das Limit überschreitet. Das Problem besteht darin, dass diese Implementierung nicht funktioniert – und der Fehler nicht subtil genug ist, um beim Testen aufzutauchen.

KV verfügt über keine atomaren Operationen. Es gibt kein Vergleichen und Tauschen, es gibt kein atomares Inkrement. Wenn zwei Worker gleichzeitig für dieselbe IP ausgeführt werden, machen beide get des Zählers aus – und erhalten beispielsweise den Wert 5 –, beide machen put mit dem Wert 6 und einer der Inkremente geht verloren. Bei realem Datenverkehr steigt die inkrementelle Verlustrate mit der Konkurrenz. Der Ratenbegrenzer zählt weniger, als er sollte, und Anfragen, die blockiert werden sollten, werden durchgelassen.

Dies ist kein Implementierungsfehler, den Sie durch Wiederholung beheben. Dies ist die direkte Folge des Fehlens von Synchronisationsprimitiven in KV. Die Architektur wurde für eine andere Art von Arbeitslast konzipiert.

Warum Ratenbegrenzung Atomizität erfordert

Ein ratenbegrenzender Zähler muss garantieren, dass die Lese-Inkrement-Schreib-Sequenz atomar ist. Wenn zwei Prozesse diese Sequenz gleichzeitig über denselben Zähler ausführen, ist das korrekte Ergebnis der ursprüngliche Wert plus zwei. Ohne Atomizität ist das Ergebnis oft der ursprüngliche Wert plus eins.

Cloudflare bietet zwei Lösungen für dieses Problem. Die erste ist die native Ratenbegrenzungsfunktion, die über Regeln im Dashboard oder über die Regelsatz-API konfiguriert werden kann, die unterhalb der Worker-Ebene arbeitet und eine interne Infrastruktur mit den richtigen Synchronisierungsgarantien nutzt. Das zweite ist „Durable Objects“, das ein Isolat mit persistentem Status und serialisiertem Zugriff bietet – Sie können einen genauen Zähler implementieren, da jeweils nur ein Worker innerhalb des Durable Objects für diesen Schlüssel ausgeführt wird.

KV ist nicht Teil der Lösung für eine exakte Ratenbegrenzung. Versuche, eine Ratenbegrenzung mit KV zu implementieren, enden in Systemen, die weniger Verkehr ablehnen, als sie sollten, und zwar genau zu Spitzenzeiten, in denen die Ratenbegrenzung am wichtigsten ist.

Feature-Flags mit KV: Was funktioniert und was nicht

Feature-Flags sind der am häufigsten genannte Anwendungsfall für KV und funktionieren innerhalb der korrekten Grenzen gut. Das Grundmodell ist einfach: Sie schreiben ein JSON-Objekt in einen Schlüssel mit allen Systemflags, und jeder Worker liest diesen Schlüssel, um über das Verhalten zu entscheiden.

// escrita (admin) await env.FLAGS.put('feature-flags', JSON.stringify({ newCheckout: true, betaSearch: false, darkMode: true })); // leitura (worker) const flags = await env.FLAGS.get('feature-flags', { type: 'json' }); if (flags.newCheckout) { /* ... */ }

Das Modell funktioniert, weil die Arbeitslast leselastig ist und nur selten geschrieben wird. Eine Flagge wechselt ein paar Mal pro Woche. Es wird bei jeder Anfrage von jedem Worker in jedem PoP gelesen. Das Lese-Schreib-Verhältnis ist für KV hervorragend.

Die wirkliche Einschränkung ist die 60-Sekunden-Ausbreitung. Durch die Aktivierung eines Flags wird es nicht für alle Benutzer gleichzeitig aktiviert – es gibt ein Fenster, in dem ein Teil der PoPs das alte Verhalten und ein Teil das neue Verhalten bedient. Für die meisten Feature-Flags ist dies tolerierbar. Bei einem sicherheitskritischen Rollout, bei dem eine Markierung erforderlich ist, um alle Benutzer gleichzeitig zu erreichen, stellt dies eine echte betriebliche Einschränkung dar.

Der Fehler des Modells liegt in der dynamischen Flag-Auswertung pro Benutzer. Wenn Sie Flags auf der Grundlage von Benutzerattributen auswerten müssen – Abonnementplan, A/B-Testgruppe, Region, spezifische Entität –, verfügt der JSON für globale Flags nicht über diese Logik. Sie benötigen eine Suche pro Benutzer, was normalerweise einen Anruf bei D1 oder einem externen Dienst bedeutet. Der KV bleibt als globaler Konfigurationscache und nicht als vollständiges Flag-System bestehen.

Für schrittweise Rollouts nach Prozentsatz der Benutzer (10 % siehe Funktion) erfordert die Implementierung mit KV, dass Sie die Stichprobenlogik im Worker codieren und KV nur zum Speichern des Zielprozentsatzes verwenden. Das Sampling selbst ist zustandslos – es erfolgt im Worker basierend auf dem Benutzer-ID-Hash –, sodass der KV korrekt als Konfigurationsspeicher verwendet wird.

Verteilte Konfiguration: der beste Anwendungsfall für KV

Wenn Feature-Flags ein guter Anwendungsfall sind, ist die verteilte Konfiguration der ideale Anwendungsfall. Der Unterschied besteht in der Granularität und Häufigkeit der Änderung.

Änderungen der Anwendungskonfiguration durch bewusste Betriebsaktion: externe Dienstendpunktaktualisierung, Timeout-Anpassung, Liste der zulässigen IPs, Geschäftsparameter. Diese Änderungen treten sehr selten auf – Stunden oder Tage zwischen Aktualisierungen – und müssen für jede Anfrage gelesen werden.

Das Caching-Muster auf Modulebene extrahiert in diesem Fall das meiste aus KV. Variablen im Modulbereich eines Workers bleiben bestehen, solange das Isolat aktiv ist, möglicherweise für Tausende von Anfragen:

let config = null; export default { async fetch(request, env, ctx) { config = config ?? await env.CONFIG.get('app-settings', { type: 'json' }); // config está disponível para todos os requests // sem read do KV após o primeiro const timeout = config.upstreamTimeoutMs; // ... } };

Die erste Anfrage für jedes Isolat liest den KV. Alle nachfolgenden Anfragen von demselben Isolat verwenden den In-Memory-Wert – keine KV-Latenz, keine berechneten Lesevorgänge. Eine Konfigurationsaktualisierung wird auf neue Isolate übertragen, während vorhandene Isolate von der Laufzeit entfernt werden.

Die effektive Ausbreitungszeit beträgt nicht mehr die 60 Sekunden der globalen KV-Ausbreitung – sie beträgt 60 Sekunden plus die Lebensdauer der aktiven Isolate. Langlebige Isolate können die alte Konfiguration länger beibehalten. Für die meisten betrieblichen Änderungen ist dies akzeptabel. In Notfallsituationen, die eine sofortige Ausbreitung erfordern, können Sie über die API einen Neustart der Worker erzwingen.

Die Betriebskosten in jedem Szenario

Für die drei Anwendungsfälle erzeugt das KV-Kostenmodell unterschiedliche Belastungen. Eine Ratenbegrenzung wäre das Schreiben pro Anfrage – für kein Volumen machbar. Für Feature-Flags gelten minimale Schreib- und Lesekosten, die davon abhängen, wie viele Worker den Schlüssel in wie vielen PoPs pro Sekunde lesen. Mit Caching auf Modulebene können sogar Feature-Flags, die von Millionen von Anfragen gelesen werden, überraschend wenige Lesevorgänge verbrauchen – ein Isolat, das 10.000 Anfragen bedient, liest den KV einmal.

Die verteilte Konfiguration mit Caching auf Modulebene ist das Szenario mit den niedrigsten Betriebskosten in KV. Ein Schreibvorgang pro Konfigurationsänderung, ein Lesevorgang pro Isolierung und Neustart – die monatlichen Kosten für KV-Vorgänge für dieses Muster sind selbst bei Anwendungen mit hohem Datenverkehr vernachlässigbar.

Was verraten diese drei Fälle über KV?

Die Analyse von Ratenbegrenzung, Feature-Flags und verteilter Konfiguration macht die Grenzen von KV deutlich: Daten, die Sie selten schreiben und oft lesen, bei denen ein Inkonsistenzfenster von bis zu 60 Sekunden tolerierbar ist und bei denen Sie keine Atomizität benötigen.

Wenn eine dieser drei Bedingungen nicht erfüllt ist, erzeugt KV entweder stille Fehler (keine Atomizität), inakzeptable Inkonsistenzen (Ausbreitungsfenster) oder unerschwingliche Kosten (hohe Schreibfrequenz). Das Erkennen dieser Grenze vor der Implementierung erspart Ihnen die Debugging-Sitzung, die oft die teuerste Methode ist, um herauszufinden, wo ein Tool nicht anwendbar ist.

Lesen Sie auch