D1 の読み取りレプリケーション(read replication)は、読み取り専用のデータベースコピー(リードレプリカ)をクライアントに近い世界各地のリージョンに追加します。読み取りクエリのレイテンシを下げ、読み取りスループットをスケールできます。
読み取りレプリケーションを使うには、D1 Sessions API が必要です。使わないと、クエリはすべてプライマリデータベースだけで実行されます。
セッションは、アプリケーション上の 1 つの論理セッションからのクエリをまとめます。たとえば、特定の Web ブラウザーセッションから来るクエリ全体が、1 つのセッションに対応します。セッション内のクエリは、そのクエリが必要とする鮮度のデータベースインスタンスから読み取ります。Sessions API は、セッション内のすべてのクエリに 逐次一貫性 を保証します。
D1 の読み取りレプリケーションを試すには、次の Worker コードを Sessions API でデプロイします。D1 データベースの作成と、そのデータベースでの読み取りレプリケーションの有効化を求められます。
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>;
読み取りレプリケーションなしで D1 を使うと、D1 は読み取りと書き込みのすべてのクエリを、世界の 1 か所 にある特定のデータベースインスタンスへ送ります。これが プライマリデータベースインスタンス です。D1 のリクエストレイテンシは、ユーザーとプライマリデータベースインスタンスの物理的な距離に依存します。プライマリから遠いユーザーは、ネットワークの往復時間 ↗ のため、リクエストレイテンシが長くなります。
読み取りレプリケーションを使うと、D1 はプライマリデータベースインスタンスの非同期複製を複数作成します。読み取りリクエストだけを処理するコピーを リードレプリカ と呼びます。D1 はリードレプリカを、Cloudflare のネットワーク上の 複数リージョン に作成します。
ユーザーがプライマリデータベースインスタンスから遠くても、リードレプリカの近くにいることがあります。D1 が読み取りリクエストをプライマリではなくリードレプリカへ送ると、読み取りクエリの応答が速くなります。
D1 は、プライマリデータベースインスタンスからの変更を、すべてのリードレプリカへ非同期に複製します。そのため、ある時点ではリードレプリカが任意に古くなっていることがあります。プライマリでコミットされた最新データがリードレプリカに複製されるまでの時間を レプリカラグ と呼びます。レプリカラグと、個々のレプリカへの非決定的なルーティングは、アプリケーションのデータ一貫性の問題につながることがあります。 D1 Sessions API は、逐次一貫性を保証することでこの問題を解決します。 詳細は レプリカラグと一貫性モデル を参照してください。
| データベースインスタンスの種類 | 説明 | 書き込みクエリの扱い | 読み取りクエリの扱い |
|---|---|---|---|
| プライマリデータベースインスタンス | データベースの「原本」を持つインスタンス | 書き込みクエリを処理できる | 読み取りクエリを処理できる |
| リードレプリカのデータベースインスタンス | 原本のコピーを持ち、プライマリデータベースインスタンスから非同期に更新を受け取るインスタンス | 書き込みクエリはプライマリデータベースインスタンスへ転送する | 自身のコピーを使って読み取りクエリを処理できる |
世界各地に複数のリードレプリカがあるシステムは、データベースの性能を向上します。
- リードレプリカの近くにいるユーザーでは、クエリレイテンシが下がります。データベースインスタンスとユーザーの物理距離を短くすると、読み取りクエリのレイテンシが下がり、アプリケーションが速くなります。
- 負荷を複数レプリカに分散すると、読み取りスループットが上がります。複数のデータベースインスタンスが読み取り専用リクエストを処理できるため、アプリケーションは同時により多くのクエリを処理できます。
読み取りレプリケーションに Sessions API を使うと、1 つの セッション からのクエリは、逐次一貫性を保証するバージョンのデータベースから読み取ります。クエリを別々のリードレプリカが処理しても、読んでいるデータベースのバージョンは論理的に一貫します。
D1 の読み取りレプリケーションは、セッション内の各クエリに ブックマーク を付けることでこれを実現します。詳細は Bookmarks を参照してください。
読み取りレプリケーションは、Cloudflare ダッシュボードでデータベース単位に有効にできます。対象の D1 データベースの Settings で、有効かどうかを確認します。
-
Cloudflare ダッシュボードで、D1 ページを開きます。
D1 SQL database を開く ↗ -
既存のデータベースを選択し、Settings > Enable Read Replication を選びます。
利用可能な任意のデータベースバージョンからセッションを作るには、パラメーターなしの withSession() を使います。最初のクエリは、プライマリデータベースインスタンスまたはリードレプリカのいずれかにルーティングされます。
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()はwithSession("first-unconstrained")と同じです。- 最新のデータベースバージョンが不要なアプリケーションに向きます。セッション内のクエリはすべて逐次一貫性を保証します。
- D1 Workers Binding API のドキュメント を参照してください。
最新のデータベースバージョンからセッションを作るには、withSession("first-primary") を使います。最初のクエリはプライマリデータベースインスタンスへルーティングされます。
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()- 最新のデータベースバージョンが必要なアプリケーションに向きます。セッション内のクエリはすべて逐次一貫性を保証します。
- D1 Workers Binding API のドキュメント を参照してください。
以前のセッションのコンテキストから新しいセッションを作るには、bookmark パラメーターを渡します。指定した bookmark と同等以上に新しいデータベースバージョンでセッションが始まります。
// 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() ?? "")bookmark付きでセッションを開始すると、新しいセッションは、そのbookmarkを生成した以前のセッションと同等以上に新しくなります。- D1 Workers Binding API のドキュメント を参照してください。
リードレプリカの追加によって D1 リクエストがどう処理されるかを見るには、D1 Result の meta オブジェクトに返る served_by_region と served_by_primary フィールドを使います。
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とserved_by_primaryは、読み取りレプリケーションの有無や Sessions API の使用に関係なく、すべての D1 リモートリクエストに含まれます。ローカル開発のnpx wrangler devでは、これらのフィールドはundefinedです。
REST API では、read_replication.mode: auto を設定すると、D1 データベースで読み取りレプリケーションが有効になります。
この REST エンドポイントには、D1:Edit 権限の API トークンが必要です。API トークンがない場合は、API トークンを作成する を参照してください。
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"}}'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" } }
)
}
)REST API では、read_replication.mode: disabled を設定すると、D1 データベースの読み取りレプリケーションが無効になります。
この REST エンドポイントには、D1:Edit 権限の API トークンが必要です。API トークンがない場合は、API トークンを作成する を参照してください。
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"}}'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" } }
)
}
)Cloudflare ダッシュボードでは、対象の D1 データベースの Settings で、読み取りレプリケーションが有効かを確認します。
または、D1 データベースの GET REST エンドポイントが、有効か無効かを返します。
この REST エンドポイントには、D1:Read 権限の API トークンが必要です。API トークンがない場合は、API トークンを作成する を参照してください。
curl -X GET "https://api.cloudflare.com/client/v4/accounts/{account_id}/d1/database/{database_id}" \
-H "Authorization: Bearer $TOKEN"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);resultオブジェクトのread_replicationプロパティを確認します"mode": "auto"は読み取りレプリケーションが有効であることを示します"mode": "disabled"は読み取りレプリケーションが無効であることを示します
現在、D1 は 対応するすべてのリージョン にリードレプリカを自動作成します。プライマリデータベースインスタンスがあるリージョンも含みます。対象リージョンは次のとおりです。
- ENAM
- WNAM
- WEUR
- EEUR
- APAC
- OC
読み取りレプリケーションの効果を確認し、追加のデータベースインスタンスが D1 リクエストをどう処理するかを見るには、次を使えます。
D1Resultの戻り値オブジェクト内のmetaオブジェクト。次のフィールドが追加されています。served_by_regionserved_by_primary
- Cloudflare ダッシュボード。D1 リクエストを処理したリージョンごとのデータベースメトリクス内訳を確認できます。
D1 の読み取りレプリケーションは D1 に組み込まれています。リードレプリカの追加ストレージやコンピュート料金はありません。レプリカの有無に関係なく、クエリの rows_read と rows_written に基づく同じ D1 の 使用量課金 です。
D1 の読み取りレプリケーションには、いくつかの既知の制限があります。
- Sessions API は D1 Worker Binding 経由でのみ利用できます。REST API ではまだ利用できません。
レプリカラグ を踏まえるには、D1 の一貫性モデルを考えることが重要です。一貫性モデルは、複数のデータベースインスタンスがあるときに、データベースシステムがユーザークエリをどう処理するか(データの更新とアクセスの仕方)を定める論理的な枠組みです。用途によって、適するモデルは異なります。多くのデータベースシステムは、設定に応じて read committed ↗、snapshot isolation ↗、serializable ↗ の一貫性モデルを提供します。
一貫性モデルを明示的に強制する枠組みがない分散データベースで、何が起き得るかを考えます。
- SQL の書き込みクエリは、プライマリデータベースインスタンスが処理します。
- 書き込みクエリを受け付けた応答を受け取ります。
- 続く SQL の読み取りクエリは、リードレプリカへ行きます。
- リードレプリカはまだ更新されておらず、書き込みクエリの変更を含みません。返す結果は、自分から見て一貫していません。
D1 Sessions API を使うと、クエリはブックマークを得ます。リードレプリカは、逐次一貫したデータだけを返せます。
- SQL の書き込みクエリは、プライマリデータベースインスタンスが処理します。
- 書き込みクエリを受け付けた応答を受け取ります。あわせて、書き込み後のデータベース状態を示すブックマーク(100)も得ます。
- 続く SQL の読み取りクエリはリードレプリカへ行き、ブックマーク(100)も渡します。
- リードレプリカは、指定したブックマーク(100)と同等以上に新しくなるまで待ちます。
- リードレプリカが更新されると(ブックマーク 104)、読み取りクエリを処理します。この時点で逐次一貫しています。
図では、返るブックマークは 104 で、読み取りクエリに渡したブックマーク(100)とは異なります。実行した 2 つの書き込み / 読み取りの間に、他のクライアントからの書き込みがリードレプリカへ複製された場合に、こうなります。
D1 の読み取りレプリケーションは 逐次一貫性 ↗ を提供します。D1 はデータベース上で行われたすべての操作にグローバルな順序を付け、クエリが見た最新バージョンを ブックマーク で識別できます。そのうえで、クエリに付いたブックマークと同等以上に新しいデータベースインスタンスでクエリを実行します。
逐次一貫性には、次のような性質があります。
- 単調読み取り(Monotonic reads): 続けて 2 回読む(read-1 のあと read-2)とき、read-2 は read-1 より古いバージョンを読めません。
- 単調書き込み(Monotonic writes): write-1 のあと write-2 を行うと、すべてのプロセスは write-1 を write-2 より先に観測します。
- 読み取り後の書き込み(Writes follow reads): 値を読んだあと書き込む場合、その書き込みはその直前に読んだ値に基づく必要があります。
- 自分の書き込みの読み取り(Read my own writes): データベースに書き込むと、その後の読み取りはすべてその書き込みを見ます。
次の資料も参照できます。