Cloudflare D1 in der Praxis: SQLite Edge-Datenbank mit globaler Replikation
0,01 Millisekunden.
So lange braucht SQLite lokal für eine Zeile. Dieselbe Abfrage auf Cloudflare D1: etwa 0,5 ms; PostgreSQL über Regionen hinweg oft 1–3 ms – klingt nach wenig? Liegen Ihre Nutzer in Tokio und die Datenbank in Virginia, frisst allein die Netzwerk-Roundtrip-Zeit schnell über 100 ms auf.
Bei einem global deployten Projekt letztes Jahr bin ich genau an diesem Punkt hängengeblieben: klassische Datenbanken mit hoher Latenz oder aufwendiger Read/Write-Trennung. Erst die globale Replikation von D1 auf der Cloudflare Developer Week 2025 machte die Sache interessant.
In diesem Artikel geht es darum, wie D1 SQLite an den Edge bringt, was Durable Objects, Lamport-Zeitstempel und Sessions API in der Praxis bedeuten – und wann D1 passt und wann nicht.
1. Was ist D1: SQLite am Edge
Kurz gesagt: D1 ist SQLite auf dem Cloudflare-Edge-Netzwerk – Lesen und Schreiben in über 300 Städten weltweit.
Wer „SQLite + CDN“ denkt, unterschätzt den Ansatz. Klassisches SQLite scheitert in Produktion oft an: Single-File ohne echte Verteilung, fehlender eingebauter Disaster Recovery, Schreib-Sperren auf die ganze Datei. D1 adressiert genau das.
Unterschiede zu klassischem SQLite
Integration. D1 läuft direkt in Cloudflare Workers – Abfragen wie Funktionsaufrufe:
// wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxx-xxxx-xxxx"
// Abfrage im 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. Historische DB-Versionen, Rollback zu beliebigen Zeitpunkten – für SQLite ungewöhnlich luxuriös. Free: 30 Tage, Paid länger.
Globale Replikation (Update 2025). Primär in einer Region, Lese-Replikas weltweit. Nutzer in Singapur lesen lokal – Latenz von dreistelligen Millisekunden auf einstellige Werte.
Harte Grenzen
10 GB pro Datenbank – danach Sharding oder andere Lösung. Bis 50.000 DBs pro Konto; bei „eine DB pro User“ vorher durchrechnen.
Single Writer. Ein Knoten schreibt – Durchsatz etwa 500–2000 writes/sec vs. 10K–50K bei PostgreSQL. Echtzeit-Auktionen, Log-Pipelines: eher nicht D1.
Sequentielle Konsistenz, keine starke Konsistenz. Gerade Geschriebenes ist beim nächsten Lesen evtl. noch nicht sichtbar – mit Sessions API steuerbar.
D1 passt am besten zu leseintensiven Web-Apps. Die meisten Sites haben >90 % Reads – globale Replikation bringt spürbaren Gewinn.
2. Architektur: Durable Objects und globale Replikation
Wer D1 ernsthaft nutzt, sollte diese Bausteine kennen.
Durable Objects: ein „Hausmeister“ pro Datenbank
Jede D1-Datenbank hat ein dediziertes Durable Object, das:
- Globale Eindeutigkeit sichert – alle Schreibvorgänge laufen darüber, keine parallelen Konflikte auf derselben Zeile
- Transaktionslog führt – Recovery und Replika-Sync
- Lese-Replikas koordiniert – wann welche Kopie aktualisiert wird
Statt Multi-Master-Koordination: ein Primär, Schreibqueue, asynchroner Sync zu Replikas.
Snapshot Isolation: Lesen blockiert nicht
SELECT wartet nicht in der Schreibqueue der Primär-DB, sondern liest einen Snapshot der nahen Replika.
Tokio-Nutzer lesen in Tokio – konsistenter Zustand zum Abfragezeitpunkt, auch wenn die Primär in Peking gerade schreibt.
Problem: direkt nach dem Schreiben evtl. noch alter Stand auf der Replika – dafür die Sessions API.
Lamport-Zeitstempel
Leslie Lamports logische Uhren (1978): jedes Ereignis hat einen Zeitstempel, Folgeereignisse sind strikt größer. D1 nutzt das für sequentielle Konsistenz innerhalb einer Session: Schreiben, dann Lesen – Sie sehen mindestens den Stand nach dem Schreiben.
Nach jedem Commit liefert D1 einen Commit Token („Lesezeichen“). Mit Token beim nächsten Request garantiert D1 Daten mindestens ab diesem Punkt.
Nutzer → Bestellung schreiben → Commit-Token „abc123“
Nutzer → Bestellung lesen (Token „abc123“) → garantiert aktueller Stand
Globale Replikation im Ablauf
Beim Anlegen wählen Sie eine Primärregion (Standard: nächstes Cloudflare-DC, manuell setzbar).
Schreiben:
- Request am Edge
- Routing zum Durable Object in der Primärregion
- Schreiben in die Hauptdatei
- Asynchroner Sync zu Replikas weltweit
Lesen:
- Request am Edge
- Lesen von der lokalen Replika
- Mit Session: Commit-Token erzwingt Mindest-Stand
Globale Replikation: laut Cloudflare ohne Zusatzgebühr. Schreiben geht trotzdem zur Primär – US-Primär, Nutzer in Asien: spürbare Schreib-Latenz.
3. Sessions API: sequentielle Konsistenz im Code
Konzeptuell ähnlich MongoDB causal consistency oder CockroachDB follower reads – ein Marker verfolgt Kausalität.
Basis
const session = env.DB.withSession();
const { results } = await session.prepare(
"SELECT * FROM products WHERE category = ?"
).bind("electronics").all();
await session.prepare(
"INSERT INTO orders (user_id, product_id, quantity) VALUES (?, ?, ?)"
).bind(userId, productId, 2).run();
const bookmark = session.latestCommitToken;
withSession() legt einen Session-Kontext an – alle Operationen teilen dieselbe Konsistenzsicht.
Drei Modi
1. first-unconstrained (Standard)
const session = env.DB.withSession("first-unconstrained");
Locker: Lesen von der nächsten Replika, auch wenn nicht ganz aktuell. Produktlisten, Blog.
2. first-primary
const session = env.DB.withSession("first-primary");
Erstes Lesen zur Primär, danach Replikas – mindestens Stand beim Session-Start. „Gerade geschrieben“ ohne jedes Mal Primär.
3. Session per Commit-Token fortsetzen
const previousToken = request.headers.get("x-d1-token") ?? "first-unconstrained";
const session = env.DB.withSession(previousToken);
// ... Operationen ...
response.headers.set("x-d1-token", session.latestCommitToken);
Token im Cookie oder Header – Konsistenz über mehrere Requests.
Praxis: E-Commerce
Browse: niedrige Latenz von Replikas. Nach Bestellung: Order muss sofort sichtbar sein.
export default {
async fetch(request, env) {
const url = new URL(request.url);
const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
const session = env.DB.withSession(token);
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
}
});
}
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();
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
}
});
}
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
}
});
}
}
}
Browse: first-unconstrained. Nach Bestellung: Token speichern, bei Order-Details mitsenden.
Client-Seite
x-d1-token speichern und bei jedem Request senden:
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();
}
Ohne das sieht der Nutzer nach der Bestellung eine leere Liste – schlechte UX bei wenig Code.
4. Performance und Wettbewerber
Zahlen aus offizieller Doku und Community-Tests.
Latenz
| Lösung | Lese-Latenz (p50) | Lese-Latenz (p99) | Schreib-Latenz (p50) | Hinweis |
|---|---|---|---|---|
| D1 | ~0,5 ms | ~2–5 ms | ~5–30 ms | Lesen Edge-Replika, Schreiben Primär |
| Turso | ~0,02 ms | ~0,1 ms | ~15–50 ms | Embedded Read, extrem schnell |
| PlanetScale | ~3–8 ms | ~10–20 ms | ~3–8 ms | MySQL-kompatibel, Proxy |
| PostgreSQL (Neon) | ~3–10 ms | ~20–50 ms | ~1–5 ms | Klassisch, Cold Start |
Turso bei Lesen nahezu lokal (0,02 ms) – SQLite-Datei am Edge, Sync-Aufwand beim Schreiben höher.
D1 mit ~0,5 ms Lesen top für Edge – Schreiben zur Primär: Tokio-Nutzer, Primär US-West → oft 30+ ms.
PlanetScale / Neon für komplexes SQL, Stored Procedures, reife Ökosysteme.
Durchsatz
| Lösung | Lese-QPS | Schreib-QPS | Hinweis |
|---|---|---|---|
| D1 | 10K–100K | 500–2K | Pro DB begrenzt |
| Turso | praktisch unbegrenzt (lokal) | Sync-limitiert | Jeder Edge-Knoten liest lokal |
| PlanetScale | 10K–50K | 5K–20K | Skalierung per Sharding |
| PostgreSQL | 10K–100K | 10K–50K | Instanzgröße |
5000 Schreibvorgänge/Sekunde: D1 wird eng – Sharding oder Wechsel.
Free-Tier
| Lösung | Speicher | Lesen | Schreiben | Hinweis |
|---|---|---|---|---|
| D1 | 5 GB | 25 Mrd. Zeilen/Monat | 50 Mio. Zeilen/Monat | max. 10 GB/DB |
| Turso | 9 GB | 1 Mrd. Zeilen/Monat | 25 Mio. Zeilen/Monat | inkl. Replikations-Traffic |
| PlanetScale | 1 GB | 10 Mrd. Zeilen/Monat | 10 Mrd. Zeilen/Monat | kein Schreib-Limit |
| Neon | 0,5 GB | 100 Mio. Einheiten/Monat | 100 Mio. Einheiten/Monat | Einheit = Read oder Write |
D1 großzügig beim Lesen; Schreiben ~1,66 Mio./Tag – Logging/Tracking kann schnell kippen.
Abrechnung
D1: Pay-as-you-go, $0,001/Mio. Zeilen Lesen, $0,10/Mio. Schreiben, $0,75/GB/Monat Speicher.
Turso: Zeilen + Replikations-Traffic – bei häufigen Updates teurer.
PlanetScale: ähnlich, Schreiben günstiger, Lesen etwas teurer.
Bereits tief in Workers/KV/R2: D1 vereinfacht die Rechnung. Sonst alle drei mit echten Lasten testen.
5. Entscheidungsbaum: wann D1?
D1 passt
- Leseintensiv: Content, E-Commerce-Browse, Docs, Blogs
- Globale Nutzer: Daten am Edge statt transozeanischer Roundtrips
- Cloudflare Workers im Einsatz: native Bindung, kein Connection Pool, kein Cold-Start-Drama
- <10 GB pro logischer DB oder natürlich viele kleine Tenant-DBs
D1 passt nicht
- Hohe Schreiblast: Auktionen, Logs, IoT – PostgreSQL, ClickHouse, TimescaleDB
- Komplexe Transaktionen: SERIALIZABLE, Cross-DB, Stored Procedures
- >10 GB ohne sauberes Sharding-Modell
- Strikte starke Konsistenz überall ohne Sessions API
Migration von PostgreSQL
SQL-Dialekt: kein RETURNING, kein SERIAL (→ INTEGER PRIMARY KEY AUTOINCREMENT), kein JSONB (TEXT + json_extract()), kein ARRAY (Relationstabellen).
Migration:
pg_dump --format=insert mydb > dump.sql
npx wrangler d1 execute my-d1-database --file=dump.sql
Komplexe Schemas oft manuell anpassen.
Verbindung: stateless statt Long-lived – ORM anpassen oder natives SQL; Prisma-D1-Adapter im Ausbau.
Schnellcheck
Schreibfrequenz > 1000/Sekunde?
├─ Ja → nicht D1
└─ Nein
└─ Starke Konsistenz überall?
├─ Ja → nicht D1 (oder Sessions API)
└─ Nein
└─ Daten > 10 GB?
├─ Ja → abwägen
└─ Nein → D1 gut geeignet
Zusammenfassung
D1 in drei Sätzen: Edge-Latenz im einstelligen ms-Bereich, Serverless ohne DB-Ops-Overhead, Sessions API löst Write-after-Read pragmatisch.
Nicht für hohe Schreiblast, komplexe Transaktionen oder sehr große monolithische Daten – dann PostgreSQL oder Spezial-DBs.
Global, leseintensiv, bereits auf Workers? In Minuten testen:
npx wrangler d1 create my-first-db
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
npx wrangler d1 execute my-first-db --command="INSERT INTO users (name) VALUES ('test')"
Latenz Tokio ↔ US-West selbst messen – dann wissen Sie, ob es passt.
"D1 ist Cloudflares SQLite-Edge-Datenbank mit globaler Lese-Replikation und Serverless-Betrieb. Die Sessions API nutzt Lamport-Zeitstempel für sequentielle Konsistenz und adressiert das klassische Write-after-Read-Problem verteilter Systeme."
Referenzen
- Building D1: a Global Database — Cloudflare Blog, 2024
- D1 Global Read Replication Beta — Cloudflare Blog, 2025
- D1 Getting Started — Cloudflare Dokumentation
- The SQLite Renaissance — DEV Community, 2026
- Database Free Tier Comparison 2026 — Agent Deals
Cloudflare D1 schnell einrichten
Vom Anlegen der Datenbank bis zu konsistenten Lesezugriffen mit der Sessions API
⏱️ Estimated time: 15 min
- 1
Step 1: D1-Datenbank anlegen
Mit der wrangler CLI:
```bash
npx wrangler d1 create my-first-db
```
Nach dem Anlegen erhalten Sie eine database_id – in wrangler.toml eintragen:
```toml
[[d1_databases]]
binding = "DB"
database_name = "my-first-db"
database_id = "your-database-id"
``` - 2
Step 2: Tabellen anlegen
SQL zum Erstellen der Struktur:
```bash
npx wrangler d1 execute my-first-db --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
```
Oder per SQL-Datei im Batch:
```bash
npx wrangler d1 execute my-first-db --file=./schema.sql
``` - 3
Step 3: Sessions API im Worker nutzen
Session-basierte Verbindung für Write-after-Read-Konsistenz:
```typescript
export default {
async fetch(request, env) {
const token = request.headers.get("x-d1-token") ?? "first-unconstrained";
const session = env.DB.withSession(token);
await session.prepare("INSERT INTO users (name) VALUES (?)")
.bind("test").run();
const { results } = await session.prepare("SELECT * FROM users")
.all();
return new Response(JSON.stringify(results), {
headers: { "x-d1-token": session.latestCommitToken }
});
}
}
``` - 4
Step 4: Globale Lese-Replikation konfigurieren
Primärregion in wrangler.toml:
```toml
[[d1_databases]]
binding = "DB"
database_name = "my-first-db"
database_id = "your-database-id"
primary_location_hint = "apne1" # Tokio
```
Region-Codes:
- apne1: Tokio
- sfo1: San Francisco
- eur3: Frankfurt
FAQ
Was ist der Unterschied zwischen Cloudflare D1 und Turso?
• D1: Single-Writer – Schreibvorgänge gehen zur Primärdatenbank, Lese-Latenz etwa 0,5 ms, ideal für leseintensive Workloads
• Turso: eingebettetes Lesen, Latenz noch niedriger (ca. 0,02 ms), Datensynchronisation komplexer
Vorteil D1: native Integration mit Cloudflare Workers, großzügiges Free-Tier (25 Mrd. Zeilen Lesen/Monat); Vorteil Turso: maximale Lese-Performance bei latenzkritischen Szenarien.
Wie umgeht man das 10-GB-Limit pro D1-Datenbank?
• Datenbank-Sharding nach Modulen
• Mandantenisolation: eine DB pro Tenant – bis zu 50.000 Datenbanken pro Konto
• Hybrid: Hot Data in D1, Cold Data in R2 oder anderem Objektspeicher
Bei dauerhaft wachsenden Daten über 10 GB: PlanetScale oder klassisches PostgreSQL prüfen.
Welchen der drei Sessions-API-Modi soll man wählen?
• first-unconstrained (Standard): Produktlisten, Blog – geringe Echtzeitanforderung
• first-primary: „gerade geschriebene“ Daten sichtbar, ohne jedes Mal die Primär-DB
• Commit-Token-Modus: E-Commerce-Bestellung und Folgeanfragen mit konsistentem Lesebild
Empfehlung E-Commerce: Browse mit first-unconstrained, nach Bestellung Token speichern und mitsenden.
Eignet sich D1 für hohe Schreibfrequenz?
Bei Echtzeit-Auktionen, Log-Pipelines, IoT-Sammlung oder >1000 Schreibvorgängen/Sekunde: PostgreSQL, ClickHouse oder TimescaleDB.
Was beachten bei Migration von PostgreSQL zu D1?
• SQL-Dialekt: SQLite ohne RETURNING, SERIAL, JSONB, ARRAY
• Verbindung: von Long-lived Connections zu stateless Funktionsaufrufen
• ORM: Prisma-D1-Adapter vorhanden, Funktionsumfang noch im Ausbau
Schritte: pg_dump, SQL manuell anpassen, wrangler d1 execute importieren – zuerst mit kleinem Datensatz testen.
Kostet globale Replikation bei D1 extra?
Hinweise:
• Schreibvorgänge laufen zur Primär-DB – Latenz hängt von der Entfernung ab
• Primär in den USA, Nutzer in Asien: Schreib-Latenz oft 30 ms+
• Lesen: 25 Mrd. Zeilen/Monat Free, Schreiben 50 Mio. Zeilen/Monat
Primärregion dort wählen, wo die meisten Nutzer sitzen.
8 Min. Lesezeit · Veröffentlicht am: 5. Mai 2026 · Aktualisiert am: 4. Juli 2026
Cloudflare Full Stack
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Cloudflare Dynamic Workers: Warum KI-Agent-Sandboxen 100× schneller als Container starten
Cloudflare Dynamic Workers nutzen V8 Isolates für KI-Agent-Sandboxen – Start in Millisekunden statt Sekunden, 10–100× weniger Speicher. Technik, Sicherheit, API-Praxis und Kosten im Vergleich.
Teil 20 von 23
Nächster
Cloudflare Free-Tier: Limits für CDN, DNS, WAF und Workers im Überblick
Vollständige Limit-Liste für Cloudflare Free: Workers 100.000 Anfragen/Tag, Pages 500 Builds/Monat, DNS 1.000 Einträge, R2 10 GB Speicher. Entscheidungshilfe mit Szenario-Tabelle.
Teil 22 von 23
Ähnliche Beiträge
Cloudflare Free Plan Limits 2026: Free, Pro und Business richtig wählen

Cloudflare Free Plan Limits 2026: Free, Pro und Business richtig wählen
Cloudflare Pages: Statischen Blog deployen – 5 Frameworks ohne typische Fehler


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen