Global read replication
D1 read replication can lower latency for read queries and scale read throughput by adding read-only database copies, called read replicas, across regions globally closer to clients.
To use read replication, you must use the D1 Sessions API, otherwise all queries will continue to be executed only by the primary database.
A session encapsulates all the queries from one logical session for your application. For example, a session may correspond to all queries coming from a particular web browser session. All queries within a session read from a database instance which is as up-to-date as your query needs it to be. Sessions API ensures sequential consistency for all queries in a session.
To checkout D1 read replication, deploy the following Worker code using Sessions API, which will prompt you to create a D1 database and enable read replication on said database.
export default { async fetch(request, env, ctx) { const url = new URL(request.url);
// A. Create the Session. // When we create a D1 Session, we can continue where we left off from a previous // Session if we have that Session's last bookmark or use a constraint. const bookmark = request.headers.get("x-d1-bookmark") ?? "first-unconstrained"; const session = env.DB01.withSession(bookmark);
try { // Use this Session for all our Workers' routes. const response = await withTablesInitialized( request, session, handleRequest, );
// B. Return the bookmark so we can continue the Session in another request. response.headers.set("x-d1-bookmark", session.getBookmark() ?? "");
return response; } catch (e) { console.error({ message: "Failed to handle request", error: String(e), errorProps: e, url, bookmark, }); return Response.json( { error: String(e), errorDetails: e }, { status: 500 }, ); } },};
export default { async fetch(request, env, ctx): Promise<Response> { const url = new URL(request.url);
// A. Create the Session. // When we create a D1 Session, we can continue where we left off from a previous // Session if we have that Session's last bookmark or use a constraint. const bookmark = request.headers.get("x-d1-bookmark") ?? "first-unconstrained"; const session = env.DB01.withSession(bookmark);
try { // Use this Session for all our Workers' routes. const response = await withTablesInitialized( request, session, handleRequest, );
// B. Return the bookmark so we can continue the Session in another request. response.headers.set("x-d1-bookmark", session.getBookmark() ?? "");
return response; } catch (e) { console.error({ message: "Failed to handle request", error: String(e), errorProps: e, url, bookmark, }); return Response.json( { error: String(e), errorDetails: e }, { status: 500 }, ); } },} satisfies ExportedHandler<Env>;
When using D1 without read replication, D1 routes all queries (both read and write) to a specific database instance in one location in the world, known as the primary database instance . D1 request latency is dependent on the physical proximity of a user to the primary database instance. Users located further away from the primary database instance experience longer request latency due to network round-trip time ↗.
When using read replication, D1 creates multiple asynchronously replicated copies of the primary database instance, which only serve read requests, called read replicas . D1 creates the read replicas in multiple regions throughout the world across Cloudflare's network.
Even though a user may be located far away from the primary database instance, they could be close to a read replica. When D1 routes read requests to the read replica instead of the primary database instance, the user enjoys faster responses for their read queries.
D1 asynchronously replicates changes from the primary database instance to all read replicas. This means that at any given time, a read replica may be arbitrarily out of date. The time it takes for the latest committed data in the primary database instance to be replicated to the read replica is known as the replica lag . Replica lag and non-deterministic routing to individual replicas can lead to application data consistency issues. The D1 Sessions API solves this by ensuring sequential consistency. For more information, refer to replica lag and consistency model.
Type of database instance | Description | How it handles write queries | How it handles read queries |
---|---|---|---|
Primary database instance | The database instance containing the “original” copy of the database | Can serve write queries | Can serve read queries |
Read replica database instance | A database instance containing a copy of the original database which asynchronously receives updates from the primary database instance | Forwards any write queries to the primary database instance | Can serve read queries using its own copy of the database |
A system with multiple read replicas located around the world improves the performance of databases:
- The query latency decreases for users located close to the read replicas. By shortening the physical distance between a the database instance and the user, read query latency decreases, resulting in a faster application.
- The read throughput increases by distributing load across multiple replicas. Since multiple database instances are able to serve read-only requests, your application can serve a larger number of queries at any given time.
By using Sessions API for read replication, all of your queries from a single session read from a version of the database which ensures sequential consistency. This ensures that the version of the database you are reading is logically consistent even if the queries are handled by different read replicas.
D1 read replication achieves this by attaching a bookmark to each query within a session. For more information, refer to Bookmarks.
Read replication can be enabled at the database level in the Cloudflare dashboard. Check Settings for your D1 database to view if read replication is enabled.
-
In the Cloudflare dashboard, go to the D1 page.
Go to D1 SQL database -
Select an existing database > Settings > Enable Read Replication.
To create a session from any available database version, use withSession()
without any parameters, which will route the first query to any database instance, either the primary database instance or a read replica.
const session = env.DB.withSession() // synchronous// query executes on either primary database or a read replicaconst result = await session .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) .run()
withSession()
is the same aswithSession("first-unconstrained")
- This approach is best when your application does not require the latest database version. All queries in a session ensure sequential consistency.
- Refer to the D1 Workers Binding API documentation.
To create a session from the latest database version, use withSession("first-primary")
, which will route the first query to the primary database instance.
const session = env.DB.withSession(`first-primary`) // synchronous// query executes on primary databaseconst result = await session .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) .run()
- This approach is best when your application requires the latest database version. All queries in a session ensure sequential consistency.
- Refer to the D1 Workers Binding API documentation.
To create a new session from the context of a previous session, pass a bookmark
parameter to guarantee that the session starts with a database version that is at least as up-to-date as the provided bookmark
.
// retrieve bookmark from previous session stored in HTTP headerconst bookmark = request.headers.get('x-d1-bookmark') ?? 'first-unconstrained';
const session = env.DB.withSession(bookmark)const result = await session .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) .run()// store bookmark for a future sessionresponse.headers.set('x-d1-bookmark', session.getBookmark() ?? "")
- Starting a session with a
bookmark
ensures the new session will be at least as up-to-date as the previous session that generated the givenbookmark
. - Refer to the D1 Workers Binding API documentation.
To see how D1 requests are processed by the addition of read replicas, served_by_region
and served_by_primary
fields are returned in the meta
object of