Design wechseln

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:

  1. Globale Eindeutigkeit sichert – alle Schreibvorgänge laufen darüber, keine parallelen Konflikte auf derselben Zeile
  2. Transaktionslog führt – Recovery und Replika-Sync
  3. 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:

  1. Request am Edge
  2. Routing zum Durable Object in der Primärregion
  3. Schreiben in die Hauptdatei
  4. Asynchroner Sync zu Replikas weltweit

Lesen:

  1. Request am Edge
  2. Lesen von der lokalen Replika
  3. 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ösungLese-Latenz (p50)Lese-Latenz (p99)Schreib-Latenz (p50)Hinweis
D1~0,5 ms~2–5 ms~5–30 msLesen Edge-Replika, Schreiben Primär
Turso~0,02 ms~0,1 ms~15–50 msEmbedded Read, extrem schnell
PlanetScale~3–8 ms~10–20 ms~3–8 msMySQL-kompatibel, Proxy
PostgreSQL (Neon)~3–10 ms~20–50 ms~1–5 msKlassisch, Cold Start
0,5ms
D1 Lese-Latenz
p50, Edge-Replika
0,02ms
Turso Lese-Latenz
Embedded Read
500-2K
D1 Schreib-Durchsatz
writes/sec
10GB
Limit pro DB
Sharding nötig
Source: Offizielle Dokumentation und Community-Tests

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ösungLese-QPSSchreib-QPSHinweis
D110K–100K500–2KPro DB begrenzt
Tursopraktisch unbegrenzt (lokal)Sync-limitiertJeder Edge-Knoten liest lokal
PlanetScale10K–50K5K–20KSkalierung per Sharding
PostgreSQL10K–100K10K–50KInstanzgröße

5000 Schreibvorgänge/Sekunde: D1 wird eng – Sharding oder Wechsel.

Free-Tier

LösungSpeicherLesenSchreibenHinweis
D15 GB25 Mrd. Zeilen/Monat50 Mio. Zeilen/Monatmax. 10 GB/DB
Turso9 GB1 Mrd. Zeilen/Monat25 Mio. Zeilen/Monatinkl. Replikations-Traffic
PlanetScale1 GB10 Mrd. Zeilen/Monat10 Mrd. Zeilen/Monatkein Schreib-Limit
Neon0,5 GB100 Mio. Einheiten/Monat100 Mio. Einheiten/MonatEinheit = 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

Cloudflare D1 schnell einrichten

Vom Anlegen der Datenbank bis zu konsistenten Lesezugriffen mit der Sessions API

⏱️ Estimated time: 15 min

  1. 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. 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. 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. 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?
Beide sind Edge-SQLite-Datenbanken, die Architektur unterscheidet sich:

• 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?
Drei Ansätze:

• 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?
Je nach Szenario:

• 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?
Eher nicht. Single-Writer begrenzt den Durchsatz auf etwa 500–2000 writes/sec – PostgreSQL erreicht 10K–50K.

Bei Echtzeit-Auktionen, Log-Pipelines, IoT-Sammlung oder >1000 Schreibvorgängen/Sekunde: PostgreSQL, ClickHouse oder TimescaleDB.
Was beachten bei Migration von PostgreSQL zu D1?
Wesentliche Unterschiede:

• 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?
Nein. Cloudflare berechnet globale Lese-Replikation nicht separat; Transfer ist in der Abrechnung enthalten.

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

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog