Cloudflare D1
SQLite
Serverless
Banco de Dados
Edge

Cloudflare D1: The SQLite database at the edge — and why 'edge' doesn't mean what it seems

D1 does not run its queries in all Cloudflare data centers — it has a primary region where all writes happen, and read replicas with eventual consistency.

Cloudflare D1: The SQLite database at the edge — and why 'edge' doesn't mean what it seems

Calling D1 a “database at the edge” creates an expectation that the architecture does not fully meet. The mental image is of SQLite running in all 300 Cloudflare data centers simultaneously, with its queries responding from the point geographically closest to the user. The reality is more restricted: there is a primary region where all writes take place, and read replicas spread across the network that receive these writes with a delay of up to 60 seconds. If your primary bank is in North America and you write from a Worker running in São Paulo, that write travels 80 to 150 milliseconds round trip before being confirmed. Quickstart doesn't mention this.

The real architecture of D1

D1 uses SQLite as its database engine — the same SQLite that runs in browsers, mobile devices and desktop applications. On top of this engine, Cloudflare built a replication layer: a primary instance receives all writes and propagates changes to read replicas distributed across the global network.

When you create a D1 bank, you choose (or let Cloudflare automatically choose) your primary region. This choice determines where the writings land. A reading made by a Worker running in São Paulo can be served by a nearby replica, with an additional latency of 5 to 20 milliseconds over the Worker's execution time. A write made by the same Worker goes to the primary region — and if this region is us-east-1, the round-trip is 80 to 150 ms of network alone before you receive confirmation.

The replica propagation time is relevant in production: a write to the primary can take up to 60 seconds to appear on all replicas. During this interval, a Worker reading from an outdated replica sees pre-write data. This behavior is called eventual consistency — the replica will converge to the correct state, but not immediately.

The consistency problem you will encounter in production

The pattern that breaks with eventual consistency is writing followed immediately by reading. You create a user, redirect to the profile page, and the query that loads the profile hits a replica that has not yet received the INSERT. The result is an empty profile, or a 404 error, or an inconsistent state that the user sees and doesn't understand.

This problem exists in any database with replication — but with D1 it appears without warning because the binding abstraction hides which replica is being consulted. Cloudflare created the Sessions API exactly for this case: within the same D1 session, a write guarantees that the subsequent read will see the written data, regardless of which replica serves the query.

The Sessions API works by creating a session with env.DB.withSession(). Within the callback, all queries share the session context and D1 ensures that post-write reads return updated data. For flows that combine writing and immediate reading — account creation, setting updates, order completion — using Sessions API is the direct path to avoiding user-visible inconsistencies.

For purely read-only flows, such as lists and dashboards, eventual consistency is not a problem: data can be delayed for a few seconds without any noticeable impact.

Real price: when the free tier is no longer enough

D1's free tier delivers 5GB of storage, 5 million lines read per day and 100 thousand lines written per day. These numbers sound generous in the abstract, but D1's read cost is not based on rows returned — it's based on rows examined by the database engine.

A query that scans 100 thousand rows to return 5 thousand consumes 100 thousand readings, not 5 thousand. An application with a thousand active users making listing queries without adequate indexes can exhaust 5 million daily readings in a few hours. The free tier is sufficient for development, low-volume internal tools, and prototyping — not for applications with real user traffic.

When the free tier is not enough, the next step requires the Workers Paid plan ($5/month for the platform) plus the variable costs of D1: $0.001 per million lines read, $1 per million lines written and $0.75 per GB-month of storage. The writing cost has simple arithmetic: a registration flow that inserts 10 lines per user amounts to $10 in writing costs for every million registered users. For fast-growing applications, this number appears earlier than expected.

The cost of reads depends almost completely on the quality of the indexes. With suitable indexes, a query that returns 20 rows reads 20 rows. Without indexes, the same query can read 500 thousand lines and cost 500 times more.

How to plan the primary region before creating the bank

The D1 primary region is an irreversible decision in the creation of the bank. There is no option to move the primary later — the alternative is to export the data, create a new database in the desired region and import. This makes region choice one of the few infrastructure decisions that requires careful consideration before writing the first line of code.

The rule of thumb: the primary region should be where most writing originates. For a Brazilian product with Brazilian users, Worker probably runs on the São Paulo PoP (GRU) or similar. Cloudflare offers southamerica-east1 as a D1 primary region option — choosing this region for a Brazilian product reduces write latency from 80-150ms to 5-20ms.

To check which region makes the most sense, measure the Worker's network latency for the candidate region using a simple fetch with timestamp before and after. The cost of a wrong choice of primary region does not appear in development — it appears when the application is in production and each write adds 100ms of noticeable latency to the user.

The limit of 2GB per bank and 10 banks in the paid plan is also included in the initial planning. If the application tends to grow beyond 2GB, data partitioning between multiple D1 banks needs to be thought of before the first data arrives — refactoring this partitioning later, with data in production, is considerably more work.

Also read