Cloudflare
KV
Cloudflare KV
Produção
Padrões
Cache
Limites

KV en production : les modèles qui fonctionnent et ceux qui sont trompeurs au début

Modèles d'utilisation de Cloudflare KV qui fonctionnent en production : astuce de métadonnées, mise en cache au niveau du module, clé d'index pour list() et anti-modèles qui épuisent rapidement le niveau gratuit.

KV en production : les modèles qui fonctionnent et ceux qui sont trompeurs au début

Un millier d'écritures par jour semblent suffisantes jusqu'à ce que vous mettiez quelque chose en production avec de vrais utilisateurs. Un système de connexion qui écrit un jeton de session sur le KV pour l'authentification épuise cette limite avec une demi-heure de trafic modéré. La première confrontation avec les limites réelles du KV se produit rarement lors d'une mise en scène.

Le niveau gratuit – 100 000 lectures et 1 000 écritures par jour – a été conçu pour refléter le modèle d’utilisation correct : beaucoup de lecture, un minimum d’écriture. Lorsqu'une équipe utilise KV de manière cohérente avec ce modèle, le niveau gratuit dure longtemps. Lorsqu'elle est utilisée comme banque de sessions ou comme magasin d'état par utilisateur, la limite apparaît au cours de la première semaine.

Modèles qui fonctionnent

L'utilisation la plus solide de KV est le stockage de la configuration de l'application lue à plusieurs reprises et rarement modifiée. Un JSON avec des indicateurs de produit, des paramètres commerciaux, des listes blanches, des points de terminaison tiers : ce type de données change par action administrative et non par utilisateur. Une écriture sur le KV se propage à tous les PoP et répond à des millions de requêtes sans frais importants. Une équipe qui écrit sur cette clé dix fois par mois et la lit dix millions de fois se situe confortablement dans le niveau gratuit.

La mise en cache du HTML rendu suit le même principe. Un article de blog, une page de produit, un résultat de recherche qui ne change pas par utilisateur : vous le restituez une fois, vous l'enregistrez dans le KV avec un TTL approprié et vous le diffusez directement à partir du cache PoP pour toute demande ultérieure. Le coût du rendu diminue, la latence diminue et le nombre d'écritures devient proportionnel à la fréquence des mises à jour du contenu, et non au volume du trafic.

Les jetons de session avec TTL conviennent également, à condition que la session soit en écriture unique. Vous écrivez le jeton lors de l'authentification (une écriture par connexion) et le lisez à chaque demande ultérieure. Si un utilisateur se connecte une fois et est actif pendant des heures, le rapport lecture/écriture est excellent. Ce qui casse ce modèle, c'est la session à état mutable : chaque mise à jour des données de session devient une écriture, et le coût explose.

L'astuce avec les métadonnées

Chaque clé du KV peut contenir jusqu'à 1 024 octets de métadonnées JSON arbitraires, distinctes de la valeur elle-même. Ce champ est renvoyé par getWithMetadata() avec la valeur, en une seule opération — sans coût de lecture supplémentaire.

L'utilisation pratique consiste à stocker des informations à côté de la valeur que vous auriez besoin d'analyser ou de déduire d'une autre manière. Pour un fichier binaire enregistré en KV, les métadonnées peuvent contenir Content-Type, ETag, la date de création, la taille d'origine et tout en-tête HTTP pertinent. Le Worker lit la clé, reçoit la valeur et les métadonnées en un seul appel et assemble la réponse HTTP avec les en-têtes corrects sans aucune logique de recherche ou d'analyse supplémentaire.

Cela fonctionne également pour la gestion de versions légère. Enregistrez la version ou l'horodatage de la dernière mise à jour dans les métadonnées. Tout consommateur peut vérifier s’il lit la version attendue sans chercher une seconde donnée.

Le problème de performances de list()

list() est l'opération KV la plus coûteuse en termes de performances relatives, et c'est celle qui apparaît le plus fréquemment sur les chemins qui ne devraient pas l'utiliser. Un appel à list() dans un espace de noms comportant 100 000 clés est lent (la latence dépendra de la taille de l'espace de noms et du curseur) et compte comme une opération de liste, qui a un quota distinct : 1 000 opérations par jour dans le niveau gratuit, 0,50 $ par million dans le niveau payant.

