diff --git a/public/images/d1/consistency-with-sessions-api.png b/public/images/d1/consistency-with-sessions-api.png new file mode 100644 index 000000000000000..341535e52828702 Binary files /dev/null and b/public/images/d1/consistency-with-sessions-api.png differ diff --git a/public/images/d1/consistency-without-sessions-api.png b/public/images/d1/consistency-without-sessions-api.png new file mode 100644 index 000000000000000..312feeda468b4b3 Binary files /dev/null and b/public/images/d1/consistency-without-sessions-api.png differ diff --git a/public/images/d1/d1-read-replication-concept.png b/public/images/d1/d1-read-replication-concept.png new file mode 100644 index 000000000000000..52a95fbc8176380 Binary files /dev/null and b/public/images/d1/d1-read-replication-concept.png differ diff --git a/src/content/apps/index.yaml b/src/content/apps/index.yaml index a83364c7e157f01..7fb77bd7bc7ebe2 100644 --- a/src/content/apps/index.yaml +++ b/src/content/apps/index.yaml @@ -340,3 +340,18 @@ languages: [TypeScript] cloudflare: false updated: 2024-10-07 +- link: https://github.com/harshil1712/e-com-d1 + id: E-commerce Store + description: An application to showcase D1 read replication in the context of an online store. + tags: [] + products: [Workers, D1] + languages: [TypeScript] + cloudflare: true + updated: 2025-02-27 +- link: https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template + id: Starter code for D1 Sessions API + description: An introduction to D1 Sessions API. This demo simulates purchase orders administration. + products: [Workers, D1] + languages: [TypeScript] + cloudflare: true + updated: 2025-03-31 \ No newline at end of file diff --git a/src/content/changelog/d1/2025-04-10-d1-read-replication-beta.mdx b/src/content/changelog/d1/2025-04-10-d1-read-replication-beta.mdx new file mode 100644 index 000000000000000..32ea1c64f77dbac --- /dev/null +++ b/src/content/changelog/d1/2025-04-10-d1-read-replication-beta.mdx @@ -0,0 +1,34 @@ +--- +title: D1 Read Replication Public Beta +description: Use D1 Sessions API to leverage read replication. +products: + - d1 + - workers +date: 2025-04-10T06:00:00Z +hidden: true +--- + +D1 read replication is available in public beta to help lower average latency and increase overall throughput for read-heavy applications like e-commerce websites or content management tools. + +Workers can leverage read-only database copies, called read replicas, by using D1 [Sessions API](/d1/best-practices/read-replication). 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. With Sessions API, D1 queries in a session are guaranteed to be [sequentially consistent](/d1/best-practices/read-replication/#replica-lag-and-consistency-model) to avoid data consistency pitfalls. D1 [bookmarks](/d1/reference/time-travel/#bookmarks) can be used from a previous session to ensure logical consistency between sessions. + +```ts +// retrieve bookmark from previous session stored in HTTP header +const 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 session +response.headers.set('x-d1-bookmark', session.getBookmark() ?? "") +``` + +Read replicas are automatically created by Cloudflare (currently one in each supported [D1 region](/d1/best-practices/read-replication/#read-replica-locations)), are active/inactive based on query traffic, and are transparently routed to by Cloudflare at no additional cost. + +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. + + [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/staging/d1-starter-sessions-api) + +To learn more about how read replication was implemented, go to our [blog post](https://blog.cloudflare.com/d1-read-replication-beta). + diff --git a/src/content/docs/d1/best-practices/read-replication.mdx b/src/content/docs/d1/best-practices/read-replication.mdx new file mode 100644 index 000000000000000..3a5e11d23a0bf9c --- /dev/null +++ b/src/content/docs/d1/best-practices/read-replication.mdx @@ -0,0 +1,468 @@ +--- +title: Global read replication +pcx_content_type: concept +sidebar: + order: 90 +--- + +import { GlossaryTooltip, Details, GitHubCode, APIRequest, Tabs, TabItem, TypeScriptExample } from "~/components" + +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. + +Your application can use read replicas with D1 [Sessions API](/d1/worker-api/d1-database/#withsession). 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](/d1/best-practices/read-replication/#replica-lag-and-consistency-model) 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. + + [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template) + +:::note[Tip: Place your database further away for the read replication demo] +To simulate how read replication can improve a worst case latency scenario, set your D1 database location hint to be in a farther away region. For example, if you are in Europe create your database in Western North America (WNAM). +::: + + + +## Primary database instance vs read replicas + +![D1 read replication concept](/images/d1/d1-read-replication-concept.png) + +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](/d1/configuration/data-location/), 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](https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/). + +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](/d1/best-practices/read-replication/#read-replica-locations) 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](/d1/best-practices/read-replication/#replica-lag-and-consistency-model). + +:::note +All write queries are still forwarded to the primary database instance. Read replication only improves the response time for read query requests. +::: + +| 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 | + +## Benefits of read replication + +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. + +## Use Sessions API + +By using [Sessions API](/d1/worker-api/d1-database/#withsession) 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](/d1/reference/time-travel/#bookmarks). + +### Enable read replication + +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. + +1. Go to [**Workers & Pages** > **D1**](https://dash.cloudflare.com/?to=/:account/workers/d1). +2. Select an existing database > **Settings** > **Enable Read Replication**. + +### Start a session without constraints + +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. + +```ts +const session = env.DB.withSession() // synchronous +// query executes on either primary database or a read replica +const result = await session + .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) + .run() +``` + +- `withSession()` is the same as `withSession("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](/d1/worker-api/d1-database#withsession). + +{/* #### Example of a D1 session without constraints + +Suppose you want to develop a feature for displaying “likes” on a social network application. + +The number of likes is a good example of a situation which does not require the latest information all the time. When displaying the number of likes of a post, the first request starts a new D1 session using the constraint `first-unconstrained`, which will be served by the nearest D1 read replica. + +Subsequent interactions on the application should continue using the same session by passing the `bookmark` from the first query to subsequent requests. This guarantees that all interactions will observe information at least as up-to-date as the initial request, and therefore never show information older than what a user has already observed. The number of likes will be updated with newer counts over time with subsequent requests as D1 asynchronously updates the read replicas with the changes from the primary database. + +```js +async function getLikes(postId: string, db: D1Database, bookmark: string | null): GetLikesResult { + // NOTE: Achieve sequential consistency with given bookmark, + // or start a new session that can be served by any replica. + const session = db.withSession(bookmark ?? "first-unconstrained"); + const { results } = session + .prepare("SELECT * FROM likes WHERE postId = ?") + .bind(postId) + .run(); + return { bookmark: session.getBookmark(), likes: results }; +} +``` */} + +### Start a session with all latest data + +To create a session from the latest database version, use `withSession("first-primary")`, which will route the first query to the primary database instance. + +```ts +const session = env.DB.withSession(`first-primary`) // synchronous +// query executes on primary database +const 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](/d1/worker-api/d1-database#withsession). + +{/* #### Example of using `first-primary` + +Suppose you want to develop a webpage for an electricity provider which lists the electricity bill statements. An assumption here is that each statement is immutable. Once issued, it never changes. + +In this scenario, you want the first request of the page to show a list of all the statements and their issue dates. Therefore, the first request starts a new D1 session using the constraint `first-primary` to get the latest information (ensuring that the list includes all issued bill statements) from the primary database instance. + +Then, when opening an individual electricity bill statement, we can continue using the same session by passing the `bookmark` from the first query to subsequent requests. Since each bill statement is immutable, any bill statement listed from the first query is guaranteed to be available in subsequent requests using the same session. + +```ts +async function listBillStatements(accountId: string, db: D1Database): Promise { + const session = db.withSession('first-primary'); + const { results } = (await session.prepare('SELECT * FROM bills WHERE accountId = ?').bind(accountId).run()) as unknown as { + results: Bill[]; + }; + return { bookmark: session.getBookmark() ?? 'first-unconstrained', bills: results }; +} + +async function getBillStatement(accountId: string, billId: string, bookmark: string, db: D1Database): Promise { + // NOTE: We achieve sequential consistency with the given `bookmark`. + const session = db.withSession(bookmark); + const result = (await session + .prepare('SELECT * FROM bills WHERE accountId = ? AND billId = ? LIMIT 1') + .bind(accountId, billId) + .first()) as unknown as Bill; + + return { bookmark: session.getBookmark() ?? 'first-unconstrained', bill: result }; +} +``` */} + +### Start a session from previous context (bookmark) + +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`. + +```ts +// retrieve bookmark from previous session stored in HTTP header +const 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 session +response.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 given `bookmark`. +- Refer to the [D1 Workers Binding API documentation](/d1/worker-api/d1-database#withsession). + +{/* #### Example of using `bookmark` + +This example follows from [Example of using `first-primary`](/d1/best-practices/read-replication/#example-of-using-first-primary), but retrieves the `bookmark` from HTTP cookie. + +```ts collapse={1-10, 22-42, 61-86} +import { ListBillStatementsResult, GetBillStatementResult, Bill } from './types'; + +async function listBillStatements(accountId: string, db: D1Database): Promise { + const session = db.withSession('first-primary'); + const { results } = (await session.prepare('SELECT * FROM bills WHERE accountId = ?').bind(accountId).run()) as unknown as { + results: Bill[]; + }; + return { bookmark: session.getBookmark() ?? 'first-unconstrained', bills: results }; +} + +async function getBillStatement(accountId: string, billId: string, bookmark: string, db: D1Database): Promise { + // NOTE: We achieve sequential consistency with the given `bookmark`. + const session = db.withSession(bookmark); + const result = (await session + .prepare('SELECT * FROM bills WHERE accountId = ? AND billId = ? LIMIT 1') + .bind(accountId, billId) + .first()) as unknown as Bill; + + return { bookmark: session.getBookmark() ?? 'first-unconstrained', bill: result }; +} + +export default { + async fetch(request, env, ctx): Promise { + // URL path + const url = new URL(request.url); + const path = url.pathname; + + // Method + const method = request.method; + + // Fetch using first-unconstrained + if (path === '/bills' && method === 'GET') { + // List bills + const result = await listBillStatements('1', env.DB); + return new Response(JSON.stringify(result), { status: 200 }); + } + if (path === '/bill' && method === 'GET') { + // Get bill + const result = await getBillStatement('1', '1', 'first-unconstrained', env.DB); + return new Response(JSON.stringify(result), { status: 200 }); + } + + // Fetch using bookmark from cookie + if (path === '/bill/cookie' && method === 'GET') { + // Get bill + const cookie = request.headers.get('Cookie'); + const bookmark = + cookie + ?.split(';') + .find((c) => c.trim().startsWith('X-D1-Bookmark')) + ?.split('=')[1] ?? 'first-unconstrained'; + console.log('bookmark', bookmark); + const result = await getBillStatement('1', '1', bookmark, env.DB); + return new Response(JSON.stringify(result), { + status: 200, + headers: { + 'Set-Cookie': `X-D1-Bookmark=${result.bookmark}; Path=/; SameSite=Strict`, + }, + }); + } + + // To ingest data + if (path === '/bill' && method === 'POST') { + // Create bill + const { accountId, amount, description, due_date } = await request.json(); + const session = env.DB.withSession('first-primary'); + const { results } = await session + .prepare('INSERT INTO bills (accountId, amount, description, due_date) VALUES (?, ?, ?, ?) RETURNING *') + .bind(accountId, amount, description, due_date) + .run(); + const bookmark = session.getBookmark() ?? 'first-unconstrained'; + + return new Response(JSON.stringify(results), { + status: 201, + headers: { + // Set bookmark cookie + 'Set-Cookie': `X-D1-Bookmark=${bookmark}; Path=/; SameSite=Strict`, + }, + }); + } + return new Response('Not Found', { + status: 404, + statusText: 'Not Found', + }); + }, +} satisfies ExportedHandler; +``` */} + +### Check where D1 request was processed + +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 [D1 Result](/d1/worker-api/return-object/#d1result). + +```ts +const result = await env.DB.withSession() + .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) + .run(); +console.log({ + servedByRegion: result.meta.served_by_region ?? "", + servedByPrimary: result.meta.served_by_primary ?? "", +}); +``` + +- `served_by_region` and `served_by_primary` fields are present for all D1 remote requests, regardless of whether read replication is enabled or if the Sessions API is used. On local development, `npx wrangler dev`, these fields are `undefined`. + +### Enable read replication via REST API + +With the REST API, set `read_replication.mode: auto` to enable read replication on a D1 database. + +For this REST endpoint, you need to have an API token with `D1:Edit` permission. If you do not have an API token, follow the guide: [Create API token](/fundamentals/api/get-started/create-token/). + + + +```curl +curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/d1/database/{database_id}" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"read_replication": {"mode": "auto"}}' +``` + + +```ts +const headers = new Headers({ + "Authorization": `Bearer ${TOKEN}` +}); + +await fetch ("/v4/accounts/{account_id}/d1/database/{database_id}", { + method: "PUT", + headers: headers, + body: JSON.stringify( + { "read_replication": { "mode": "auto" } } + ) + } +) +``` + + + +### Disable read replication via REST API + +With the REST API, set `read_replication.mode: disabled` to disable read replication on a D1 database. + +For this REST endpoint, you need to have an API token with `D1:Edit` permission. If you do not have an API token, follow the guide: [Create API token](/fundamentals/api/get-started/create-token/). + +:::note +Disabling read replication takes up to 24 hours for replicas to stop processing requests. Sessions API works with databases that do not have read replication enabled, so it is safe to run code with Sessions API even after disabling read replication. +::: + + + +```curl +curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/d1/database/{database_id}" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"read_replication": {"mode": "disabled"}}' +``` + +```ts +const headers = new Headers({ + "Authorization": `Bearer ${TOKEN}` +}); + +await fetch ("/v4/accounts/{account_id}/d1/database/{database_id}", { + method: "PUT", + headers: headers, + body: JSON.stringify( + { "read_replication": { "mode": "disabled" } } + ) + } +) +``` + + + +### Check if read replication is enabled + +On the Cloudflare dashboard, check **Settings** for your D1 database to view if read replication is enabled. + +Alternatively, `GET` D1 database REST endpoint returns if read replication is enabled or disabled. + +For this REST endpoint, you need to have an API token with `D1:Read` permission. If you do not have an API token, follow the guide: [Create API token](/fundamentals/api/get-started/create-token/). + + + +```curl +curl -X GET "https://api.cloudflare.com/client/v4/accounts/{account_id}/d1/database/{database_id}" \ + -H "Authorization: Bearer $TOKEN" +``` + + +```ts +const headers = new Headers({ + "Authorization": `Bearer ${TOKEN}` +}); + +const response = await fetch("/v4/accounts/{account_id}/d1/database/{database_id}", { + method: "GET", + headers: headers +}); + +const data = await response.json(); +console.log(data.read_replication.mode); +``` + + + +- Check the `read_replication` property of the `result` object + - `"mode": "auto"` indicates read replication is enabled + - `"mode": "disabled"` indicates read replication is disabled + +## Read replica locations + +Currently, D1 automatically creates a read replica in [every supported region](/d1/configuration/data-location/#available-location-hints), including the region where the primary database instance is located. These regions are: + +- ENAM +- WNAM +- WEUR +- EEUR +- APAC +- OC + +:::note +Read replica locations are subject to change at Cloudflare's discretion. +::: + +## Observability + +To see the impact of read replication and check the how D1 requests are processed by additional database instances, you can use: + +- The `meta` object within the [`D1Result`](/d1/worker-api/return-object/#d1result) return object, which includes new fields: + - `served_by_region` + - `served_by_primary` +- The [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/workers/d1), where you can view your database metrics breakdown by region that processed D1 requests. + +## Known limitations + +There are some known limitations for D1 read replication. + +- Sessions API is only available via the [D1 Worker Binding](/d1/worker-api/d1-database/#withsession) and not yet available via the REST API. + +## Background information + +### Replica lag and consistency model + +To account for replica lag, it is important to consider the consistency model for D1. A consistency model is a logical framework that governs how a database system serves user queries (how the data is updated and accessed) when there are multiple database instances. Different models can be useful in different use cases. Most database systems provide [read committed](https://jepsen.io/consistency/models/read-committed), [snapshot isolation](https://jepsen.io/consistency/models/snapshot-isolation), or [serializable](https://jepsen.io/consistency/models/serializable) consistency models, depending on their configuration. + +#### Without Sessions API + +Consider what could happen in a distributed database system. + +![Distributed replicas could cause inconsistencies without Sessions API](/images/d1/consistency-without-sessions-api.png) + +1. Your SQL write query is processed by the primary database instance. +2. You obtain a response acknowledging the write query. +3. Your subsequent SQL read query goes to a read replica. +4. The read replica has not yet been updated, so does not contain changes from your SQL write query. The returned results are inconsistent from your perspective. + +#### With Sessions API + +When using D1 Sessions API, your queries obtain bookmarks which allows the read replica to only serve sequentially consistent data. + +![D1 offers sequential consistency when using Sessions API](/images/d1/consistency-with-sessions-api.png) + +1. SQL write query is processed by the primary database instance. +2. You obtain a response acknowledging the write query. You also obtain a bookmark (100) which identifies the state of the database after the write query. +3. Your subsequent SQL read query goes to a read replica, and also provides the bookmark (100). +4. The read replica will wait until it has been updated to be at least as up-to-date as the provided bookmark (100). +5. Once the read replica has been updated (bookmark 104), it serves your read query, which is now sequentially consistent. + +In the diagram, the returned bookmark is bookmark 104, which is different from the one provided in your read query (bookmark 100). This can happen if there were other writes from other client requests that also got replicated to the read replica in between the two write/read queries you executed. + +#### Sessions API provides sequential consistency + +D1 read replication offers [sequential consistency](https://jepsen.io/consistency/models/sequential). D1 creates a global order of all operations which have taken place on the database, and can identify the latest version of the database that a query has seen, using [bookmarks](/d1/reference/time-travel/#bookmarks). It then serves the query with a database instance that is at least as up-to-date as the bookmark passed along with the query to execute. + +Sequential consistency has properties such as: + +- **Monotonic reads**: If you perform two reads one after the other (read-1, then read-2), read-2 cannot read a version of the database prior to read-1. +- **Monotonic writes**: If you perform write-1 then write-2, all processes observe write-1 before write-2. +- **Writes follow reads**: If you read a value, then perform a write, the subsequent write must be based on the value that was just read. +- **Read my own writes**: If you write to the database, all subsequent reads will see the write. + +## Supplementary information + +You may wish to refer to the following resources: + +- Blog: [Sequential consistency without borders: How D1 implements global read replication](https://blog.cloudflare.com/d1-read-replication-beta/) +- Blog: [Building D1: a Global Database](https://blog.cloudflare.com/building-d1-a-global-database/) +- [D1 Sessions API documentation](/d1/worker-api/d1-database#withsession) +- [Starter code for D1 Sessions API demo](https://github.com/cloudflare/templates/tree/staging/d1-starter-sessions-api) +- [E-commerce store read replication tutorial](/d1/tutorials/using-read-replication-for-e-com) \ No newline at end of file diff --git a/src/content/docs/d1/configuration/data-location.mdx b/src/content/docs/d1/configuration/data-location.mdx index 445506e0cd6a023..1f19d1d16225231 100644 --- a/src/content/docs/d1/configuration/data-location.mdx +++ b/src/content/docs/d1/configuration/data-location.mdx @@ -9,13 +9,13 @@ Learn how the location of data stored in D1 is determined, including where the l ## Automatic (recommended) -By default, D1 will automatically create your database in a location close to where you issued the request to create a database. In most cases this allows D1 to choose the optimal location for your database on your behalf. +By default, D1 will automatically create your primary database instance in a location close to where you issued the request to create a database. In most cases this allows D1 to choose the optimal location for your database on your behalf. ## Provide a location hint -Location hint is an optional parameter you can provide to indicate your desired geographical location for your database. +Location hint is an optional parameter you can provide to indicate your desired geographical location for your primary database instance. -You may want to explicitly provide a location hint in cases where the majority of your writes to a specific database come from a different location than where you are creating the database from. location hints can be useful when: +You may want to explicitly provide a location hint in cases where the majority of your writes to a specific database come from a different location than where you are creating the database from. Location hints can be useful when: - Working in a distributed team. - Creating databases specific to users in specific locations. @@ -33,9 +33,7 @@ Providing a location hint does not guarantee that D1 runs in your preferred loca ### Use Wrangler :::note - To install Wrangler, the command-line interface for D1 and Workers, refer to [Install and Update Wrangler](/workers/wrangler/install-and-update/). - ::: To provide a location hint when creating a new database, pass the `--location` flag with a valid location hint: @@ -70,3 +68,11 @@ D1 supports the following location hints: :::caution D1 location hints are not currently supported for South America (`sam`), Africa (`afr`), and the Middle East (`me`). D1 databases do not run in these locations. ::: + +## Read replica locations + +With read replication enabled, D1 creates and distributes read-only copies of the primary database instance around the world. This reduces the query latency for users located far away from the primary database instance. + +When using D1 read replication, D1 automatically creates a read replica in [every available region](/d1/configuration/data-location#available-location-hints), including the region where the primary database instance is located. + +Refer to [D1 read replication](/d1/best-practices/read-replication/) for more information. \ No newline at end of file diff --git a/src/content/docs/d1/configuration/index.mdx b/src/content/docs/d1/configuration/index.mdx index 35b901b9e04f0a5..bd877291ba15a2b 100644 --- a/src/content/docs/d1/configuration/index.mdx +++ b/src/content/docs/d1/configuration/index.mdx @@ -2,7 +2,7 @@ title: Configuration pcx_content_type: navigation sidebar: - order: 8 + order: 9 group: hideIndex: true --- diff --git a/src/content/docs/d1/d1-api.mdx b/src/content/docs/d1/d1-api.mdx index 25c486c6a10520c..f44f45af0287ea2 100644 --- a/src/content/docs/d1/d1-api.mdx +++ b/src/content/docs/d1/d1-api.mdx @@ -3,5 +3,5 @@ pcx_content_type: navigation title: REST API external_link: /api/resources/d1/subresources/database/methods/create/ sidebar: - order: 6 + order: 7 --- diff --git a/src/content/docs/d1/demos.mdx b/src/content/docs/d1/demos.mdx index eb856d104531d2c..77f1b1974c17ccb 100644 --- a/src/content/docs/d1/demos.mdx +++ b/src/content/docs/d1/demos.mdx @@ -2,7 +2,7 @@ pcx_content_type: navigation title: Demos and architectures sidebar: - order: 12 + order: 13 --- @@ -10,6 +10,18 @@ import { ExternalResources, GlossaryTooltip, ResourcesBySelector } from "~/compo Learn how you can use D1 within your existing application and architecture. +## Featured Demos + +- [Starter code for D1 Sessions API](https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template): An introduction to D1 Sessions API. This demo simulates purchase orders administration. + + [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template) + +:::note[Tip: Place your database further away for the read replication demo] +To simulate how read replication can improve a worst case latency scenario, select your primary database location to be in a farther away region (one of the deployment steps). + +You can find this in the **Database location hint** dropdown. +::: + ## Demos Explore the following demo applications for D1. diff --git a/src/content/docs/d1/examples/index.mdx b/src/content/docs/d1/examples/index.mdx index 8ad40e6d10db80d..991ad6cbf6b5339 100644 --- a/src/content/docs/d1/examples/index.mdx +++ b/src/content/docs/d1/examples/index.mdx @@ -4,7 +4,7 @@ hideChildren: false pcx_content_type: navigation title: Examples sidebar: - order: 10 + order: 11 group: hideIndex: true --- diff --git a/src/content/docs/d1/get-started.mdx b/src/content/docs/d1/get-started.mdx index 6fcf23ccae110fe..399df132a6c8cbd 100644 --- a/src/content/docs/d1/get-started.mdx +++ b/src/content/docs/d1/get-started.mdx @@ -6,16 +6,7 @@ sidebar: order: 2 --- -import { - Render, - PackageManagers, - Steps, - FileTree, - Tabs, - TabItem, - TypeScriptExample, - WranglerConfig -} from "~/components"; +import { Render, PackageManagers, Steps, FileTree, Tabs, TabItem, TypeScriptExample, WranglerConfig } from "~/components"; This guide instructs you through: diff --git a/src/content/docs/d1/observability/index.mdx b/src/content/docs/d1/observability/index.mdx index 35c902c3124a360..e6f2e7cc962fed7 100644 --- a/src/content/docs/d1/observability/index.mdx +++ b/src/content/docs/d1/observability/index.mdx @@ -2,7 +2,7 @@ title: Observability pcx_content_type: navigation sidebar: - order: 9 + order: 10 group: hideIndex: true --- diff --git a/src/content/docs/d1/platform/index.mdx b/src/content/docs/d1/platform/index.mdx index 1720605aee02065..adf92ca4d07c8bd 100644 --- a/src/content/docs/d1/platform/index.mdx +++ b/src/content/docs/d1/platform/index.mdx @@ -2,7 +2,7 @@ pcx_content_type: navigation title: Platform sidebar: - order: 12 + order: 13 group: hideIndex: true --- diff --git a/src/content/docs/d1/platform/limits.mdx b/src/content/docs/d1/platform/limits.mdx index 2074b06ee948285..bcd05ee24e6dcfd 100644 --- a/src/content/docs/d1/platform/limits.mdx +++ b/src/content/docs/d1/platform/limits.mdx @@ -15,7 +15,7 @@ import { Render } from "~/components"; | Maximum storage per account | 250 GB (Workers Paid)[^1] / 5 GB (Free) | | [Time Travel](/d1/reference/time-travel/) duration (point-in-time recovery) | 30 days (Workers Paid) / 7 days (Free) | | Maximum Time Travel restore operations | 10 restores per 10 minute (per database) | -| Queries per Worker invocation (read [subrequest limits](/workers/platform/limits/#how-many-subrequests-can-i-make)) | 50 (Bundled) / 1000 (Unbound) | +| Queries per Worker invocation (read [subrequest limits](/workers/platform/limits/#how-many-subrequests-can-i-make)) | 50 (Free) / 1000 (Paid) | | Maximum number of columns per table | 100 | | Maximum number of rows per table | Unlimited (excluding per-database storage limits) | | Maximum string, `BLOB` or table row size | 2,000,000 bytes (2 MB) | diff --git a/src/content/docs/d1/reference/index.mdx b/src/content/docs/d1/reference/index.mdx index adcefc0bc391465..99f8aa882f71c44 100644 --- a/src/content/docs/d1/reference/index.mdx +++ b/src/content/docs/d1/reference/index.mdx @@ -2,7 +2,7 @@ pcx_content_type: navigation title: Reference sidebar: - order: 13 + order: 14 group: hideIndex: true --- diff --git a/src/content/docs/d1/reference/migrations.mdx b/src/content/docs/d1/reference/migrations.mdx index 363b0df10c0e316..05bdca83f2b7706 100644 --- a/src/content/docs/d1/reference/migrations.mdx +++ b/src/content/docs/d1/reference/migrations.mdx @@ -20,6 +20,12 @@ Currently, the migrations system aims to be simple yet effective. With the curre Every migration file in the `migrations` folder has a specified version number in the filename. Files are listed in sequential order. Every migration file is an SQL file where you can specify queries to be run. +:::note[Binding name vs Database name] +When running a migration script, you can use either the binding name or the database name. + +However, the binding name can change, whereas the database name cannot. Therefore, to avoid accidentally running migrations on the wrong binding, you may wish to use the database name for D1 migrations. +::: + ## Wrangler customizations By default, migrations are created in the `migrations/` folder in your Worker project directory. Creating migrations will keep a record of applied migrations in the `d1_migrations` table found in your database. diff --git a/src/content/docs/d1/reference/time-travel.mdx b/src/content/docs/d1/reference/time-travel.mdx index 15ffadef2353692..bd2f5192bf408c0 100644 --- a/src/content/docs/d1/reference/time-travel.mdx +++ b/src/content/docs/d1/reference/time-travel.mdx @@ -5,6 +5,8 @@ sidebar: order: 2 --- +import { GlossaryTooltip} from "~/components" + Time Travel is D1's approach to backups and point-in-time-recovery, and allows you to restore a database to any minute within the last 30 days. - You do not need to enable Time Travel. It is always on. @@ -23,13 +25,14 @@ To understand which storage subsystem your database uses, run `wrangler d1 info ## Bookmarks -Time Travel introduces the concept of a "bookmark" to D1. A bookmark represents the state of a database at a specific point in time, and is effectively an append-only log. +Time Travel leverages D1's concept of a bookmark to restore to a point in time. -- Bookmarks are lexicographically sortable. Sorting orders a list of bookmarks from oldest-to-newest. - Bookmarks older than 30 days are invalid and cannot be used as a restore point. - Restoring a database to a specific bookmark does not remove or delete older bookmarks. For example, if you restore to a bookmark representing the state of your database 10 minutes ago, and determine that you needed to restore to an earlier point in time, you can still do so. +- Bookmarks are lexicographically sortable. Sorting orders a list of bookmarks from oldest-to-newest. +- Bookmarks can be derived from a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) (seconds since Jan 1st, 1970), and conversion between a specific timestamp and a bookmark is deterministic (stable). -Bookmarks can be derived from a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) (seconds since Jan 1st, 1970), and conversion between a specific timestamp and a bookmark is deterministic (stable). +Bookmarks are also leveraged by [Sessions API](/d1/best-practices/read-replication/#sessions-api-examples) to ensure sequential consistency within a Session. ## Timestamps diff --git a/src/content/docs/d1/sql-api/index.mdx b/src/content/docs/d1/sql-api/index.mdx index 8b74fb521d38d05..3228a42dd6a9bcd 100644 --- a/src/content/docs/d1/sql-api/index.mdx +++ b/src/content/docs/d1/sql-api/index.mdx @@ -2,7 +2,7 @@ title: SQL API pcx_content_type: navigation sidebar: - order: 5 + order: 6 group: hideIndex: true --- diff --git a/src/content/docs/d1/tutorials/index.mdx b/src/content/docs/d1/tutorials/index.mdx index 11a1a82e0f5331e..4ae5ba9499914ed 100644 --- a/src/content/docs/d1/tutorials/index.mdx +++ b/src/content/docs/d1/tutorials/index.mdx @@ -4,7 +4,7 @@ pcx_content_type: navigation title: Tutorials hideChildren: true sidebar: - order: 11 + order: 12 --- diff --git a/src/content/docs/d1/tutorials/using-read-replication-for-e-com/index.mdx b/src/content/docs/d1/tutorials/using-read-replication-for-e-com/index.mdx new file mode 100644 index 000000000000000..bfd4dfdbb50cbba --- /dev/null +++ b/src/content/docs/d1/tutorials/using-read-replication-for-e-com/index.mdx @@ -0,0 +1,800 @@ +--- +updated: 2025-04-09 +difficulty: Beginner +content_type: Tutorial +pcx_content_type: tutorial +title: Using D1 Read Replication for your e-commerce website +products: + - D1 +languages: + - JavaScript + - TypeScript + - SQL +--- + +import { + Render, + Steps, + PackageManagers, + WranglerConfig, + Details, +} from "~/components"; + +[D1 Read Replication](/d1/best-practices/read-replication/) is a feature that allows you to replicate your D1 database to multiple regions. This is useful for your e-commerce website, as it reduces read latencies and improves read throughput. In this tutorial, you will learn how to use D1 read replication for your e-commerce website. + +While this tutorial uses a fictional e-commerce website, the principles can be applied to any use-case that requires low read latencies and scaling reads, such as a news website, a social media platform, or a marketing website. + +## Quick start + +If you want to skip the steps and get started quickly, click on the below button: + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/harshil1712/e-com-d1-hono) + +This will create a repository in your GitHub account and deploy the application to Cloudflare Workers. It will also create and bind a D1 database, create the required tables, add some sample data. During deployment, tick the `Enable read replication` box to activate read replication. + +You can then visit the deployed application. + +## Prerequisites + + + +## Step 1: Create a Workers project + +Create a new Workers project by running the following command: + + + + + +For creating the API routes, you will use [Hono](https://hono.dev/). You need to install Hono by running the following command: + +```sh +npm install hono +``` + +## Step 2: Update the frontend + +The above step creates a new Workers project with a default frontend and installs Hono. You will update the frontend to list the products. You will also add a new page to the frontend to display a single product. + +Navigate to the newly created Worker project folder. + +```sh +cd fast-commerce +``` + + +Update the `public/index.html` file to list the products. Use the below code as a reference. + +
+```html + + + + + + E-commerce Store + + + +
+

E-commerce Store

+ +
+ +
+ +
+ +
+

© 2025 E-commerce Store. All rights reserved.

+
+ + + + +``` +
+ +Create a new `public/product-details.html` file to display a single product. + +
+```html + + + + + + Product Details - E-commerce Store + + + +
+ E-commerce Store + +
+ +
+ ← Back to products +

Product Name

+

Product description goes here.

+ +
+

$0.00

+

0 in stock

+
+ + +
+ +
Added to cart!
+ +
+

© 2025 E-commerce Store. All rights reserved.

+
+ + + + + +``` +
+ +You now have a frontend that lists products and displays a single product. However, the frontend is not yet connected to the D1 database. If you start the development server now, you will see no products. In the next steps, you will create a D1 database and create APIs to fetch products and display them on the frontend. + +## Step 3: Create a D1 database and enable read replication + +Create a new D1 database by running the following command: + +```sh +npx wrangler d1 create fast-commerce +``` + +Add the D1 bindings returned in the terminal to the `wrangler` file: + + +```toml +[[d1_databases]] +binding = "DB" +database_name = "fast-commerce" +database_id = "YOUR_DATABASE_ID" +``` + + +Run the following command to update the `Env` interface in the `worker-congifuration.d.ts` file. + +```sh +npm run cf-typegen +``` + +Next, enable read replication for the D1 database. Navigate to [**Workers & Pages** > **D1**](https://dash.cloudflare.com/?to=/:account/workers/d1), then select an existing database > **Settings** > **Enable Read Replication**. + +## Step 4: Create the API routes + +Update the `src/index.ts` file to import the Hono library and create the API routes. + +```ts +import { Hono } from 'hono'; +// Set db session bookmark in the cookie +import { getCookie, setCookie } from 'hono/cookie'; + +const app = new Hono<{ Bindings: Env }>(); + +// Get all products +app.get('/api/products', async (c) => { + return c.json({ message: 'get list of products' }); +}); + +// Get a single product +app.get('/api/products/:id', async (c) => { + return c.json({ message: 'get a single product' }); +}); + +// Upsert a product +app.post('/api/product', async (c) => { + return c.json({ message: 'create or update a product' }); +}); + +export default app; +``` + +The above code creates three API routes: + +- `GET /api/products`: Returns a list of products. +- `GET /api/products/:id`: Returns a single product. +- `POST /api/product`: Creates or updates a product. + +However, the API routes are not connected to the D1 database yet. In the next steps, you will create a products table in the D1 database, and update the API routes to connect to the D1 database. + +## Step 5: Create local D1 database schema + +Create a products table in the D1 database by running the following command: + +```sh +npx wrangler d1 execute fast-commerce --command "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, inventory INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)" +``` + +Next, create an index on the products table by running the following command: + +```sh +npx wrangler d1 execute fast-commerce --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)" +``` + +For development purposes, you can also execute the insert statements on the local D1 database by running the following command: + +```sh +npx wrangler d1 execute fast-commerce --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')" +``` + + + +## Step 6: Update the API routes + +Update the API routes to connect to the D1 database. + + +### 1. POST /api/product + +```ts +app.post('/api/product', async (c) => { + const product = await c.req.json(); + + if (!product) { + return c.json({ message: 'No data passed' }, 400); + } + + const db = c.env.DB; + const session = db.withSession('first-primary'); + + const { id } = product; + + try { + // Check if the product exists + const { results } = await session.prepare('SELECT * FROM products where id = ?').bind(id).run(); + if (results.length === 0) { + const fields = [...Object.keys(product)]; + const values = [...Object.values(product)]; + // Insert the product + await session + .prepare(`INSERT INTO products (${fields.join(', ')}) VALUES (${fields.map(() => '?').join(', ')})`) + .bind(...values) + .run(); + const latestBookmark = session.getBookmark(); + latestBookmark && + setCookie(c, 'product_bookmark', latestBookmark, { + maxAge: 60 * 60, // 1 hour + }); + return c.json({ message: 'Product inserted' }); + } + + // Update the product + const updates = Object.entries(product) + .filter(([_, value]) => value !== undefined) + .map(([key, _]) => `${key} = ?`) + .join(', '); + + if (!updates) { + throw new Error('No valid fields to update'); + } + + const values = Object.entries(product) + .filter(([_, value]) => value !== undefined) + .map(([_, value]) => value); + + await session + .prepare(`UPDATE products SET ${updates} WHERE id = ?`) + .bind(...[...values, id]) + .run(); + const latestBookmark = session.getBookmark(); + latestBookmark && + setCookie(c, 'product_bookmark', latestBookmark, { + maxAge: 60 * 60, // 1 hour + }); + return c.json({ message: 'Product updated' }); + } catch (e) { + console.error(e); + return c.json({ message: 'Error upserting product' }, 500); + } +}); + +``` + +In the above code: + +- You get the product data from the request body. +- You then check if the product exists in the database. + - If it does, you update the product. + - If it doesn't, you insert the product. +- You then set the bookmark in the cookie. +- Finally, you return the response. + +Since you want to start the session with the latest data, you use the `first-primary` constraint. Even if you use the `first-unconstrained` constraint or pass a bookmark, the write request will always be routed to the primary database. + +The bookmark set in the cookie can be used to guarantee that a new session reads a database version that is at least as up-to-date as the provided bookmark. + +If you are using an external platform to manage your products, you can connect this API to the external platform, such that, when a product is created or updated in the external platform, the D1 database automatically updates the product details. + +### 2. GET /api/products + +```ts +app.get('/api/products', async (c) => { + const db = c.env.DB; + + // Get bookmark from the cookie + const bookmark = getCookie(c, 'product_bookmark') || 'first-unconstrained'; + + const session = db.withSession(bookmark); + + try { + const { results } = await session.prepare('SELECT * FROM products').all(); + + const latestBookmark = session.getBookmark(); + + // Set the bookmark in the cookie + latestBookmark && + setCookie(c, 'product_bookmark', latestBookmark, { + maxAge: 60 * 60, // 1 hour + }); + + return c.json(results); + } catch (e) { + console.error(e); + return c.json([]); + } +}); + +``` + +In the above code: + +- You get the database session bookmark from the cookie. + - If the bookmark is not set, you use the `first-unconstrained` constraint. +- You then create a database session with the bookmark. +- You fetch all the products from the database and get the latest bookmark. +- You then set this bookmark in the cookie. +- Finally, you return the results. + +### 3. GET /api/products/:id + +```ts +app.get('/api/products/:id', async (c) => { + const id = c.req.param('id'); + + if (!id) { + return c.json({ message: 'Invalid id' }, 400); + } + + const db = c.env.DB; + + // Get bookmark from the cookie + const bookmark = getCookie(c, 'product_bookmark') || 'first-unconstrained'; + + const session = db.withSession(bookmark); + + try { + const { results } = await session.prepare('SELECT * FROM products where id = ?').bind(id).run(); + + const latestBookmark = session.getBookmark(); + + // Set the bookmark in the cookie + latestBookmark && + setCookie(c, 'product_bookmark', latestBookmark, { + maxAge: 60 * 60, // 1 hour + }); + + console.log(results); + + return c.json(results); + } catch (e) { + console.error(e); + return c.json([]); + } +}); + +``` + +In the above code: +- You get the product ID from the request parameters. +- You then create a database session with the bookmark. +- You fetch the product from the database and get the latest bookmark. +- You then set this bookmark in the cookie. +- Finally, you return the results. + + +## Step 7: Test the application + +You have now updated the API routes to connect to the D1 database. You can now test the application by starting the development server and navigating to the frontend. + +```sh +npm run dev +``` + +Navigate to `http://localhost:8787. You should see the products listed. Click on a product to view the product details. + +To insert a new product, use the following command (while the development server is running): + +```sh +curl -X POST http://localhost:8787/api/product \ + -H "Content-Type: application/json" \ + -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1000.00, "inventory": 10, "category": "Electronics"}' +``` + +Navigate to `http://localhost:8787/product-details?id=6`. You should see the new product. + +Update the product using the following command, and navigate to `http://localhost:8787/product-details?id=6` again. You will see the updated product. + +```sh +curl -X POST http://localhost:8787/api/product \ + -H "Content-Type: application/json" \ + -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1050.00, "inventory": 10, "category": "Electronics"}' +``` + +:::note +Read replication is only used when the application has been [deployed](/d1/tutorials/using-read-replication-for-e-com/#step-8-deploy-the-application). D1 does not create read replicas when you develop locally. To test it locally, you can start the development server with the `--remote` flag. +::: + +## Step 8: Deploy the application + +Since the database you used in the previous steps is local, you need to create the products table in the remote database. Execute the following D1 commands to create the products table in the remote database. + +```sh +npx wrangler d1 execute fast-commerce --remote --command "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, inventory INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)" +``` + +Next, create an index on the products table by running the following command: + +```sh +npx wrangler d1 execute fast-commerce --remote --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)" +``` + +Optionally, you can insert the products into the remote database by running the following command: + +```sh +npx wrangler d1 execute fast-commerce --remote --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')" +``` + +Now, you can deploy the application with the following command: + +```sh +npm run deploy +``` + +This will deploy the application to Workers and the D1 database will be replicated to the remote regions. If a user requests the application from any region, the request will be redirected to the nearest region where the database is replicated. + +## Conclusion + +In this tutorial, you learned how to use D1 Read Replication for your e-commerce website. You created a D1 database and enabled read replication for it. You then created an API to create and update products in the database. You also learned how to use the bookmark to get the latest data from the database. + +You then created the products table in the remote database and deployed the application. + +You can use the same approach for your existing read heavy application to reduce read latencies and improve read throughput. If you are using an external platform to manage the content, you can connect the external platform to the D1 database, so that the content is automatically updated in the database. + +You can find the complete code for this tutorial in the [GitHub repository](https://github.com/harshil1712/e-com-d1-hono). + + diff --git a/src/content/docs/d1/worker-api/d1-database.mdx b/src/content/docs/d1/worker-api/d1-database.mdx index a870a5c2f94308b..0d6b23d65c5421b 100644 --- a/src/content/docs/d1/worker-api/d1-database.mdx +++ b/src/content/docs/d1/worker-api/d1-database.mdx @@ -241,4 +241,61 @@ return new Response(dump, { #### Return values -- None. \ No newline at end of file +- None. + +### `withSession()` + +Starts a D1 session which maintains sequential consistency among queries executed on the returned `D1DatabaseSession` object. + +```ts +const session = env.DB.withSession(""); +``` + +#### Parameters + +- first-primary: + - Directs the first query in the Session (whether read or write) to the primary database instance. Use this option if you need to start the Session with the most up-to-date data from the primary database instance. + - Subsequent queries in the Session may use read replicas. + - Subsequent queries in the Session have sequential consistency. + +- first-unconstrained: + - Directs the first query in the Session (whether read or write) to any database instance. Use this option if you do not need to start the Session with the most up-to-date data, and wish to prioritize minimizing query latency from the very start of the Session. + - Subsequent queries in the Session have sequential consistency. + - This is the default behavior when no parameter is provided. + +- bookmark: + - A [`bookmark`](/d1/reference/time-travel/#bookmarks) from a previous D1 Session. This allows you to start a new Session from at least the provided `bookmark`. + - Subsequent queries in the Session have sequential consistency. + +#### Return values + +- D1DatabaseSession: + - An object which contains the methods [`prepare()`](/d1/worker-api/d1-database#prepare) and [`batch()`](/d1/worker-api/d1-database#batch) similar to `D1Database`, along with the additional [`getBookmark`](/d1/worker-api/d1-database#getbookmark) method. + +#### Guidance + +You can return the last encountered `bookmark` for a given Session using [`session.getBookmark()`](/d1/worker-api/d1-database/#getbookmark). + +## `D1DatabaseSession` methods + +### `getBookmark` + +Retrieves the latest `bookmark` from the D1 Session. + +```ts +const session = env.DB.withSession("first-primary"); +const result = await session + .prepare(`SELECT * FROM Customers WHERE CompanyName = 'Bs Beverages'`) + .run() +return { bookmark } = session.getBookmark(); +``` + +#### Parameters + +- None + +#### Return values + +- bookmark: + - A [`bookmark`](/d1/reference/time-travel/#bookmarks) which identifies the latest version of the database seen by the last query executed within the Session. + - Returns `null` if no query is executed within a Session. \ No newline at end of file diff --git a/src/content/docs/d1/worker-api/index.mdx b/src/content/docs/d1/worker-api/index.mdx index 376ae96925e5901..343460d33f248e8 100644 --- a/src/content/docs/d1/worker-api/index.mdx +++ b/src/content/docs/d1/worker-api/index.mdx @@ -2,7 +2,7 @@ pcx_content_type: navigation title: Workers Binding API sidebar: - order: 4 + order: 5 --- import { DirectoryListing, Details, Steps } from "~/components"; @@ -81,16 +81,17 @@ Replace the contents of your `index.js` file with the code below to view the eff export default { async fetch(request, env) { const { pathname } = new URL(request.url); - // if (pathname === "/api/beverages") { // // If you did not use `DB` as your binding name, change it here // const { results } = await env.DB.prepare("SELECT * FROM Customers WHERE CompanyName = ?",).bind("Bs Beverages").all(); // return Response.json(results); // } - const companyName1 = `Bs Beverages`; const companyName2 = `Around the Horn`; const stmt = env.DB.prepare(`SELECT * FROM Customers WHERE CompanyName = ?`); + const stmtMulti = env.DB.prepare(`SELECT * FROM Customers; SELECT * FROM Customers WHERE CompanyName = ?`); + const session = env.DB.withSession("first-primary") + const sessionStmt = session.prepare(`SELECT * FROM Customers WHERE CompanyName = ?`); if (pathname === `/RUN`){ const returnValue = await stmt.bind(companyName1).run(); @@ -114,6 +115,11 @@ export default { } else if (pathname === `/EXEC`){ const returnValue = await env.DB.exec(`SELECT * FROM Customers WHERE CompanyName = "Bs Beverages"`); return Response.json(returnValue); + + } else if (pathname === `/WITHSESSION`){ + const returnValue = await sessionStmt.bind(companyName1).run(); + console.log("You're now using D1 Sessions!") + return Response.json(returnValue); } return new Response( @@ -121,8 +127,7 @@ export default { \nChange the URL to test the various methods inside your index.js file.`, ); }, -}; - + }; ``` @@ -130,26 +135,22 @@ export default { 1. Navigate to your tutorial directory you created by following step 1. -2. Run `npx wrangler dev`. +2. Run `npx wrangler deploy`. ```sh - npx wrangler dev + npx wrangler deploy ``` ```sh output - ⛅️ wrangler 3.85.0 (update available 3.86.1) - ------------------------------------------------------- + ⛅️ wrangler 3.112.0 + -------------------- + Total Upload: 1.90 KiB / gzip: 0.59 KiB Your worker has access to the following bindings: - D1 Databases: - - DB: (DATABASE_ID) (local) - ⎔ Starting local server... - [wrangler:inf] Ready on http://localhost:8787 - ╭───────────────────────────╮ - │ [b] open a browser │ - │ [d] open devtools │ - │ [l] turn off local mode │ - │ [c] clear console │ - │ [x] to exit │ - ╰───────────────────────────╯ + - DB: DATABASE_NAME () + Uploaded WORKER_NAME (7.01 sec) + Deployed WORKER_NAME triggers (1.25 sec) + https://jun-d1-rr.d1-sandbox.workers.dev + Current Version ID: VERSION_ID ``` 3. Open a browser at the specified address. diff --git a/src/content/glossary/d1.yaml b/src/content/glossary/d1.yaml index cffb5e0863db0f4..b876c11b95e2ff7 100644 --- a/src/content/glossary/d1.yaml +++ b/src/content/glossary/d1.yaml @@ -1,6 +1,22 @@ --- productName: D1 entries: + - term: primary database instance + general_definition: |- + the primary database instance is the original instance of a database. This database instance only exists in one location in the world. + - term: read replica + general_definition: |- + a read replica is an eventually-replicated copy of the primary database instance which only serve read requests. There may be multiple read replicas for a single primary database instance. + - term: replica lag + general_definition: |- + the time it takes for the primary database instance to replicate its changes to a specific read replica. - term: "query planner" general_definition: |- - A component in a database management system which takes a user query and generates the most efficient plan of executing that query (the query plan). For example, the query planner decides which indices to use, or which table to access first. + a component in a database management system which takes a user query and generates the most efficient plan of executing that query (the query plan). For example, the query planner decides which indices to use, or which table to access first. + - term: session + general_definition: |- + 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. + - term: bookmark + general_definition: |- + a bookmark represents the state of a database at a specific point in time. + - Bookmarks are lexicographically sortable. Sorting orders a list of bookmarks from oldest-to-newest. \ No newline at end of file