Changer le thème

Cloudflare Workers KV en pratique : stockage clé-valeur distribué de A à Z

Easton editorial illustration: bottleneck pressure gauge

En regardant la courbe de latence dans le Cloudflare Dashboard. La ligne rouge reste au-dessus de 200 ms — Workers est pourtant en place, le code est déjà minimal, pourquoi chaque requête utilisateur met-elle autant de temps ?

Le problème venait de la base de données. Chaque requête session partait du nœud edge, traversait l’Atlantique vers un datacenter européen, puis revenait. Même si l’exécution Worker ne prend que 5 ms, le réseau absorbe tout le budget.

J’ai compris ensuite : Workers est stateless. Il fallait un stockage qui « vit vraiment en edge » — Cloudflare Workers KV.

Cet article expose les pièges que j’ai rencontrés, les chiffres mesurés et le code écrit. De ce qu’est KV et pourquoi la latence peut descendre sous 10 ms, jusqu’aux implémentations complètes session storage et cache API. Et quand préférer KV, D1 ou R2.

KV en bref — comprendre le stockage edge distribué

En clair, KV est la « mémoire de poche » que Cloudflare fournit aux Workers. Elle n’est pas dans un seul datacenter : elle est répartie sur plus de 300 nœuds edge dans le monde. Un utilisateur à Tokyo peut lire des données déjà présentes à Tokyo ; une requête depuis Francfort peut toucher un cache local.

Cloudflare Workers KV est un stockage clé-valeur distribué mondial conçu pour l’edge computing. Trois caractéristiques essentielles :

Lecture ultra-rapide. Les hot keys en cache affichent une latence entre 500 µs et 10 ms — au premier abord j’étais sceptique, jusqu’à ce qu’un benchmark confirme des valeurs stables en millisecondes simples.

Réplication mondiale. Une écriture est propagée vers tous les nœuds edge. Contrairement à un cluster Redis, le modèle KV est « écrire une fois, lire partout », idéal pour lecture-intensive.

Débit élevé. Une même clé peut supporter des milliers de RPS en lecture, car les données sont en cache edge sans retour systématique à l’origine.

500µs - 10ms
Plage de latence des hot keys

Panorama du stockage Cloudflare

KV n’est qu’une pièce de la matrice de stockage Cloudflare. Vue d’ensemble :

ServiceModèleMeilleur cas d’usageLimite d’écritureLatence
KVKey-ValueSession, cache, config1 RPS/cléhot keys 500µs-10ms
D1SQL (SQLite)Données utilisateur, commandes, rapportsPas de limite dure50-200 ms selon la région
R2Object StorageFichiers, images, vidéosPas de limite dureTéléchargement rapide
Durable ObjectsObjets statefulÉdition collaborative, WebSocketPas de limite dureRoutage vers un nœud précis

1 RPS/clé, qu’est-ce que ça veut dire ? Chaque clé ne peut être écrite qu’une fois par seconde — la limite la plus critique de KV. On y reviendra en détail.

Quand utiliser KV — guide rapide

Recommandé pour KV :

  • Session storage (état de connexion)
  • Cache de réponses API (retours d’API tierces)
  • Compteurs de rate limiting
  • Feature flags / données de configuration
  • Redirect mapping (règles de redirection URL)

Déconseillé pour KV :

  • Données écrites très fréquemment (compteur temps réel > 1 RPS/clé)
  • Données nécessitant du SQL (tables utilisateurs, commandes → D1)
  • Gros fichiers (images, vidéos → R2)
  • Transactions financières à cohérence forte (→ Durable Objects)

La doc Cloudflare est claire : KV convient aux scénarios « forte lecture, faible modification, cohérence non immédiate ». OpenAuth et d’autres frameworks d’authentification utilisent KV par défaut pour les sessions — implémentation complète plus bas.

Architecture KV — pourquoi c’est si rapide

La vitesse de KV n’est pas magique : c’est le résultat d’une architecture cache à trois niveaux.