Le vrai problème est d'utiliser list() dans le chemin actif d'une requête. Si chaque requête doit découvrir quelles clés existent pour servir une réponse, vous avez placé une opération de gestion des données dans le chemin de performances critique.

La solution est de conserver une clé d'index. Vous écrivez une clé comme __index__ dans le KV dont la valeur est un JSON avec la liste des clés d'espace de noms — ou simplement les identifiants nécessaires à la logique. Lorsque l'espace de noms change, vous mettez à jour l'index avec l'écriture principale. Le coût est d’une écriture supplémentaire par opération d’écriture. L'avantage est que toute lecture d'index est une lecture régulière, avec une latence du cache et sans les problèmes de mise à l'échelle de list().

Ce modèle présente la limitation évidente que l'index doit être synchronisé manuellement. Si vous disposez de plusieurs Writers, l'absence d'opérations atomiques dans KV crée une fenêtre d'incohérence dans l'index. Pour les espaces de noms avec une seule écriture ou une écriture contrôlée par un seul Writer, le modèle fonctionne bien.

Mise en cache au niveau du module : l'optimisation que personne ne documente explicitement

Les travailleurs sur Cloudflare s'exécutent sur des isolats V8. Un seul isolat peut traiter des milliers de requêtes avant d’être expulsé. Les variables déclarées dans la portée du module – en dehors du gestionnaire – persistent entre les requêtes tant que l'isolat est actif.

Cela crée une opportunité d’optimisation simple et efficace pour les données de configuration. Au lieu de faire env.CONFIG.get('settings') dans chaque requête, vous déclarez une variable dans la portée du module et recherchez le KV uniquement lorsqu'il n'a pas encore été initialisé :

let config = null; export default { async fetch(request, env) { config = config ?? await env.CONFIG.get('settings', { type: 'json' }); // usa config } };

La première demande d'isolement lit le KV. Toutes les demandes ultérieures pour le même isolat utilisent la valeur en mémoire. Pour les données qui changent rarement (configuration du produit, indicateurs de fonctionnalités), cela élimine la lecture KV de presque toutes les requêtes, réduisant ainsi la latence et la consommation des opérations de lecture.

L'implication est qu'une mise à jour du KV n'est pas immédiatement reflétée dans tous les Workers : chaque isolat continuera à utiliser la valeur mise en cache jusqu'à ce qu'il soit expulsé. Pour les données pour lesquelles un retard de 60 secondes à quelques minutes est acceptable, le compromis est excellent. Pour les données qui doivent être mises à jour immédiatement sur tous les Workers, ce modèle ne convient pas.

Ce qu'il ne faut pas mettre en production sans repenser

L'utilisation de KV comme file d'attente de tâches ne fonctionne pas. Sans opérations atomiques, deux Workers peuvent lire le même travail, le traiter en double et le marquer comme terminé indépendamment. Le résultat est un traitement en double sans mécanisme de détection.

L'enregistrement des données utilisateur mutables par clé utilisateur s'adapte mal à la limite d'écriture. Une application avec 10 000 utilisateurs actifs par jour qui met à jour les données de profil même une fois par session est déjà de l'ordre de grandeur de la limite payante de 1 million d'écritures mensuelles - et le coût par écriture de 0,50 $/million commence à apparaître lorsque vous dépassez.

Les espaces de noms avec une densité de clés élevée et un besoin fréquent de listage constituent un piège en termes de performances. list() est lent dans les grands espaces de noms et ne devrait pas se trouver dans le chemin de la requête. Si votre cas d'utilisation nécessite une liste fréquente, le modèle de données doit changer, soit avec des clés d'index gérées manuellement, soit avec un outil différent.

Ce que l'offre gratuite révèle sur le design

Les limites de l'offre gratuite (100 000 lectures pour 1 000 écritures) sont un document de conception déguisé. Le rapport de 100 : 1 entre les lectures et les écritures n’est pas arbitraire. Il décrit la charge de travail pour laquelle le KV a été conçu. Toute utilisation qui inverse ou s'approche de ce rapport est en dehors du modèle opérationnel prévu et se heurtera à des limitations de coût, de performance ou de cohérence qui n'apparaissent pas dans les tests à faible volume.

A lire aussi