April 1, 2024·6 min read·engineeringsqlited1replicationconsistency

D1 reaches general availability with a single database location

D1's production release expands storage and tooling. Its proposed replication design explains why putting SQL near a global application also requires an explicit consistency model.

A Worker can execute near a user while its database remains far away. Cloudflare's April 1 D1 announcement makes that limitation explicit alongside general availability. A D1 database currently resides in a single location. The same article describes a proposed read replication system, but that design is future work, separate from the database service becoming generally available today.

The distinction matters because the announcement's title, Building D1: a Global Database, covers both the release and the next architectural problem. Production readiness does not mean the service already has every property in that design. D1 now offers a production service with larger databases and improved operational tooling, while global read replication remains under development.

I find D1 interesting because SQLite already supplies the SQL engine and persistent representation that many applications need. A hosted service can take responsibility for provisioning and recovery around that engine. The harder question is what changes when application requests arrive globally and several copies of the data might eventually answer them.

What the production release changes

Cloudflare raises the supported per-database size to 10 GB, with 50,000 databases available on the Workers Paid plan. Those limits support an application structure in which a business entity or customer gets a separate database. Increasing one database's capacity and allowing many databases address different scaling decisions. Neither implies that all application data has to share one database.

The September 2023 beta announcement had already made the database-per-customer model part of D1's public direction. It also described row-based pricing and Time Travel recovery. General availability therefore follows a period of work on how developers create, inspect, and recover databases, rather than introducing SQL access for the first time.

The April release improves bulk imports through wrangler d1 execute and describes atomic import operations. An import that fails partway through should not leave a partially applied database. That property is relevant to migration and local development because an SQL file can contain many dependent changes. A sequence that is only partly present is a different starting state from either the original database or the completed import.

Wrangler also defaults execution to local development unless the caller requests the remote database. The announcement's commands make remote intent explicit. These examples use the command structure shown in the release, with example database and file names.

npx wrangler d1 execute example-db --remote --file=./schema.sql
npx wrangler d1 export example-db --remote --output=./database.sql

The explicit remote flag changes the consequence of a short command typed during development. The export command gives the application another way to move a database's SQL representation outside the service. These capabilities concern the working life of a database, beyond whether a request can execute a query successfully.

One active copy simplifies routing

The current design routes requests to one active D1 database copy. Cloudflare explains that it implements this arrangement using Durable Objects and their global uniqueness guarantee. The service can direct requests to the database's Durable Object rather than independently deciding which active replica should answer each operation.

That design gives D1 the snapshot isolation behavior Cloudflare describes for the existing interface. It also means geography remains relevant. Invoking application code near the user does not move the database into that location. A request that needs database access still has to reach the active copy.

Smart Placement addresses one part of that cost by placing Worker execution closer to a frequently accessed backend. It changes where application code runs. Read replication would change where data can be read. Those mechanisms can address different network paths, and the announcement distinguishes them before proposing the next design.

The architectural cost of a single active copy is therefore visible without treating the arrangement as a defect. Central routing makes some ordering questions easier to answer. Distributing reads across copies would introduce lag and force the service to define which histories an application can observe across requests.

Replica lag can reverse observed history

Asynchronous replication lets a replica answer reads without requiring every write to wait for that replica. The tradeoff is that the replica may not yet contain the latest committed changes. A read can therefore return an older committed state. That alone is already relevant to an application displaying a recently submitted update.

A second problem appears when related requests use different replicas. One replica can be more current than another. A user can read recent data from the first copy, then older data from the second copy on the next request. Time has advanced for the application, while the database state visible to it has moved backward.

This does not require either replica to return uncommitted data. Both answers can describe committed states. The failure concerns the relationship between successive observations. A program that assumes its later read includes what its earlier read saw can make an incorrect decision even though each individual result was once a valid committed state.

I consider that example the most useful part of the replication discussion. It identifies a concrete application assumption that geographic distribution can break. A promise about fast nearby reads is incomplete unless it also says how related reads preserve the progress an application has already observed.

The proposed session carries progress

Cloudflare proposes sessions that retain commit tokens. A token identifies progress through the database's ordered commits. The application supplies the last token it observed, and a replica delays a dependent read until it has received the required updates. Writes still go to the primary, which establishes their order.

This design would let the service select a replica without requiring every related request to reach the same machine. The replica has an explicit condition to satisfy before answering. A sufficiently current copy can proceed, while a lagging copy must wait. The token transfers the relevant ordering requirement along with the request.

The announcement describes sequential consistency for operations within the proposed session. That allows the application to read its own writes and retain ordering across dependent operations. It does not mean every independent session must begin at the newest possible state. The proposal explicitly discusses different choices for how a session's first query establishes its starting point.

One proposed option starts with the state available at the selected replica. Another sends the first query to the primary. A caller with a prior commit token can continue an earlier session's progress. These are design choices in the April proposal, rather than a reason to write production code against an assumed final Sessions API today.

A global service still needs an application boundary

The proposal also explains how a session could span separate HTTP requests. An application returns the latest token to a browser or mobile client, which sends it with the next request. The ordering requirement then survives beyond one Worker invocation. Without some transfer of that state, a later invocation cannot know which database progress the client has already observed.

The token's role differs from the location of the replica. Geography can guide where a read should run, but geography does not establish whether that copy has caught up. A nearby replica that is behind the required token cannot satisfy the same request immediately. This gives latency a dependency on both network distance and replication progress.

Cloudflare says the new API will be opt-in because it changes the consistency model. Existing API methods will retain their existing behavior and will not use replicas through this proposed mechanism. That is a significant compatibility choice. The service is not proposing to route existing application reads among asynchronous replicas without an application-level change.

Recovery and isolation already affect database structure

Time Travel, described in the earlier beta announcement, restores a database to a prior point using a bookmark. The database-per-customer model affects the scope of that operation. Restoring one customer's database has a different scope from restoring a shared database containing every customer's records. The unit of recovery becomes part of the data model decision.

This is one reason the count of supported databases matters alongside the maximum size. A design can distribute business entities across databases for isolation and recovery purposes, even before global read replication exists. It still needs to decide which application operations require data from more than one of those databases.

My preference for SQLite comes partly from keeping database machinery proportionate to the workload. D1's general availability makes managed recovery and SQL access available around that engine, with larger limits and more complete tooling. Its replication proposal adds a separate responsibility for applications that need distributed reads. They will have to identify the logical session whose observed progress should follow the user between requests, so the service can use that progress when choosing how a replica answers.