La tentation d’utiliser KV pour limiter le débit est compréhensible. Vous avez déjà le KV dans la liaison, il est global, et la limitation de débit semble simple : incrémentez un compteur par clé IP et rejetez-le lorsqu'il dépasse la limite. Le problème est que cette implémentation ne fonctionne pas – et la faille n’est pas assez subtile pour apparaître lors des tests.
KV n'a pas d'opérations atomiques. Il n’y a pas de comparaison et d’échange, il n’y a pas d’incrément atomique. Lorsque deux Workers courent simultanément pour la même IP, ils font tous deux get du compteur — obtenant, disons, la valeur 5 — ils font tous les deux put avec la valeur 6, et l'un des incréments est perdu. En trafic réel, le taux de perte supplémentaire augmente avec la concurrence. Le limiteur de débit compte moins qu'il ne le devrait et les requêtes qui devraient être bloquées transitent.
Il ne s'agit pas d'un bug d'implémentation que vous résolvez en réessayant. C'est la conséquence directe de l'absence de primitives de synchronisation en KV. L'architecture a été conçue pour un autre type de charge de travail.
Pourquoi la limitation du débit nécessite l'atomicité
Un compteur limiteur de débit doit garantir que la séquence de lecture-incrémentation-écriture est atomique. Si deux processus exécutent cette séquence simultanément sur le même compteur, le résultat correct est la valeur d'origine plus deux. Sans atomicité, le résultat est souvent la valeur originale plus un.
Cloudflare propose deux solutions à ce problème. La première est la fonctionnalité native de limitation de débit, configurable via des règles dans le tableau de bord ou via l'API Rulesets, qui fonctionne en dessous du niveau Worker et utilise une infrastructure interne avec les garanties de synchronisation correctes. Le second est Durable Objects, qui offre un isolat avec un état persistant et un accès sérialisé : vous pouvez implémenter un compteur exact car un seul Worker à la fois s'exécute dans l'objet Durable pour cette clé.
KV ne fait pas partie de la solution pour une limitation exacte du débit. Les tentatives visant à mettre en œuvre une limitation de débit avec KV aboutissent à des systèmes qui rejettent moins de trafic qu'ils ne le devraient, exactement aux heures de pointe où la limitation de débit est la plus importante.
Indicateurs de fonctionnalités avec KV : ce qui fonctionne et ce qui ne fonctionne pas
Les indicateurs de fonctionnalités sont le cas d'utilisation le plus cité pour KV et ils fonctionnent bien dans les limites correctes. Le modèle de base est simple : vous écrivez un objet JSON dans une clé avec tous les indicateurs du système, et chaque Worker lit cette clé pour décider du comportement.
// 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) { /* ... */ }
Le modèle fonctionne car la charge de travail est lourde en lecture et rarement en écriture. Un drapeau change plusieurs fois par semaine. Il est lu à chaque requête de chaque Worker dans chaque PoP. Le rapport lecture/écriture est excellent pour KV.
La vraie limitation est la propagation de 60 secondes. L'activation d'un indicateur ne l'active pas simultanément pour tous les utilisateurs : il existe une fenêtre dans laquelle une partie des PoP servent l'ancien comportement et une partie sert le nouveau. Pour la plupart des indicateurs de fonctionnalités, cela est tolérable. Pour un déploiement critique en matière de sécurité où il faut un flag pour atteindre tous les utilisateurs en même temps, c'est une véritable contrainte opérationnelle.
Là où le modèle se décompose, c'est dans l'évaluation dynamique des indicateurs par utilisateur. Si vous devez évaluer les indicateurs en fonction des attributs de l'utilisateur (plan d'abonnement, groupe de test A/B, région, entité spécifique), les indicateurs globaux JSON ne comportent pas cette logique. Vous avez besoin d'une recherche par utilisateur, ce qui signifie généralement un appel à D1 ou à un service externe. Le KV reste un cache de configuration global, et non un système de drapeaux complet.
Pour des déploiements progressifs par pourcentage d'utilisateurs (10 % voir la fonctionnalité), l'implémentation avec KV nécessite de coder la logique d'échantillonnage dans le Worker et d'utiliser KV uniquement pour stocker le pourcentage cible. L'échantillonnage lui-même est sans état - effectué dans le Worker en fonction du hachage de l'ID utilisateur - de sorte que le KV est correctement utilisé comme magasin de configuration.
Configuration distribuée : le meilleur cas d'utilisation pour KV
Si les indicateurs de fonctionnalités constituent un bon cas d’utilisation, la configuration distribuée est le cas d’utilisation idéal. La différence réside dans la granularité et la fréquence des changements.
La configuration de l'application change par une action opérationnelle délibérée : mise à jour du point de terminaison de service externe, ajustement du délai d'expiration, liste des adresses IP autorisées, paramètres commerciaux. Ces modifications se produisent très rarement (des heures ou des jours entre les mises à jour) et doivent être lues pour chaque demande.
Le modèle de mise en cache au niveau du module extrait le plus de KV dans ce cas. Les variables dans la portée d'un module Worker persistent tant que l'isolat est actif, potentiellement pour des milliers de requêtes :
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; // ... } };
La première requête pour chaque isolat lit le KV. Toutes les requêtes ultérieures du même isolat utilisent la valeur en mémoire : latence zéro KV, aucune opération de lecture facturée. Une mise à jour de configuration se propage aux nouveaux isolats à mesure que les isolats existants sont supprimés par le runtime.
Le temps de propagation effectif n'est plus de 60 secondes de propagation globale du KV, mais de 60 secondes plus la durée de vie des isolats actifs. Les isolats à longue durée de vie peuvent conserver l’ancienne configuration plus longtemps. Pour la plupart des changements opérationnels, cela est acceptable. Pour les situations d'urgence nécessitant une propagation immédiate, vous pouvez forcer le redémarrage de Workers via l'API.
Le coût de fonctionnement dans chaque scénario
Pour les trois cas d'utilisation, le modèle de coût KV crée des pressions différentes. La limitation du débit consisterait à écrire par demande – ce qui est irréalisable pour n'importe quel volume. Les indicateurs de fonctionnalités ont un coût d'écriture et un coût de lecture minimum qui dépendent du nombre de travailleurs qui lisent la clé et du nombre de PoP par seconde. Avec la mise en cache au niveau du module, même les indicateurs de fonctionnalités lus par des millions de requêtes peuvent consommer étonnamment peu d'opérations de lecture : un isolat traitant 10 000 requêtes lit le KV une fois.
La configuration distribuée avec mise en cache au niveau du module constitue le scénario de coût opérationnel le plus bas possible dans KV. Une écriture par changement de configuration, une lecture par isolement et par redémarrage : le coût mensuel des opérations KV pour ce modèle est négligeable, même dans les applications à fort trafic.
Que révèlent ces trois cas sur KV
L'analyse de la limitation de débit, des indicateurs de fonctionnalités et de la configuration distribuée montre clairement la frontière du KV : des données que vous écrivez rarement et que vous lisez souvent, où une fenêtre d'incohérence allant jusqu'à 60 secondes est tolérable et où vous n'avez pas besoin d'atomicité.
Lorsqu'une de ces trois conditions n'est pas remplie, KV produira soit des bugs silencieux (pas d'atomicité), soit des incohérences inacceptables (fenêtre de propagation), soit un coût prohibitif (fréquence d'écriture élevée). Reconnaître cette limite avant l'implémentation évite la session de débogage qui est souvent le moyen le plus coûteux de savoir où un outil ne s'applique pas.
A lire aussi
-Cloudflare KV : Que signifie une distribution mondiale lorsque vous devez écrire
- Invalidation du cache dans KV : le problème que personne ne résout avec élégance
- KV en production : les schémas qui marchent et ceux qui trompent au début
- WAF + Rate Limiting + Bot Management : le tiercé gagnant de la protection Edge
- Ce que font seuls les travailleurs, ce que font seuls les pages et où les deux se rencontrent
- KV vs R2 vs API Cache : quand utiliser chaque niveau de stockage Cloudflare
