Cloudflare D1 en pratique : SQLite en edge avec réplication mondiale

0,01 milliseconde.
C’est le temps pour lire une ligne SQLite en local. La même requête sur Cloudflare D1 prend environ 0,5 ms ; PostgreSQL en accès inter-région peut atteindre 1 à 3 ms — la différence paraît mince ? Mais si vos utilisateurs sont à Tokyo et la base en Virginie, le seul aller-retour réseau dépasse déjà 100 ms.
L’an dernier, sur un projet déployé mondialement, ce problème m’a bloqué. Les bases classiques imposent soit une latence élevée, soit un read/write split complexe. Puis Cloudflare a annoncé la réplication mondiale de D1 lors du Developer Week 2025 — et les choses ont commencé à changer.
Cet article explique comment D1 déplace SQLite vers l’edge, ce que signifient concrètement Durable Objects, horodatages Lamport et l’API Sessions, et quand choisir D1 — ou passer son chemin.
I. Qu’est-ce que D1 : SQLite sur le réseau edge
En bref, D1 est SQLite hébergé sur le réseau edge de Cloudflare, lisible et inscriptible depuis plus de 300 villes dans le monde.
Si vous pensez que c’est juste « SQLite + CDN », vous sous-estimez l’ambition du produit. SQLite classique a des limites en production : fichier unique difficile à distribuer, pas de reprise intégrée, écritures qui verrouillent toute la base. D1 repense ces contraintes.
En quoi diffère-t-il de SQLite traditionnel
Intégration. D1 tourne directement dans Cloudflare Workers ; vous interrogez la base comme une fonction :
// wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxx-xxxx-xxxx"
// Requête dans le Worker
export default {
async fetch(request, env) {
const { results } = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(1).all();
return Response.json(results);
}
}
Time Travel. D1 conserve automatiquement l’historique ; vous pouvez revenir à n’importe quel instant — un luxe pour SQLite. Compte gratuit : 30 jours ; comptes payants : plus long.
Réplication mondiale (mise à jour majeure 2025). Le nœud primary est dans une région ; les réplicas de lecture se synchronisent partout. Un utilisateur à Singapour lit depuis le réplica local — latence passée de trois chiffres à quelques millisecondes.
Limites à connaître avant de choisir
D1 n’est pas universel :
10 Go maximum par base. Au-delà : sharding ou autre solution. Jusqu’à 50 000 bases par compte — suffisant pour la plupart des projets ; si votre modèle est « une base par utilisateur », calculez bien.
Architecture à écrivain unique. Un seul nœud traite les écritures à la fois ; débit plafonné autour de 500-2 000 writes/sec, loin des 10K-50K de PostgreSQL. Enchères temps réel, pipelines de logs : D1 peut ne pas tenir.
Cohérence séquentielle, pas forte. Vous venez d’écrire : la lecture suivante peut ne pas voir la donnée — sauf si vous utilisez correctement l’API Sessions.
En pratique, D1 convient surtout aux applications Web lecture-intensives. La plupart des sites font plus de 90 % de lectures ; la réplication mondiale répond depuis le nœud le plus proche — gain tangible.
II. Architecture D1 : Durable Objects et réplication mondiale
Ce chapitre est plus technique, mais indispensable pour bien utiliser D1.
Durable Objects : un « majordome » par base
Le cœur de D1, ce sont les Durable Objects. Imaginez un processus dédié par base, qui :
- Garantit l’unicité globale : toutes les écritures passent par lui — pas de conflits concurrents sur la même ligne
- Maintient le journal des transactions : chaque écriture est enregistrée pour reprise et synchronisation des réplicas
- Coordonne les réplicas : indique aux copies mondiales quand mettre à jour
Design astucieux. Les bases distribuées classiques coordonnent plusieurs nœuds — latence et pannes compliquent tout. D1 : un primary, file d’attente pour les écritures, traitement séquentiel, puis sync asynchrone vers les réplicas.
Snapshot Isolation : les lectures ne bloquent pas
Un SELECT sur D1 ne fait pas la queue au primary : il lit un « instantané » sur le réplica le plus proche.
Exemple : primary à Pékin, réplicas à Tokyo, Singapour, Sydney. Un utilisateur à Tokyo lit depuis Tokyo — état figé au début de la requête. Même si le primary écrit en parallèle, votre lecture n’est pas bloquée.
Problème : vous écrivez puis lisez tout de suite — la donnée peut manquer, le réplica n’étant pas encore à jour.
D’où l’API Sessions.
Horodatages Lamport : donner un ordre au chaos
Leslie Lamport (1978) a proposé un ordonnancement logique des événements en système distribué — horodatages Lamport. Chaque événement a une horloge logique ; un événement ultérieur a un timestamp plus grand.
D1 s’en sert pour la cohérence séquentielle : dans une session, si vous écrivez puis lisez, D1 garantit de voir l’état post-écriture, pas un vieux réplica.
Après chaque écriture, D1 renvoie un « marque-page » (commit token) : « toutes les modifications jusqu’ici sont visibles ». À la requête suivante, avec ce token, D1 garantit un état au moins aussi récent.
Utilisateur → écrit commande → obtient commit token « abc123 »
Utilisateur → lit commande (token « abc123 ») → voit la donnée fraîche
Comment fonctionne la réplication mondiale
À la création, D1 choisit une « région primary » — par défaut le datacenter Cloudflare le plus proche, ou manuellement.
Écriture :
- Requête vers le nœud edge le plus proche
- Routage vers le Durable Object de la région primary
- Écriture sur le fichier primary
- Réplication asynchrone vers les réplicas mondiaux
Lecture :
- Requête vers le nœud edge le plus proche
- Lecture depuis le réplica local
- Avec session : application du marque-page de cohérence
Cloudflare annonce que la réplication mondiale n’est pas facturée en plus — le transfert est inclus. Les écritures restent routées vers le primary : latence selon la distance. Primary aux États-Unis, utilisateurs en Asie : latence d’écriture perceptible.
III. API Sessions en pratique : cohérence séquentielle en code
Passons à la pratique.
L’API Sessions (2025) résout le problème « écrire puis lire ». Si vous connaissez la cohérence causale de MongoDB ou les follower reads de CockroachDB, le principe est proche — un marqueur trace la causalité.
Usage minimal
// Créer une Session
const session = env.DB.withSession();
// Lecture routée vers le réplica proche
const { results } = await session.prepare(
"SELECT * FROM products WHERE category = ?"
).bind("electronics").all();
// Écriture routée automatiquement vers le primary
await session.prepare(
"INSERT INTO orders (user_id, product_id, quantity) VALUES (?, ?, ?)"
).bind(userId, productId, 2).run();
// Marque-page de cohérence de la session
const bookmark = session.latestCommitToken;
La clé : withSession(). Elle crée un contexte où toutes les opérations partagent la même vue de cohérence.
Trois modes de cohérence
1. first-unconstrained (défaut)
const session = env.DB.withSession("first-unconstrained");
Mode le plus permissif. Lecture depuis le réplica proche, même s’il n’est pas à jour. Listes produits, articles de blog — faible exigence de fraîcheur.
2. first-primary
const session = env.DB.withSession("first-primary");
La première lecture va au primary ; les suivantes aux réplicas. Vous voyez au moins l’état au moment de la création de session — utile pour voir « ce qu’on vient d’écrire » sans interroger le primary à chaque fois.
3. Reprendre une session avec un marque-page
// Récupérer le marque-page précédent depuis l'en-tête
const previousToken = request.headers.get("x-d1-token") ?? "first-unconstrained";
// Créer une session qui continue la précédente
const session = env.DB.withSession(previousToken);
// Opérations...
// Renvoyer le nouveau marque-page
response.headers.set("x-d1-token", session.latestCommitToken);
Usage le plus puissant. Stockez le marque-page côté client (Cookie, en-tête) pour la cohérence inter-requêtes.
Cas concret : e-commerce
Plateforme mondiale : navigation produits depuis le réplica proche (latence minimale) ; après commande, l’utilisateur doit voir sa commande.
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Token depuis l'en-tête (null à la première requête)
const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
const session = env.DB.withSession(token);
// Cas 1 : liste produits (cohérence forte non requise)
if (url.pathname === "/api/products") {
const { results } = await session.prepare(
"SELECT * FROM products WHERE status = ?"
).bind("active").all();
return new Response(JSON.stringify(results), {
headers: {
"Content-Type": "application/json",
"x-d1-token": session.latestCommitToken
}
});
}
// Cas 2 : créer commande (écriture → primary)
if (url.pathname === "/api/orders" && request.method === "POST") {
const body = await request.json();
await session.prepare(`
INSERT INTO orders (user_id, total_amount, status)
VALUES (?, ?, ?)
`).bind(body.userId, body.total, "pending").run();
// Lire immédiatement pour confirmer l'écriture
const order = await session.prepare(`
SELECT * FROM orders WHERE user_id = ?
ORDER BY created_at DESC LIMIT 1
`).bind(body.userId).first();
return new Response(JSON.stringify(order), {
headers: {
"Content-Type": "application/json",
"x-d1-token": session.latestCommitToken
}
});
}
// Cas 3 : détail commande (token garantit la cohérence)
if (url.pathname.startsWith("/api/orders/")) {
const orderId = url.pathname.split("/")[3];
const order = await session.prepare(
"SELECT * FROM orders WHERE id = ?"
).bind(orderId).first();
return new Response(JSON.stringify(order), {
headers: {
"Content-Type": "application/json",
"x-d1-token": session.latestCommitToken
}
});
}
}
}
Navigation : first-unconstrained, performance optimale. Après commande, le client conserve le token pour les requêtes suivantes.
Côté client
Stocker x-d1-token et l’envoyer à chaque requête :
// Exemple frontend
let d1Token = localStorage.getItem('d1-token') ?? 'first-unconstrained';
async function fetchProducts() {
const response = await fetch('/api/products', {
headers: { 'x-d1-token': d1Token }
});
d1Token = response.headers.get('x-d1-token');
localStorage.setItem('d1-token', d1Token);
return response.json();
}
async function createOrder(data) {
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-d1-token': d1Token
},
body: JSON.stringify(data)
});
d1Token = response.headers.get('x-d1-token');
localStorage.setItem('d1-token', d1Token);
return response.json();
}
Peu de code pour un gros problème. Sans ce mécanisme, après une commande la liste peut rester vide — mauvaise expérience.
IV. Benchmarks et comparaison concurrentielle
Les chiffres ne mentent pas. Comparaison des principales options, d’après docs officielles et retours communautaires.
Latence
| Solution | Latence lecture (p50) | Latence lecture (p99) | Latence écriture (p50) | Notes |
|---|---|---|---|---|
| D1 | ~0,5 ms | ~2-5 ms | ~5-30 ms | Lecture réplica edge, écriture primary |
| Turso | ~0,02 ms | ~0,1 ms | ~15-50 ms | Lecture embarquée, très rapide |
| PlanetScale | ~3-8 ms | ~10-20 ms | ~3-8 ms | MySQL compatible, proxy lecture/écriture |
| PostgreSQL (Neon) | ~3-10 ms | ~20-50 ms | ~1-5 ms | Architecture classique, cold start |
Turso en lecture : 0,02 ms, quasi accès mémoire locale — SQLite embarqué copié sur le nœud edge. Contrepartie : sync plus complexe, latence d’écriture plus élevée.
D1 : 0,5 ms en lecture, excellent pour l’edge. Écriture : routage vers le primary — distance physique = plancher de latence. Primary côte ouest US, utilisateur à Singapour : 30 ms minimum.
PlanetScale et Neon : mieux pour les apps classiques. Latence moins spectaculaire, mais écosystème mature. Procédures stockées, triggers, index riches : plus adaptés.
Débit
| Solution | Débit lecture (QPS) | Débit écriture (QPS) | Notes |
|---|---|---|---|
| D1 | 10K-100K | 500-2K | Limite par base |
| Turso | Illimité (lecture locale) | Limité par sync | Chaque nœud edge lit en local |
| PlanetScale | 10K-50K | 5K-20K | Extensible par sharding |
| PostgreSQL | 10K-100K | 10K-50K | Selon taille d’instance |
Point faible de D1 : écriture. Écrivain unique = plafond. Plus de 5 000 écritures/seconde : goulot ou sharding (complexité), ou autre solution.
Quotas gratuits
| Solution | Stockage | Lecture | Écriture | Remarques |
|---|---|---|---|---|
| D1 | 5 Go | 25 milliards lignes/mois | 50 millions lignes/mois | Max 10 Go/base |
| Turso | 9 Go | 1 milliard lignes/mois | 25 millions lignes/mois | Inclut trafic réplication |
| PlanetScale | 1 Go | 10 milliards lignes/mois | 10 milliards lignes/mois | Pas de limite d’écriture |
| Neon | 0,5 Go | 100 millions unités/mois | 100 millions unités/mois | unité = lecture ou écriture |
D1 est généreux en lecture (25 milliards de lignes). Attention à l’écriture : 50 millions/mois, ~1,66 million/jour — logs et tracking dépassent vite.
Tarification
D1 : à l’usage, sans minimum. Au-delà du gratuit : 0,001 $/million lignes lues, 0,10 $/million lignes écrites ; stockage 0,75 $/Go/mois.
Turso : « lignes lues » et « trafic réplication » — coût de réplication élevé si mises à jour fréquentes.
PlanetScale : lecture et écriture ; écriture moins chère que D1, lecture un peu plus.
Conseil : écosystème Cloudflare (Workers, KV, R2) → facturation D1 plus claire. Projet isolé : testez les trois avec vos données réelles.
V. Arbre de décision : quand choisir D1
Scénarios adaptés
Application lecture-intensive : site de contenu, e-commerce (navigation), blog, documentation. Plus de 90 % de lectures — réplication mondiale, latence en millisecondes.
Utilisateurs mondiaux : base mono-région = latence inter-océans. D1 rapproche les données de l’utilisateur.
Déjà sur Cloudflare Workers : intégration native, quelques lignes de config, pas de pool de connexions, pas de cold start DB.
Volume sous 10 Go : au-delà, sharding. Modèle « une base par locataire » : la limite importe moins.
Scénarios inadaptés
Écritures fréquentes : enchères, logs, IoT — dizaines de milliers d’écritures/seconde. PostgreSQL, ClickHouse, TimescaleDB.
Transactions complexes : niveau SQLite actuel ; pas SERIALIZABLE, pas de transactions cross-base, pas de procédures stockées avancées.
Plus de 10 Go : sharding possible mais lourd. Données volumineuses (séries temporelles, archives) : autre choix dès le départ.
Cohérence forte : D1 est en cohérence finale — lecture immédiate après écriture peut échouer sans Sessions API.
Migration depuis PostgreSQL
1. Différences de dialecte
SQLite vs PostgreSQL :
- Pas de clause
RETURNING(insert puis select) - Pas de type
SERIAL(INTEGER PRIMARY KEY AUTOINCREMENT) - Pas de
JSONB(TEXT+json_extract()) - Pas de type
ARRAY(table associée)
2. Outils de migration
# Exporter PostgreSQL
pg_dump --format=insert mydb > dump.sql
# Importer dans D1
npx wrangler d1 execute my-d1-database --file=dump.sql
Schémas complexes : ajustements manuels.
3. Mode de connexion
Longues connexions → appels stateless. ORM à adapter ou SQL natif. Prisma a un adaptateur D1, encore en évolution.
Décision rapide
Écritures > 1 000/seconde ?
├─ Oui → pas D1
└─ Non
└─ Cohérence forte requise ?
├─ Oui → pas D1 (ou Sessions API)
└─ Non
└─ Volume > 10 Go ?
├─ Oui → à évaluer prudemment
└─ Non → D1 convient bien
Conclusion
D1 en trois phrases : déploiement edge pour latence en millisecondes, serverless sans ops DB, Sessions API pour la cohérence post-écriture sans complexité excessive.
Ce n’est pas la solution universelle. Écritures fréquentes, transactions complexes, très gros volumes : PostgreSQL et bases temporelles restent pertinents. Pas de balle en argent — des compromis.
Application Web globale, lecture-intensive, déjà sur Workers : D1 mérite un essai. Créer une base de test prend quelques minutes :
# Créer la base
npx wrangler d1 create my-first-db
# Créer une table
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
# Insérer des données
npx wrangler d1 execute my-first-db --command="INSERT INTO users (name) VALUES ('test')"
Testez la latence Tokyo ↔ base côte ouest — vous saurez si D1 correspond à votre projet.
"D1 est la base SQLite edge de Cloudflare, avec réplication mondiale en lecture et expérience serverless. L’API Sessions utilise les horodatages Lamport pour la cohérence séquentielle et résout le problème classique « écrire puis lire » en système distribué."
Références
- Building D1: a Global Database — Blog Cloudflare, 2024
- D1 Global Read Replication Beta — Blog Cloudflare, 2025
- D1 Getting Started — Documentation Cloudflare
- The SQLite Renaissance — DEV Community, 2026
- Database Free Tier Comparison 2026 — Agent Deals
Démarrer rapidement avec Cloudflare D1
Du création de la base à la lecture cohérente via l'API Sessions
⏱️ Estimated time: 15 min
- 1
Step 1: Créer une base D1
Utilisez wrangler CLI pour créer la base :
```bash
npx wrangler d1 create my-first-db
```
Après création, vous obtenez un database_id à configurer dans wrangler.toml :
```toml
[[d1_databases]]
binding = "DB"
database_name = "my-first-db"
database_id = "your-database-id"
``` - 2
Step 2: Créer les tables
Exécutez du SQL pour créer le schéma :
```bash
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
```
Vous pouvez aussi exécuter un fichier SQL en lot :
```bash
npx wrangler d1 execute my-first-db --file=./schema.sql
``` - 3
Step 3: Utiliser l'API Sessions dans un Worker
Créez une connexion avec session pour garantir la cohérence après écriture :
```typescript
export default {
async fetch(request, env) {
// Récupérer le session token depuis l'en-tête
const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
const session = env.DB.withSession(token);
// Écrire des données
await session.prepare("INSERT INTO users (name) VALUES (?)")
.bind("test").run();
// Lire en garantissant la cohérence
const { results } = await session.prepare("SELECT * FROM users")
.all();
return new Response(JSON.stringify(results), {
headers: { "x-d1-token": session.latestCommitToken }
});
}
}
``` - 4
Step 4: Configurer la réplication mondiale en lecture
Spécifiez la région primaire dans wrangler.toml :
```toml
[[d1_databases]]
binding = "DB"
database_name = "my-first-db"
database_id = "your-database-id"
primary_location_hint = "apne1" # Région Tokyo
```
Codes de région disponibles :
- apne1 : Tokyo
- sfo1 : San Francisco
- eur3 : Francfort
FAQ
Quelle différence entre Cloudflare D1 et Turso ?
• D1 : écrivain unique, écritures routées vers le primary, latence de lecture ~0,5 ms — idéal pour lecture-intensive
• Turso : lecture embarquée, latence plus basse (~0,02 ms), mais synchronisation plus complexe
D1 s'intègre nativement à Cloudflare Workers avec un quota gratuit plus généreux (25 milliards de lignes lues/mois) ; Turso excelle en latence de lecture pour les cas ultra sensibles.
Comment contourner la limite de 10 Go par base D1 ?
• Sharding par module métier : une base par module
• Isolation par locataire : une base par tenant (jusqu'à 50 000 bases)
• Stockage hybride : données chaudes dans D1, froides vers R2 ou autre stockage objet
Si le volume dépasse durablement 10 Go, évaluez PlanetScale ou PostgreSQL classique.
Comment choisir entre les trois modes de l'API Sessions ?
• first-unconstrained (défaut) : listes produits, blogs — faible exigence de fraîcheur
• first-primary : voir les données « juste écrites » sans interroger le primary à chaque fois
• mode commit token : e-commerce, commandes — cohérence inter-requêtes
Recommandation e-commerce : first-unconstrained à la navigation, sauvegarder le token après commande, le renvoyer ensuite.
D1 convient-il aux écritures fréquentes ?
Si votre application ressemble à :
• enchères temps réel
• pipelines de logs, tracking
• collecte IoT
• plus de 1 000 écritures/seconde
Préférez PostgreSQL, ClickHouse ou TimescaleDB.
Migrer de PostgreSQL vers D1 : points d'attention ?
• Dialecte SQL : SQLite sans RETURNING, SERIAL, JSONB, ARRAY
• Connexion : appels stateless au lieu de connexions longues
• ORM : adaptateur Prisma pour D1, encore en évolution
Étapes :
1. Exporter avec pg_dump
2. Adapter manuellement le SQL incompatible
3. Importer via wrangler d1 execute
Testez d'abord sur un petit volume avant migration complète.
La réplication mondiale D1 est-elle facturée en plus ?
À noter :
• Les écritures restent routées vers le primary — latence selon la distance physique
• Primary aux États-Unis, utilisateurs en Asie : latence d'écriture ~30 ms+
• Quota lecture très élevé (25 milliards de lignes/mois), écriture 50 millions de lignes/mois
Placez le primary là où se concentrent vos utilisateurs.
11 min de lecture · Publié le: 5 mai 2026 · Mis à jour le: 27 juil. 2026
Cloudflare Full Stack
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Cloudflare Dynamic Workers : le secret d'un sandbox Agent IA 100× plus rapide que les conteneurs
Cloudflare Dynamic Workers utilise des V8 Isolates pour le sandbox des Agents IA : démarrage 100× plus rapide que les conteneurs, efficacité mémoire 10 à 100× supérieure. Analyse approfondie des principes techniques, mécanismes de sécurité, API pratiques et rapport coût-bénéfice, avec recommandations complètes de choix technologique.
Partie 20 sur 23
Suivant
Limites du plan gratuit Cloudflare : CDN, DNS, WAF, Workers — qu'est-ce qui suffit ?
Liste complète des limites du plan gratuit Cloudflare : Workers 100 000 requêtes/jour, Pages 500 builds/mois, DNS 1 000 enregistrements, R2 10 Go de stockage. Décidez si votre projet dépasse les quotas, avec tableau de décision par scénario et critères de montée en gamme.
Partie 22 sur 23



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire