Cloudflare Durable Objectsは、エッジ上での状態管理を根本から変える技術として注目を集めています。Durable Objectsとは、エッジサーバー上でオブジェクト単位に状態を永続保持し、セッション管理やリアルタイム同期を低レイテンシーで実現するCloudflareの機能です。従来のサーバーレス設計(CloudflareのWorkers、AWS Lambdaなど)では関数実行後に状態がリセットされていましたが、Durable Objectsはこの制約を打ち破り、変化する情報(チャット履歴、ゲーム進行状況、マルチプレイヤーセッション)を一元管理できます。
「ChatbotやBotが会話の履歴を覚える仕組みが知りたい」「複数ユーザーの状態を同時に管理するにはどうするのか」「月額コストはどの程度になるか」——こうした疑問を持つ開発者や企業は少なくありません。本記事では、Durable Objectsの状態管理を実装する際の3つの設計パターン、本番環境で実際に起きるつまずき、メモリとデータベースの使い分け、そしてコスト最適化の実践方法を解説します。
Durable Objectsの状態管理の仕組み
Cloudflare Durable Objectsは、分散されたエッジサーバー上で状態を永続的に保持しながら、リアルタイムアプリケーションを構築するための仕組みです。従来のサーバーレスでは関数が実行されるたびに状態がリセットされていました。一方、Durable Objectsは「オブジェクト」という単位で状態をメモリに保持し、複数のリクエストがそのオブジェクトに連続アクセスすることで、変化する情報を管理できます。
Cloudflare Workersと異なり、Durable Objectsは特定の地理的ロケーション(通常、アクセスパターンに基づいて最初にアクセスされた地点)に固定されます。この制約が、一貫した状態管理を実現する鍵になります。たとえば、あるユーザーのチャットセッションに紐付けられたDurable Objectインスタンスは、常に同じサーバーで動作するため、メモリ内の会話履歴が次のリクエストでも確実に残っています。
これまでのエッジコンピューティング環境では、状態を保持するにはRedisのような外部キャッシュサービスに頼るしかありませんでした。しかし外部サービスの呼び出しはレイテンシーを増加させます。Durable Objectsはこの課題を解決し、エッジ上で直接状態管理と高速レスポンスの両立を可能にします。
3つの実装パターン:セッション管理から協調編集まで
Durable Objectsを使った状態管理には、用途に応じた3つの実装パターンがあります。プロジェクトの要件によって、どのパターンを選ぶかが開発の効率性とコストに大きく影響します。以下のパターンは、月間アクティブユーザー100〜100,000程度の中規模Webアプリケーションを前提としています。
パターン1:セッションベースの状態管理。 ユーザーごとのセッション情報(チャット履歴、認証トークン、フォーム途中保存など)を保持する最も一般的なパターンです。ユーザーがアクセスする度に同じDurable Objectインスタンスが状態を引き継ぎます。京谷商会のテックビルド部門でLINE Messaging APIと連携するBot実装を行った際も、このパターンでユーザーの会話コンテキストを一定期間メモリに保持し、ステートフルなダイアログを実現しました。
パターン2:リアルタイム協調編集。 複数ユーザーが同時に同じドキュメントやホワイトボードを編集する場合、Durable Objectが編集状態の一元管理役を担います。変更が加えられるたびにすべてのクライアントに即座にブロードキャストされます。
パターン3:イベントストリーム&リプレイ。 ユーザーの行動や状態の変化をイベント列として記録し、後で状態を復元する手法です。オンライン対戦ゲームのプレイヤースコア管理やリアルタイムダッシュボードに適しています。
各パターンは状態の更新頻度、耐久性要件、スケーリング特性が異なります。セッションベースはメモリ効率を優先、協調編集はレイテンシー最小化、イベントストリームは監査ログと復旧性を重視する特性があります。
本番環境で起きやすい落とし穴と対策
Durable Objectsの導入に失敗する最大の原因は、「メモリに保持した状態がいつ失われるか」の仕組みを誤解することです。開発環境では動作しても、本番環境で予期せずに状態がリセットされる事象は、Cloudflareコミュニティフォーラムで複数の報告が存在します。
最も一般的な落とし穴は、Durable Objectインスタンスが非アクティブになると、Cloudflareによって自動的にメモリからアンロードされるという事実を見落とすことです。デフォルトでは約15分間リクエストがないとインスタンスが破棄されます。「ユーザーがブラウザを開いたままで数時間待機している」という状況では、その間に状態が完全に消失し、次のユーザーアクションで初期状態に戻ってしまいます。
対処方法は2つあります。第一に、重要な状態は定期的にD1(Cloudflareのデータベース)やR2(オブジェクトストレージ)に永続化することです。メモリに保持するのは「ホットな」データ(直近の入力、キャッシュ)に限定し、永続化が必要な情報はDurable Objectのput()メソッドでストレージAPIに書き込みます。第二に、アプリケーション側で定期的なハートビート送信を実装し、Durable Objectの「非アクティブタイムアウト」をリセットすることです。WebSocketで長時間接続を保つ場合は特に重要です。
もう一つの落とし穴は、複数の独立したDurable Objectインスタンスが同じユーザーのデータに対して競合的にアクセスする設計です。「ユーザーID」を状態管理の主キーに選ぶべきなのに、「リクエストID」や「タブID」で細分化しすぎると、同じユーザーが異なるタブで作業する場合に状態が分散し、データ不整合が発生します。実装時には、必ず「どのキーでDurable Objectを一意に識別するか」を最初に設計しておく必要があります。
メモリとストレージの効果的な使い分け
Durable Objectsは基本的にメモリベースの状態管理ですが、本番環境では「何をメモリに、何をDBに」という層の設計が不可欠です。
メモリに保持すべき情報は、レイテンシーが最も重要な「ホット」なデータです。チャットボットの場合、直近3〜5ターンの会話履歴、ユーザーが現在入力中のテキスト、一時的な計算結果などがこれに該当します。これらはミリ秒単位のレスポンスを期待するため、エッジのメモリに置かないと意味がありません。
クエリー応答性よりも耐久性が優先される情報(完了した取引、確定した注文、長期保存すべきログ)は、D1やR2に即座に記録します。Durable Objectのコンストラクタでenv.DB.prepare()を使ってD1の接続を開き、状態が重要な「チェックポイント」を迎える度にデータベースに書き込むパターンが標準的です。以下の疑似コードで典型的な実装フローを示します。
export class UserSession {
constructor(state, env) {
this.state = state.storage;
this.env = env;
this.conversationHistory = []; // メモリ上の会話履歴(最新5件)
}
async addMessage(userMessage) {
this.conversationHistory.push(userMessage);
if (this.conversationHistory.length > 5) {
this.conversationHistory.shift();
}
// 一定数の会話後はDBに永続化
if (this.conversationHistory.length % 10 === 0) {
await this.persistToDatabase();
}
}
async persistToDatabase() {
const stmt = this.env.DB.prepare(
'UPDATE sessions SET history = ? WHERE user_id = ?'
);
await stmt.bind(
JSON.stringify(this.conversationHistory),
this.userId
).run();
}
}
この設計により、ユーザーのレスポンス体験は損なわず、サーバー再起動時にも履歴を復元できます。
コスト構造と現実的な最適化戦略
Cloudflare Durable Objectsの料金は、インスタンスの実行時間とストレージ容量に基づいています(2026年3月時点)。Cloudflare公式の料金ページでは、3つの課金軸が明記されています:インスタンスあたりの基本料金、CPU時間課金、ストレージ容量課金です。一見「低コスト」に見えますが、大規模なセッション管理や長時間稼働が必要な場合は思わぬコスト増加になり得ます。
最も効果的なコスト最適化は、「本当に必要なインスタンスだけを生存させる」という設計です。ユーザーが3時間も操作していない古いセッションのDurable Objectを生かし続けることは無駄です。実装側で明示的にタイムアウト機構を組み込み、非アクティブなインスタンスを定期的に削除してメモリ領域を解放するパターンが実践的です。
次に、Durable Objectインスタンスの「粒度」の設計も重要です。「ユーザーごと」の粒度が基本ですが、高頻度でアクセスされるグローバルな集計データ(ランキング、イベント参加者数など)は、専用の単一インスタンスで一元管理し、複数のユーザーからのリクエストを集約する設計にすると、インスタンス数を削減できます。Cloudflare Workersとの組み合わせを工夫することも有効です。「ステートレスな処理」(キャッシュ読み取り、簡単な計算、リダイレクト)はWorkers側で済ませ、「状態管理が本当に必要な処理」だけをDurable Objectに委譲することで、総じてのインスタンス稼働時間を短縮できます。
地方中小企業がDurable Objectsを導入する際の現実的なステップ
京谷商会では、Cloudflare Durable Objectsの導入を段階的に進めることが現実的であると判断し、LINE Messaging API連携のBot実装で実際にこのアプローチを取りました。以下が具体的なステップです。
第1段階:ステートレスなWorkerで基本的なメッセージ処理を実装する。 Honoなどのシンプルなフレームワークを使い、APIのルーティングと基本的なロジックをCloudflare Workers上で完成させます。この段階では状態管理は不要です。
第2段階:最小限のセッション情報(ユーザーID、タイムスタンプ)をメモリに保持するDurable Objectを実装する。 テストを通じて、インスタンスの生存期間や更新頻度の感覚をつかみます。本当に必要なのか、Redisキャッシュで十分ではないかを判断する情報も得られます。
第3段階:D1データベースへの永続化を追加し、サーバー再起動時の復旧機構を確認する。 この段階で初めて本番運用に耐える堅牢性が確保されます。意図しないインスタンス終了時にデータが失われないことを確認することが重要です。
第4段階:WebSocket対応やブロードキャスト機能など、リアルタイム要件を追加していく。 各段階で動作確認でき、問題が発生した場合の原因特定も容易です。
このステップを踏むことで、月額基本料金や従量課金がどの程度になるかを実装を通じて把握でき、予算規模に応じた判断が可能になります。スタートアップや新規事業では第1〜2段階の実装に留め、既存システムとの統合が必要な場合に第3段階以降へ進むという柔軟な運用も有効です。
よくある質問
Q1:Durable Objectsはどのくらいの規模のアプリまでスケールしますか?
単一インスタンスあたりのスループットは、通常1秒あたり数百〜数千のリクエストを処理できます。ただし、Durable Objectは特定の地理的ロケーションに固定されるため、「グローバルに分散した数百万ユーザーの同時セッション管理」といった超大規模ユースケースには向きません。その場合、リージョンごと、あるいはテナントごとに複数のDurable Objectを配置し、リクエストをルーティングする多階層設計が必要になります。
Q2:メモリ上の状態がいつ失われるか、正確に知る方法はありますか?
Cloudflareは非アクティブ期間の明確な値を公開していません。開発環境では15分程度と想定されていますが、本番の負荷状況により異なります。重要なデータは必ずD1やR2に「検査点」として定期的に保存し、インスタンス再起動時に復元する仕組みを組み込むことをお勧めします。
Q3:Durable ObjectsはWebSocketをサポートしていますか?
はい。Durable ObjectsはWebSocket接続を保持し、複数のクライアントからの同時接続を管理できます。特にリアルタイム協調編集やマルチプレイヤーゲームで有用です。ただしWebSocket接続も非アクティブタイムアウトの対象になるため、定期的なハートビートの実装は必須です。