Nullstead

Cloudflare D1

Cloudflare D1のSession APIとブックマークで、AI自動化を「たまにズレる」から卒業する実践ガイド

Cloudflare D1のグローバル読み取りレプリケーション時代に、Session APIとブックマークを使って整合性のズレを防ぐ実装パターンを解説。WorkersやAIエージェント自動化でハマりやすい罠と対策を、コード付きでまとめます。

約10分

最近、AIエージェントや自動化フローを作っていると「動いてはいる。でもたまに変な結果になる」という、いちばん説明しづらい不具合に遭遇しがちです。ログを見ると成功。監視も緑。なのに人間だけが「いや、これ昨日のデータ読んでない?」と気づくやつです。

今回はその犯人になりやすい「分散環境での読み取り整合性」を、Cloudflare D1のSession APIとブックマーク機能でどう扱うかをまとめます。テーマは少し地味ですが、AI自動化の品質を上げるにはかなり効きます。派手なモデル更新より、こういう地味インフラの方が夜よく眠れます。私はこっち派です。

なぜ今、D1の整合性を真面目に考えるべきなのか

Cloudflare D1は、2025年にグローバル読み取りレプリケーションの提供を進め、世界中のユーザーへ低遅延で読み取りを返せる方向に進化しました。これは体感速度の面で非常にありがたいです。

一方で、AI自動化の現場では次のような処理が増えています。

  • ユーザー操作を受けてWorkerが書き込み
  • すぐ後続のAI処理が同じデータを読み取り
  • 条件分岐して通知や外部API実行

このとき、読み取りが書き込み直後の最新状態と論理的につながっていないと、

  • まだ存在しないはずの「未更新データ」を見て分岐を誤る
  • 二重通知・二重課金・重複ジョブ発行
  • たまにだけ失敗するため、再現が難しい

という“再現性の低い地獄”が起きます。障害報告の文章量だけが増えていく、あの地獄です。

Session APIとブックマークの基本を30秒で

まず前提を短く整理します。

  • D1のSession APIを使うと、セッション内のクエリは順序付きで扱われる
  • セッションは「最後に見えた状態」をブックマークとして保持する
  • 次のセッション開始時にブックマークを渡すと、前回までの状態を前提に読み取りを始められる

※Session API: D1のWorkersバインディングで使える、一貫性を保つための実行コンテキスト ※ブックマーク: 「この時点までの更新は見えている」という境界を表すトークン ※順序整合性(sequential consistency): 自分が観測した順番が、後続の観測でも崩れない性質

公式ドキュメントでも、セッションを使わない単発クエリと、セッション経由クエリでは整合性の保証が異なる点が明示されています。ここを理解せずに「とりあえずSELECT」で進むと、運が悪い日だけバグります。

ありがちな失敗パターン: 「書いた直後に別経路で読む」

私がよく見るのは次の構成です。

  1. API A で UPDATE tasks SET status = 'ready'
  2. 直後に API B(別リクエスト)で SELECT status FROM tasks
  3. B の結果でAIジョブ起動可否を判定

このときAとBでセッション連携がないと、Bが古い状態を観測する可能性があります。特にグローバルユーザー向けでリージョンをまたぐと、低確率だけど無視できないズレが出ます。

「低確率なら大丈夫では?」と思いがちですが、ジョブ実行回数が1日10万を超えると、低確率は普通に事故件数へ変換されます。確率論はだいたい冷酷です。

実装パターン1: 1リクエスト内は必ず同一Sessionで閉じる

最初の鉄則はシンプルです。1つのHTTPリクエスト内で行う複数クエリは、同じセッションに寄せます。

// 動作方針サンプル(環境に合わせて調整してください)
export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    const session = env.DB.withSession();

    // 1) 書き込み
    await session
      .prepare("UPDATE tasks SET status = ?1, updated_at = ?2 WHERE id = ?3")
      .bind("ready", new Date().toISOString(), "task_123")
      .run();

    // 2) 同じSessionで読み取り
    const row = await session
      .prepare("SELECT status, updated_at FROM tasks WHERE id = ?1")
      .bind("task_123")
      .first<{ status: string; updated_at: string }>();

    return Response.json({ ok: true, row });
  },
};

この形なら、書き込みと読み取りの観測順序を同一セッションに固定できます。コードレビューでも意図が明確なので、将来の自分が助かります。未来の自分は、だいたい他人より厳しいレビュアーです。

実装パターン2: リクエストをまたぐならブックマークを受け渡す

問題はここからです。実際の自動化では、処理が複数リクエストや複数サービスに分かれます。

  • APIで書き込み
  • Queue/Workflowへイベント投入
  • 別Workerが読み取り+推論+通知

この場合、前段で得たブックマークを後段へ渡し、後段セッション開始時に利用します。

// 書き込み側: ブックマークをイベントに乗せるイメージ
const session = env.DB.withSession();
await session.prepare("INSERT INTO jobs (id, state) VALUES (?1, ?2)").bind(jobId, "queued").run();

const bookmark = session.getBookmark?.(); // 実際のAPI名は利用SDK版で確認
await env.JOBS_QUEUE.send({ jobId, bookmark });
// 読み取り側: 受け取ったブックマークからSession開始
const { jobId, bookmark } = messageBody;
const session = env.DB.withSession(bookmark ?? undefined);