Imaginez un dépanneur. Idéal : le produit est sur l’étagère à côté de la caisse (edge cache). Moins bien : en réserve locale, le vendeur va le chercher (regional cache). Pire : entrepôt central, livraison camion (central store).

KV fonctionne ainsi :

Requête → Edge Cache (le plus rapide)
        ↓ miss
      Regional Cache
        ↓ miss
      Central Store (le plus lent)

D’après le blog Cloudflare d’octobre 2025, environ 30 % des requêtes sont résolues dans les couches cache. Un tiers des lectures n’atteint jamais le stockage central — d’où la latence basse.

30%
Taux de hit cache edge

Performances : doc officielle et mesures

Chiffres de référence Cloudflare :

  • Hot keys (accès fréquents) : 500 µs à 10 ms
  • Cold keys (premier accès ou rares) : latence plus élevée, retour à l’origine

Le « 500 µs » m’a semblé optimiste. Mesure personnelle :

// Test de latence simple
const start = Date.now();
await env.KV.get("test-key");
const latency = Date.now() - start;
console.log(`Latency: ${latency}ms`);

100 lectures : hot keys en moyenne 5-8 ms. Cold keys : 50 ms+ au premier accès, puis baisse — le cache fonctionne.

En 2025, Cloudflare a refondu KV ; la doc annonce un gain de vitesse ×3, principalement :

  1. Connexion directe Workers ↔ KV, sans l’ancienne couche Front Line
  2. Chemin de transfert interne simplifié

Les services Cloudflare dépendant de KV (Turnstile, Waiting Room, etc.) en profitent aussi.

Modèle de cohérence : le prix de la cohérence finale

KV est eventually consistent. Une écriture n’apparaît pas instantanément sur tous les nœuds edge ; la propagation prend quelques secondes à quelques dizaines de secondes en pratique.

Problématique :

  • Session écrite après connexion, requête suivante sur un autre nœud edge : session introuvable
  • Édition collaborative temps réel : B ne voit pas la modification de A immédiatement

Acceptable :

  • Feature flags : quelques secondes de délai OK
  • Cache API : TTL de plusieurs minutes, propagation lente sans impact
  • Redirect mapping : mise à jour lente, imperceptible pour l’utilisateur

Besoin de cohérence immédiate ? Préférez Durable Objects — état localisé sur un nœud précis.

Configuration pratique avec Wrangler CLI

Passons à la pratique.

Deux étapes : créer un namespace, puis le lier dans wrangler.toml.

Créer un namespace

Un namespace est le « conteneur » KV. Chaque namespace peut contenir un nombre illimité de paires clé-valeur ; le compte est limité à 1000 namespaces (200 → 1000 début 2025).

# Créer le namespace production
wrangler kv namespace create MY_KV

# Sortie type :
# Created namespace with id "abc123def456..."
# Add the following to your wrangler.toml:
# [[kv_namespaces]]
# binding = "MY_KV"
# id = "abc123def456..."

Prévoyez aussi un namespace preview pour le dev local :

# Créer le namespace preview
wrangler kv namespace create MY_KV --preview

# Sortie type :
# Created preview namespace with id "preview_abc123..."

Configuration wrangler.toml

Insérez les id dans wrangler.toml :

name = "my-worker"
main = "src/index.ts"

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456..."        # namespace production
preview_id = "preview_abc123..." # namespace preview (dev local)

Le nom binding détermine l’accès dans le code Worker :

// binding = "MY_KV" → env.MY_KV dans le code
const value = await env.MY_KV.get("some-key");

REST API vs Workers Binding API

Deux façons d’accéder à KV :

Workers Binding API (recommandé) :

  • env.MY_KV.get() directement dans le Worker
  • Pas de requête réseau supplémentaire, le plus rapide
  • Gratuit (compté dans le temps d’exécution Worker)

REST API :

  • Accès HTTP avec token d’authentification
  • Pour systèmes externes
  • Soumis aux limites globales REST Cloudflare

Dans la grande majorité des cas, utilisez Binding API. REST API surtout pour :

  • Systèmes externes lisant/écrivant KV
  • Import batch en CI/CD
  • Debug et ops ponctuels

Commandes Wrangler KV courantes

# Écrire
wrangler kv key put --namespace-id=abc123 "my-key" "my-value"

# Lire
wrangler kv key get --namespace-id=abc123 "my-key"

# Supprimer
wrangler kv key delete --namespace-id=abc123 "my-key"

# Lister les clés (filtre par préfixe)
wrangler kv key list --namespace-id=abc123 --prefix="session:"

Utiles en debug ; en production, préférez le code Worker.

Code TypeScript en pratique

Exemples complets et exécutables.

CRUD de base

// src/index.ts
interface Env {
  MY_KV: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;

    // Écrire
    if (path === "/put") {
      const key = url.searchParams.get("key") || "default";
      const value = url.searchParams.get("value") || "hello";
      
      await env.MY_KV.put(key, value);
      return new Response(`Saved: ${key} = ${value}`);
    }

    // Lire
    if (path === "/get") {
      const key = url.searchParams.get("key") || "default";
      const value = await env.MY_KV.get(key);
      
      if (value === null) {
        return new Response("Key not found", { status: 404 });
      }
      return new Response(value);
    }

    // Supprimer
    if (path === "/delete") {
      const key = url.searchParams.get("key") || "default";
      await env.MY_KV.delete(key);
      return new Response(`Deleted: ${key}`);
    }

    // Lister les clés (avec préfixe)
    if (path === "/list") {
      const prefix = url.searchParams.get("prefix") || "";
      const keys = await env.MY_KV.list({ prefix });
      
      const keyList = keys.keys.map(k => k.name).join("\n");
      return new Response(keyList || "No keys found");
    }

    return new Response("Try /put, /get, /delete, or /list");
  },
};

Lancement avec Wrangler :

wrangler dev
# Test écriture
curl "http://localhost:8787/put?key=test&value=helloworld"
# Test lecture
curl "http://localhost:8787/get?key=test"

Session Storage — implémentation complète

Cas d’usage le plus courant de KV :

// src/session.ts
interface SessionData {
  userId: string;
  email: string;
  createdAt: number;
  expiresAt: number;
}

interface Env {
  SESSION_KV: KVNamespace;
}

const SESSION_TTL = 3600; // expiration 1 heure

class SessionManager {
  private kv: KVNamespace;

  constructor(kv: KVNamespace) {
    this.kv = kv;
  }

  // Créer une session
  async create(userId: string, email: string): Promise<string> {
    const sessionId = crypto.randomUUID();
    const sessionData: SessionData = {
      userId,
      email,
      createdAt: Date.now(),
      expiresAt: Date.now() + SESSION_TTL * 1000,
    };

    // Écriture KV avec TTL (expiration auto)
    await this.kv.put(
      `session:${sessionId}`,
      JSON.stringify(sessionData),
      { expirationTtl: SESSION_TTL }
    );

    return sessionId;
  }

  // Lire une session
  async get(sessionId: string): Promise<SessionData | null> {
    const raw = await this.kv.get(`session:${sessionId}`);
    if (!raw) return null;

    try {
      return JSON.parse(raw) as SessionData;
    } catch {
      return null;
    }
  }

  // Supprimer une session (déconnexion)
  async delete(sessionId: string): Promise<void> {
    await this.kv.delete(`session:${sessionId}`);
  }

  // Rafraîchir une session (prolonger l'expiration)
  async refresh(sessionId: string): Promise<boolean> {
    const session = await this.get(sessionId);
    if (!session) return false;

    session.expiresAt = Date.now() + SESSION_TTL * 1000;
    await this.kv.put(
      `session:${sessionId}`,
      JSON.stringify(session),
      { expirationTtl: SESSION_TTL }
    );

    return true;
  }
}