const job = await session
  .prepare("SELECT id, state FROM jobs WHERE id = ?1")
  .bind(jobId)
  .first();

※上記は設計理解のためのサンプルです。実際のメソッド名や型は、使用中のWorkers/D1 APIバージョンに合わせて確認してください。

ポイントは「データそのもの」だけでなく「どこまで見えているか」の境界情報も一緒に運ぶことです。イベント駆動ではここを落としやすいです。

AI自動化で効く設計: idempotencyと観測境界をセットで持つ

整合性対策はSessionだけで終わりません。AI連携だと再試行が頻繁に発生するため、次も合わせて入れると安定します。

  1. idempotency key(冪等キー)を保存
  2. bookmark を処理文脈に同梱
  3. 実行前に「同じキーで完了済みか」を確認
  4. 未完了なら処理、完了ならスキップ
CREATE TABLE IF NOT EXISTS automation_runs (
  idempotency_key TEXT PRIMARY KEY,
  job_id TEXT NOT NULL,
  status TEXT NOT NULL,
  created_at TEXT NOT NULL
);
// 疑似コード: 二重実行防止
const existing = await session
  .prepare("SELECT status FROM automation_runs WHERE idempotency_key = ?1")
  .bind(key)
  .first();

if (existing?.status === "done") {
  return { skipped: true };
}

※冪等性(idempotency): 同じ要求を何度実行しても最終結果が変わらない性質

「順序が正しいか」と「重複実行しないか」は別問題です。片方だけ守ると、もう片方から事故ります。セキュリティでいう“多層防御”に近い感覚で考えるとわかりやすいです。

Workflows・Queues・外部LLMとつなぐときの実践チェックリスト

Cloudflare WorkflowsがGAになってから、長時間処理の設計はかなり楽になりました。とはいえ、D1整合性を意識しないと、ワークフロー自体が丁寧に不整合を運ぶ装置になります。怖いですね。

次のチェックリストを先に入れておくと、後で楽です。

  • 書き込み→読み取りの境界でSessionを明示する
  • イベントペイロードにbookmarkを入れる
  • 再試行可能な処理は冪等キー必須
  • 監視項目に「重複実行数」「古い状態観測数」を追加
  • LLM呼び出し前後でDB状態を再確認する

特に最後の「LLM呼び出し前後の再確認」は効きます。推論に数秒〜数十秒かかる間に、業務状態が進むケースは珍しくありません。モデルは賢いですが、時間停止能力は持っていません(いまのところ)。

どこまでやれば十分か: 小規模個人開発の現実解

「全部やるのは重いです」という声もその通りです。私も最初は最小構成で始めます。おすすめは段階導入です。

  • Phase 1: 1リクエスト内Session統一
  • Phase 2: Queue越し処理にbookmark受け渡し
  • Phase 3: 冪等キー+重複監視
  • Phase 4: 主要ユースケースだけ整合性E2Eテスト

この順番なら、工数を抑えつつ事故率を下げられます。最初から完璧を狙うより、「事故りやすい導線を先に潰す」方が個人開発では勝率が高いです。

なお、D1が向くワークロードと、Hyperdrive経由で既存RDBMSを使うべきケースは分けて考えるのが安全です。Cloudflare公式のストレージ選定ガイドも、この住み分けを明確に示しています。

まとめ: AI時代の自動化品質は「モデル性能 + データ一貫性」で決まる

AI機能を載せると、どうしてもモデルやプロンプトの話が中心になります。もちろん重要です。ただ、プロダクション運用で効いてくるのは「モデルに渡す前のデータが、ちゃんと正しいか」です。

Cloudflare D1のSession APIとブックマークを使う設計は、見た目こそ地味ですが、

  • 調査不能な“たまにバグる”を減らす
  • 再試行時の重複事故を抑える
  • 自動化パイプラインの説明可能性を上げる

という意味で、AI開発の基礎体力を底上げしてくれます。

派手なデモは作れないかもしれませんが、夜中の障害対応回数は確実に減ります。これは体感で断言できます。睡眠は、最高の開発者体験です。


参考リンク

  1. Cloudflare Blog: Sequential consistency without borders: how D1 implements global read replication
    https://blog.cloudflare.com/d1-read-replication-beta/
  2. Cloudflare Docs: Global read replication (D1)
    https://developers.cloudflare.com/d1/best-practices/read-replication/
  3. Cloudflare Docs: D1 Database API (Sessions/Bookmark)
    https://developers.cloudflare.com/d1/worker-api/d1-database/
  4. Cloudflare Docs: D1 Release notes
    https://developers.cloudflare.com/d1/platform/release-notes/
  5. Cloudflare Blog: Workflows is now GA
    https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution/
  6. Cloudflare Docs: Choosing a data or storage product
    https://developers.cloudflare.com/workers/platform/storage-options/
  7. InfoQ: Cloudflare Upgrades D1 Database with Global Read Replication
    https://www.infoq.com/news/2025/05/cloudflare-d1-global-replication/
Tags #Cloudflare D1 #Workers #Automation #AI #Database