// Point d'entrée Worker
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sessionManager = new SessionManager(env.SESSION_KV);
    const url = new URL(request.url);

    // Connexion (créer session)
    if (url.pathname === "/login" && request.method === "POST") {
      const body = await request.json();
      const sessionId = await sessionManager.create(
        body.userId as string,
        body.email as string
      );
      
      return new Response(JSON.stringify({ sessionId }), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // Vérifier session
    if (url.pathname === "/verify") {
      const sessionId = url.searchParams.get("sessionId");
      if (!sessionId) {
        return new Response("Missing sessionId", { status: 400 });
      }

      const session = await sessionManager.get(sessionId);
      if (!session) {
        return new Response("Session not found", { status: 401 });
      }

      return new Response(JSON.stringify(session), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // Déconnexion
    if (url.pathname === "/logout") {
      const sessionId = url.searchParams.get("sessionId");
      if (sessionId) {
        await sessionManager.delete(sessionId);
      }
      return new Response("Logged out");
    }

    return new Response("Not found", { status: 404 });
  },
};

Points clés :

  1. TTL auto : expirationTtl supprime les données expirées sans nettoyage manuel
  2. Préfixe de clé : session: pour filtrer et typer les données
  3. Sérialisation JSON : KV ne stocke que des chaînes ; objets complexes via JSON.stringify/parse

Cache de réponses API

Autre cas fréquent : mettre en cache les retours d’API tierces.

// src/api-cache.ts
interface Env {
  CACHE_KV: KVNamespace;
}

const DEFAULT_CACHE_TTL = 300; // cache 5 minutes

async function cachedFetch(
  kv: KVNamespace,
  cacheKey: string,
  url: string,
  ttl: number = DEFAULT_CACHE_TTL
): Promise<Response> {
  // Tentative lecture cache
  const cached = await kv.get(cacheKey, "text");
  
  if (cached) {
    console.log(`Cache hit: ${cacheKey}`);
    return new Response(cached, {
      headers: {
        "Content-Type": "application/json",
        "X-Cache": "HIT",
      },
    });
  }

  // Miss : appel API réel
  console.log(`Cache miss: ${cacheKey}`);
  const response = await fetch(url);
  const body = await response.text();

  // Écriture cache (cacheTtl optimise les lectures)
  await kv.put(cacheKey, body, {
    expirationTtl: ttl,
    // cacheTtl : cache edge plus long, moins de retours origine
  });

  return new Response(body, {
    headers: {
      "Content-Type": "application/json",
      "X-Cache": "MISS",
    },
  });
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const apiUrl = url.searchParams.get("api");

    if (!apiUrl) {
      return new Response("Missing api parameter", { status: 400 });
    }

    const cacheKey = `api:${apiUrl}`;
    
    return cachedFetch(env.CACHE_KV, cacheKey, apiUrl);
  },
};

Optimisation avec cacheTtl

Paramètre souvent négligé.

cacheTtl contrôle la durée du cache edge. Par défaut 60 secondes : lectures répétées de la même clé servies depuis l’edge sans retour origine.

Pour les données chaudes, augmentez cacheTtl :

// Données config très lues : cache edge plus long
await env.MY_KV.get("config:feature-flags", {
  cacheTtl: 3600, // 1 heure de cache edge
});

Même si le central store est inchangé, le nœud edge garde la valeur 1 heure. Idéal pour feature flags où un délai de propagation est acceptable.

KV vs D1 vs R2 — guide de choix

J’ai longtemps hésité entre toutes ces options Cloudflare. Voici un arbre de décision :

Arbre de décision par scénario

Quel type de données ?

├─ Fichiers (images, vidéos, PDF) ?
│   └─ OUI → R2

├─ Requêtes SQL (utilisateurs, commandes, jointures) ?
│   └─ OUI → D1

├─ Key-value simple, lecture >> écriture ?
│   ├─ Écriture &gt; 1 RPS/clé ?
│   │   └─ OUI → KV inadapté ; D1 ou Durable Objects
│   │
│   └─ NON → KV ✓

├─ Cohérence immédiate requise ?
│   └─ OUI → Durable Objects
│   └─ NON → KV peut convenir

└─ Incertain ?
    └─ Commencez par KV ; ne changez que si insuffisant
500µs-10ms
Latence hot key KV
50-200ms
Latence D1
25MB
Value max KV
Source: Documentation officielle Cloudflare

Tableau comparatif détaillé

DimensionKVD1R2
ModèleKey-ValueSQL (SQLite)Object Storage
Requêtesget/put/delete uniquementSQL completPas de requête, chemins uniquement
Limite écriture1 RPS par cléPas de limite durePas de limite dure
Latence lecture500µs - 10ms (hot)50-200 msRapide (téléchargement)
CohérenceEventuelleForte (mono-région)Eventuelle
Value max25 MoLimite ligne SQLite5 To par fichier
Quota gratuit100k lectures/jour5 Go + 25M lignes lues10 Go stockage
Cas typiquesSession, cache, configUtilisateurs, commandesFichiers, images, backup

Recommandations par scénario

Authentification / Session
KV

Session = key-value simple, lecture à chaque requête, écriture rare (login/logout). OpenAuth utilise KV par défaut.

// session:userId → données session
await env.SESSION_KV.put(`session:${sessionId}`, JSON.stringify(session));

Profils utilisateur / commandes
D1

Requêtes SQL (« toutes les commandes d’un utilisateur », « CA du mois dernier ») impossibles en get/put KV.

-- Requêtes complexes possibles en D1
SELECT * FROM orders WHERE user_id = ? AND created_at &gt; ?

Images / fichiers
R2

Fichiers > 25 Mo ou stockage objet natif.

// Stockage fichier R2
await env.MY_BUCKET.put("images/profile.jpg", imageBuffer);

Rate limiting API
KV (avec prudence)

Compteur key-value, mais écriture possible > 1 RPS. Compteur journalier OK ; rate limit précise à la seconde → Durable Objects ou Upstash Redis.

// Rate limiting simple (rafraîchi chaque jour)
const count = parseInt(await env.KV.get(`rate:${userId}`) || "0");
if (count &gt; 100) {
  return new Response("Rate limit exceeded", { status: 429 });
}
await env.KV.put(`rate:${userId}`, String(count + 1));

Cache API tierce
KV

Lecture fréquente, écriture rare ; TTL 5 minutes sans besoin de cohérence immédiate.

Exemple de combinaison

Un projet peut mixer plusieurs stockages :

interface Env {
  SESSION_KV: KVNamespace;   // sessions
  CACHE_KV: KVNamespace;     // cache API
  DATABASE_D1: D1Database;   // utilisateurs, commandes
  FILES_R2: R2Bucket;        // fichiers uploadés
}

// Une requête peut tout utiliser :
// 1. SESSION_KV pour la session
// 2. CACHE_KV pour le cache API tierce
// 3. DATABASE_D1 pour les commandes
// 4. FILES_R2 pour l'avatar

C’est la vraie force de l’écosystème Cloudflare.

Optimisation des performances

KV bien utilisé accélère ; mal utilisé, il devient un goulot. Quelques techniques testées en conditions réelles.

1. Ajuster cacheTtl

Par défaut 60 secondes. Pour les hot data, montez la valeur.

// ❌ Comportement par défaut : 60 s cache edge
await env.KV.get("config:feature-flags");

// ✅ Config : cache plus long
await env.KV.get("config:feature-flags", {
  cacheTtl: 3600, // 1 heure cache edge
});

Adapté : feature flags, config statique, règles de redirection.

Inadapté : session (état connexion immédiat), compteurs temps réel.

2. Appels parallèles, pas séquentiels

Piège fréquent : plusieurs clés lues l’une après l’autre.

// ❌ Séquentiel : latence cumulée
const user = await env.KV.get(`user:${userId}`);
const settings = await env.KV.get(`settings:${userId}`);
const permissions = await env.KV.get(`permissions:${userId}`);
// latence totale = 3 × latence unitaire

// ✅ Parallèle
const [user, settings, permissions] = await Promise.all([
  env.KV.get(`user:${userId}`),
  env.KV.get(`settings:${userId}`),
  env.KV.get(`permissions:${userId}`),
]);
// latence ≈ la plus lente des trois

Les appels KV sont asynchrones. Promise.all compresse la latence.

Mesure : 3 clés, séquentiel ~20 ms, parallèle ~8 ms.

60%
Réduction latence (parallèle vs séquentiel)
Source: Mesures internes

3. Stratégie hot keys

Les performances KV dépendent du « chaud » des clés.

// ❌ Clés trop dispersées, cold keys
await env.KV.get(`session:${userId}`); // un seul utilisateur

// ✅ Hot key pour données partagées
await env.KV.get("config:global-flags"); // tous les utilisateurs

Ne regroupez pas tout dans une clé. Règle :

  • Données privées : une clé par utilisateur (session, profil)
  • Données globales : une hot key (config, flags, redirections)

4. Organisation des namespaces

1000 namespaces par compte. Isolez par type :

# wrangler.toml
[[kv_namespaces]]
binding = "SESSION_KV"
id = "xxx"  # sessions

[[kv_namespaces]]
binding = "CACHE_KV"
id = "yyy"  # cache API

[[kv_namespaces]]
binding = "CONFIG_KV"
id = "zzz"  # configuration

Avantages :

  1. Nettoyage isolé : vider CACHE_KV sans toucher SESSION_KV
  2. TTL différents : session courte, config longue
  3. Monitoring séparé dans le Dashboard

5. Opérations batch

list() avec préfixe :

// Lister toutes les clés session
const result = await env.SESSION_KV.list({ prefix: "session:" });

for (const key of result.keys) {
  console.log(key.name);
}

// Pagination si beaucoup de clés
if (!result.list_complete) {
  const next = await env.SESSION_KV.list({
    prefix: "session:",
    cursor: result.cursor,
  });
}

Nettoyage batch (prudence) :

// Supprimer toutes les sessions (à utiliser avec discernement)
const keys = await env.SESSION_KV.list({ prefix: "session:" });
for (const key of keys.keys) {
  await env.SESSION_KV.delete(key.name);
}

Attention : consomme beaucoup de quota d’écriture.

Tarification et limites — maîtriser les coûts

Tarification KV généreuse, mais quelques limites peuvent faire planter votre app.

100,000
Lectures gratuites/jour
1,000
Écritures gratuites/jour
1 GB
Stockage gratuit
$5
Forfait Paid/mois
Source: Page tarifs Cloudflare

Free Plan vs Paid Plan

IndicateurFree PlanPaid Plan ($5/mois)
Lectures100 000 / jourIllimitées (facturées)
Écritures1 000 / jourIllimitées (facturées)
Suppressions1 000 / jourIllimitées (facturées)
Listes1 000 / jourIllimitées (facturées)
Stockage1 GoIllimité (facturé)
Namespaces10001000

Free suffit pour projets perso et tests. Production : Paid à $5/mois pour lectures illimitées (pay-as-you-go), plus d’écritures et monitoring Dashboard.

Write Rate Limit — la limite critique

Chaque clé unique : maximum 1 écriture par seconde (1 RPS)

Dépassement = erreur directe.

// ❌ Écritures rapides échouent
for (let i = 0; i &lt; 10; i++) {
  await env.KV.put("counter", String(i)); // 2e+ échec
}

// ✅ Clés dispersées dans le temps
await env.KV.put(`counter:${Math.floor(Date.now() / 1000)}`, value);
// une nouvelle clé par seconde

KV réplique chaque écriture mondialement ; Cloudflare limite pour protéger l’infrastructure.

Stratégies :

  1. Clés avec horodatage : counter:timestamp
  2. UUID par écriture
  3. D1 ou Durable Objects si écriture haute fréquence obligatoire

Limite de taille des values

Maximum 25 Mo (10 Mo → 25 Mo début 2025).

// ❌ &gt; 25 Mo = erreur
const largeData = generateBigString(30_000_000); // 30 Mo
await env.KV.put("large-key", largeData); // Error!

// ✅ Gros volumes → R2
await env.R2_BUCKET.put("large-key", largeData);

25 Mo suffit pour session, config et cache ; JSON volumineux ou fichiers → R2.

Stratégie de namespaces

1000 namespaces par compte (200 → 1000 en 2025).

// Groupement par fonction
SESSION_KV    // sessions
CACHE_KV      // cache API
CONFIG_KV     // configuration
RATE_LIMIT_KV // rate limiting

Namespaces épuisés ? Préfixes dans un seul namespace :

// Isolation par préfixe
await env.KV.put("session:user1", data);
await env.KV.put("cache:api1", data);
await env.KV.put("config:flags", data);

Formule d’estimation des coûts

Avec Paid plan :

Coût mensuel = $5 (base) + lectures + écritures + stockage

Lectures = nb lectures × $0,01 / 100 000
Écritures = nb écritures × $1,00 / 1 000 000
Stockage = taille × $0,50 / Go

Exemple : 100 000 requêtes/jour

  • Lectures : 100 000 × 30 = 3M/mois ≈ $0,30
  • Écritures : 1 000 × 30 = 30k/mois ≈ $0,03
  • Stockage : 10 Mo ≈ $0,005
  • Total : $5 + $0,33 ≈ $5,35/mois

Typique Cloudflare : abordable.

Conclusion

En une phrase : KV est la « mémoire de poche » de Workers — session, cache, config, lecture >> écriture.

Choisir KV si :

  • Données key-value simples
  • Lecture bien plus fréquente que l’écriture
  • Cohérence immédiate non requise
  • ≤ 1 écriture/seconde par clé

Passer à D1 si :

  • Requêtes SQL
  • Jointures complexes
  • Écriture possible > 1 RPS

Passer à R2 si :

  • Fichiers, images, vidéos
  • Value > 25 Mo

Passer à Durable Objects si :

  • Cohérence immédiate
  • Édition collaborative temps réel

Prochaine étape : brancher KV sur votre projet Workers. Commencez par session storage — le code ci-dessus est prêt à l’emploi. La doc Cloudflare est détaillée ; cette série cloudflare-bindui couvre D1 et R2 pour compléter la matrice de stockage.

FAQ

Quelle est la limite d'écriture de Cloudflare Workers KV ?
Chaque clé unique ne peut être écrite qu'une fois par seconde maximum (1 RPS). Au-delà, la requête échoue directement. Stratégies : disperser les clés avec un horodatage (ex. counter:timestamp), ou migrer vers D1/Durable Objects.
KV convient-il au stockage des sessions utilisateur ?
Très bien adapté. Les sessions sont du key-value simple, avec une fréquence de lecture élevée (validation à chaque requête) et une fréquence d'écriture faible (connexion/déconnexion uniquement). Avec TTL automatique, pas de nettoyage manuel.
Quelle différence entre KV et D1 ? Lequel choisir ?
Différences clés :

• KV : modèle Key-Value, hot keys 500µs-10ms, limite 1 RPS/clé
• D1 : modèle SQL (SQLite), requêtes complexes, pas de limite d'écriture stricte

Choix : SQL → D1 ; key-value simple et lecture-intensive → KV.
Pourquoi KV atteint-il une latence de 500µs-10ms ?
Architecture cache à trois niveaux : Edge Cache (nœuds edge) → Regional Cache → Central Store. Environ 30 % des requêtes sont servies directement depuis le cache edge. Après l'optimisation Cloudflare de 2025, la vitesse a triplé.
À quoi sert le paramètre cacheTtl ?
Contrôle la durée de vie du cache edge. Par défaut 60 secondes. Pour les données chaudes (feature flags, config), vous pouvez passer à 3600 secondes (1 heure) pour réduire les retours à l'origine.
Quelle taille maximale pour une value KV ?
25 Mo (passé de 10 Mo début 2025). Au-delà, erreur — pour images, vidéos, etc., utilisez R2 Object Storage.

15 min de lecture · Publié le: 22 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog