mcpField
C
cloudflare-durable-objects

cloudflare-durable-objects

github.com/jezwebクラウド・DevOps1.1k332 回インストール3 週間前に更新
リポジトリ

cloudflare-durable-objects について

Cloudflare Workers の開発者向けに、Durable Objects の永続状態、並行処理、WebSocket の休止、アラーム、マイグレーションを解説するガイドです。API の使い方に加え、実装パターンとよくあるエラーの予防策を扱います。

主な機能

  1. 01SQLite とキーバリューストレージ
  2. 02WebSocket の休止と状態復元
  3. 03アラームによるスケジュール処理
  4. 04RPC と HTTP Fetch のパターン
  5. 05オブジェクト ID とマイグレーション管理

活用シーン

  1. 01チャットルームの接続メタデータ保存
  2. 02セッション管理とレート制限の構築
  3. 03アラームを使ったデータの一括処理や削除

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-durable-objects

インストール後、Claude Code で/cloudflare-durable-objectsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.claude-plugin/
plugin.json
references/
alarms-api.md
best-practices.md
migrations-guide.md
rpc-patterns.md
state-api-reference.md
top-errors.md
websocket-hibernation.md
wrangler-commands.md
rules/
cloudflare-durable-objects.md
scripts/
check-versions.sh
templates/
alarms-api-do.ts
basic-do.ts
location-hints.ts
multi-do-coordination.ts
package.json
rpc-vs-fetch.ts
state-api-patterns.ts
websocket-hibernation-do.ts

詳細

Cloudflare Durable Objects

ステータス:本番環境で利用可能 ✅ 最終更新日:2026-01-21 依存関係:cloudflare-worker-base(推奨) 最新バージョン:[email protected]、@cloudflare/[email protected] 公式ドキュメント:https://developers.cloudflare.com/durable-objects/

最近の更新(2025年):

  • 2025年10月:WebSocket メッセージサイズが 1 MiB から 32 MiB に拡大、SQLite DO 向け Data Studio UI(ダッシュボードでストレージを表示・編集)
  • 2025年8月:名前付き DO 向け getByName() API ショートカット
  • 2025年6月:@cloudflare/actors ライブラリ(ベータ版)— マイグレーション、アラーム、Actor クラスパターンを備えた推奨 SDK。注意:ベータ版の安定性に関する問題については、本番環境で使用する前に現在の問題をご確認ください(RPC シリアル化、vitest 連携、メモリ管理)
  • 2025年5月:Durable Objects で Python Workers をサポート
  • 2025年4月:10GB ストレージを備えた SQLite が GA(ベータ版から GA、1GB から 10GB に拡大)、Free ティアで利用可能に
  • 2025年2月:PRAGMA optimize をサポート、参照 ID によるエラー診断を改善

クイックスタート

新しい DO プロジェクトをスキャフォールドします:

npm create cloudflare@latest my-durable-app -- --template=cloudflare/durable-objects-template --ts

または、既存の Worker に追加します:

// src/counter.ts - Durable Object class
import { DurableObject } from 'cloudflare:workers';

export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('value')) || 0;
    await this.ctx.storage.put('value', ++value);
    return value;
  }
}
export default Counter;  // CRITICAL: Export required
// wrangler.jsonc - Configuration
{
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] }  // SQLite backend (10GB limit)
  ]
}
// src/index.ts - Worker
import { Counter } from './counter';
export { Counter };

export default {
  async fetch(request: Request, env: { COUNTER: DurableObjectNamespace<Counter> }) {
    const stub = env.COUNTER.getByName('global-counter');  // Aug 2025: getByName() shortcut
    return new Response(`Count: ${await stub.increment()}`);
  }
};

DO クラスの基本

import { DurableObject } from 'cloudflare:workers';

export class MyDO extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);  // REQUIRED first line

    // Load state before requests (optional)
    ctx.blockConcurrencyWhile(async () => {
      this.value = await ctx.storage.get('key') || defaultValue;
    });
  }

  // RPC methods (recommended)
  async myMethod(): Promise<string> { return 'Hello'; }

  // HTTP fetch handler (optional)
  async fetch(request: Request): Promise<Response> { return new Response('OK'); }
}

export default MyDO;  // CRITICAL: Export required

// Worker must export DO class too
import { MyDO } from './my-do';
export { MyDO };

コンストラクターのルール:

  • ✅ 最初に super(ctx, env) を呼び出します
  • ✅ 最小限に抑えます。重い処理はハイバネーションからの復帰を妨げます
  • ✅ ストレージの初期化には ctx.blockConcurrencyWhile() を使用します
  • ❌ setTimeout/setInterval は使用しないでください(アラームを使用します)
  • ❌ WebSocket でメモリ内状態に依存しないでください(ストレージに永続化します)

ストレージ API

利用可能なバックエンドは 2 つです:

  • SQLite(推奨):10GB のストレージ、SQL クエリ、アトミック操作、PITR
  • KV:128MB のストレージ、キーと値のみ

マイグレーションで SQLite を有効にします:

{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }] }
SQL API(SQLite バックエンド)
export class MyDO extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY, text TEXT, created_at INTEGER);
      CREATE INDEX IF NOT EXISTS idx_created ON messages(created_at);
      PRAGMA optimize;  // Feb 2025: Query performance optimization
    `);
  }

  async addMessage(text: string): Promise<number> {
    const cursor = this.sql.exec('INSERT INTO messages (text, created_at) VALUES (?, ?) RETURNING id', text, Date.now());
    return cursor.one<{ id: number }>().id;
  }

  async getMessages(limit = 50): Promise<any[]> {
    return this.sql.exec('SELECT * FROM messages ORDER BY created_at DESC LIMIT ?', limit).toArray();
  }
}

SQL メソッド:

  • sql.exec(query, ...params) → カーソル
  • cursor.one<T>() → 単一の行(行がない場合は例外をスロー)
  • cursor.one<T>({ allowNone: true }) → 行または null
  • cursor.toArray<T>() → すべての行
  • ctx.storage.transactionSync(() => { ... }) → 複数ステートメントをアトミックに実行

ベストプラクティス:

  • ✅ パラメーター化クエリには ? プレースホルダーを使用します
  • ✅ 頻繁にクエリする列にインデックスを作成します
  • ✅ スキーマ変更後に PRAGMA optimize を使用します
  • ✅ 型の適合性を強制し、型の不一致を早期に検出するには、テーブル定義に STRICT キーワードを追加します
  • ✅ ブール値は整数(0/1)に変換します。SQLite バックエンドでは、ブール値は文字列 "true"/"false" としてバインドされます
キーと値の API(両方のバックエンド)
// Single operations
await this.ctx.storage.put('key', value);
const value = await this.ctx.storage.get<T>('key');
await this.ctx.storage.delete('key');

// Batch operations
await this.ctx.storage.put({ key1: val1, key2: val2 });
const map = await this.ctx.storage.get(['key1', 'key2']);
await this.ctx.storage.delete(['key1', 'key2']);

// List and delete all
const map = await this.ctx.storage.list({ prefix: 'user:', limit: 100 });
await this.ctx.storage.deleteAll();  // Atomic on SQLite only

// Transactions
await this.ctx.storage.transaction(async (txn) => {
  await txn.put('key1', val1);
  await txn.put('key2', val2);
});

**ストレージ上限:**SQLite 10GB(2025年4月に GA)|KV 128MB


WebSocket ハイバネーション API

機能:

  • インスタンスごとに数千の WebSocket 接続を処理
  • アイドル時(約10秒間アクティビティがない場合)にハイバネーションしてコストを削減
  • メッセージの到着時に自動復帰
  • メッセージサイズ上限:32 MiB(2025年10月、1 MiB から拡大)

仕組み:

  1. アクティブ → メッセージを処理
  2. アイドル → 約10秒間アクティビティなし
  3. ハイバネーション → メモリ内状態が消去され、WebSocket 接続は維持
  4. 復帰 → メッセージが到着 → コンストラクターを実行 → ハンドラーを呼び出し

重要:ハイバネーション中にメモリ内状態は失われます。serializeAttachment() を使用して、WebSocket ごとのメタデータを永続化してください。

ハイバネーション対応パターン
export class ChatRoom extends DurableObject {
  sessions: Map<WebSocket, { userId: string; username: string }>;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sessions = new Map();

    // CRITICAL: Restore WebSocket metadata after hibernation
    ctx.getWebSockets().forEach((ws) => {
      this.sessions.set(ws, ws.deserializeAttachment());
    });
  }

  async fetch(request: Request): Promise<Response> {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    const url = new URL(request.url);
    const metadata = { userId: url.searchParams.get('userId'), username: url.searchParams.get('username') };

    // CRITICAL: Use ctx.acceptWebSocket(), NOT ws.accept()
    this.ctx.acceptWebSocket(server);
    server.serializeAttachment(metadata);  // Persist across hibernation
    this.sessions.set(server, metadata);

    return new Response(null, { status: 101, webSocket: client });
  }

  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    const session = this.sessions.get(ws);
    // Handle message (max 32 MiB since Oct 2025)
  }

  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
    this.sessions.delete(ws);
    ws.close(code, 'Closing');
  }

  async webSocketError(ws: WebSocket, error: any): Promise<void> {
    this.sessions.delete(ws);
  }
}

ハイバネーションのルール:

  • ✅ ctx.acceptWebSocket(ws) — ハイバネーションを有効にします
  • ✅ ws.serializeAttachment(data) — メタデータを永続化します
  • ✅ ctx.getWebSockets().forEach() — コンストラクターで状態を復元します
  • ✅ setTimeout/setInterval の代わりにアラームを使用します
  • ❌ ws.accept() — 標準 API のため、ハイバネーションに対応しません
  • ❌ setTimeout/setInterval — ハイバネーションを妨げます
  • ❌ 処理中の fetch() — ハイバネーションを妨げます

Alarms API

将 DO 设定为在未来的指定时间唤醒。用途: 批处理、クリーンアップ、リマインダー、定期タスク。

export class Batcher extends DurableObject {
  async addItem(item: string): Promise<void> {
    // Add to buffer
    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    buffer.push(item);
    await this.ctx.storage.put('buffer', buffer);

    // Schedule alarm if not set
    if ((await this.ctx.storage.getAlarm()) === null) {
      await this.ctx.storage.setAlarm(Date.now() + 10000);  // 10 seconds
    }
  }

  async alarm(info: { retryCount: number; isRetry: boolean }): Promise<void> {
    if (info.retryCount > 3) return;  // Give up after 3 retries

    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    await this.processBatch(buffer);
    await this.ctx.storage.put('buffer', []);
    // Alarm auto-deleted after success
  }
}

API メソッド:

  • await ctx.storage.setAlarm(Date.now() + 60000) - アラームを設定します(既存のアラームは上書きされます)
  • await ctx.storage.getAlarm() - タイムスタンプまたは null を取得します
  • await ctx.storage.deleteAlarm() - アラームをキャンセルします
  • async alarm(info) - アラームが発火したときに呼び出されるハンドラー

動作:

  • ✅ 少なくとも 1 回実行され、自動的に再試行されます(最大 6 回、指数バックオフ)
  • ✅ ハイバネーションやエビクション後も維持されます
  • ✅ 成功後に自動的に削除されます
  • ⚠️ DO ごとに 1 つのアラームのみ設定できます(新しいアラームで上書きされます)

RPC と HTTP Fetch の比較

RPC(推奨): メソッドを直接呼び出せ、型安全でシンプルです

// DO class
export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('count')) || 0;
    await this.ctx.storage.put('count', ++value);
    return value;
  }
}

// Worker calls
const stub = env.COUNTER.getByName('my-counter');
const count = await stub.increment();  // Type-safe!

HTTP Fetch: リクエスト/レスポンス方式で、WebSocket のアップグレードに必要です

// DO class
export class Counter extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === '/increment') {
      let value = (await this.ctx.storage.get<number>('count')) || 0;
      await this.ctx.storage.put('count', ++value);
      return new Response(JSON.stringify({ count: value }));
    }
    return new Response('Not found', { status: 404 });
  }
}

// Worker calls
const stub = env.COUNTER.getByName('my-counter');
const response = await stub.fetch('https://fake-host/increment', { method: 'POST' });
const data = await response.json();

使い分け: 新しいプロジェクトでは RPC(よりシンプル)を、WebSocket のアップグレードや複雑なルーティングでは HTTP Fetch を使用します


DO スタブの取得

ID を取得する 3 つの方法:

  1. idFromName(name) - 一貫したルーティング(同じ名前なら同じ DO)
const stub = env.CHAT_ROOM.getByName('room-123');  // Aug 2025: Shortcut for idFromName + get
// Use for: chat rooms, user sessions, per-tenant logic, singletons
  1. newUniqueId() - ランダムな一意の ID(再利用するには保存が必要です)
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // Optional: EU compliance
const idString = id.toString();  // Save to KV/D1 for later
  1. idFromString(idString) - 保存済みの ID から再作成します
const id = env.MY_DO.idFromString(await env.KV.get('session:123'));
const stub = env.MY_DO.get(id);

ロケーションヒント(ベストエフォート):

const stub = env.MY_DO.get(id, { locationHint: 'enam' });  // wnam, enam, sam, weur, eeur, apac, oc, afr, me

管轄(厳格に適用):

const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // Options: 'eu', 'fedramp'
// Cannot combine with location hints, higher latency outside jurisdiction

マイグレーション

必要な操作: DO クラスの作成、名前変更、削除、移行

1. 作成:

{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }] }  // SQLite 10GB
// Or: "new_classes": ["Counter"]  // KV 128MB (legacy)

2. 名前変更:

{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["OldName"] },
  { "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]}

3. 削除:

{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["Counter"] },
  { "tag": "v2", "deleted_classes": ["Counter"] }  // Immediate deletion, cannot undo
]}

4. 移行:

{ "migrations": [{ "tag": "v1", "transferred_classes": [
  { "from": "OldClass", "from_script": "old-worker", "to": "NewClass" }
]}]}

マイグレーションのルール:

  • ❌ アトミックです(すべてのインスタンスが一度に移行され、段階的なロールアウトはできません)
  • ❌ タグは一意で、追加のみ可能です
  • ❌ 既存の KV ベースの DO で SQLite を有効にすることはできません
  • ✅ コードの変更にマイグレーションは不要です(スキーマの変更のみ必要です)
  • ✅ クラス名はアカウントごとにグローバルで一意である必要があります

一般的なパターン

レート制限:

async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
  const requests = (await this.ctx.storage.get<number[]>(`rate:${userId}`)) || [];
  const valid = requests.filter(t => Date.now() - t < window);
  if (valid.length >= limit) return false;
  valid.push(Date.now());
  await this.ctx.storage.put(`rate:${userId}`, valid);
  return true;
}

TTL を使用したセッション管理:

async set(key: string, value: any, ttl?: number): Promise<void> {
  const expiresAt = ttl ? Date.now() + ttl : null;
  this.sql.exec('INSERT OR REPLACE INTO session (key, value, expires_at) VALUES (?, ?, ?)',
    key, JSON.stringify(value), expiresAt);
}

async alarm(): Promise<void> {
  this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());
  await this.ctx.storage.setAlarm(Date.now() + 3600000);  // Hourly cleanup
}

リーダー選出:

async electLeader(workerId: string): Promise<boolean> {
  try {
    this.sql.exec('INSERT INTO leader (id, worker_id, elected_at) VALUES (1, ?, ?)', workerId, Date.now());
    return true;
  } catch { return false; }  // Already has leader
}

複数 DO の連携:

// Coordinator delegates to child DOs
const gameRoom = env.GAME_ROOM.getByName(gameId);
await gameRoom.initialize();
await this.ctx.storage.put(`game:${gameId}`, { created: Date.now() });

重要なルール

必ず行うこと

✅ Worker から DO クラスをエクスポートします

export class MyDO extends DurableObject { }
export default MyDO;  // Required

✅ コンストラクターで super(ctx, env) を呼び出します

constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);  // Required first line
}

✅ 新しい DO には new_sqlite_classes を使用します

{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }

✅ ハイバネーションには ctx.acceptWebSocket() を使用します

this.ctx.acceptWebSocket(server);  // Enables hibernation

✅ 重要な状態をストレージに永続化します(メモリだけに保存しないでください)

await this.ctx.storage.put('important', value);

✅ setTimeout/setInterval の代わりに アラームを使用します

await this.ctx.storage.setAlarm(Date.now() + 60000);

✅ パラメーター化された SQL クエリを使用します

this.sql.exec('SELECT * FROM table WHERE id = ?', id);

✅ コンストラクターでの処理を最小限にします

constructor(ctx, env) {
  super(ctx, env);
  // Minimal initialization only
  ctx.blockConcurrencyWhile(async () => {
    // Load from storage
  });
}
絶対に行わないこと

❌ マイグレーションなしで DO を作成する

// Missing migrations array = error

❌ DO クラスのエクスポートを忘れる

class MyDO extends DurableObject { }
// Missing: export default MyDO;

❌ setTimeout または setInterval を使用する

setTimeout(() => {}, 1000);  // Prevents hibernation

❌ WebSocket でメモリ内の状態だけに依存する

// ❌ WRONG: this.sessions will be lost on hibernation
// ✅ CORRECT: Use serializeAttachment()

❌ マイグレーションを段階的にデプロイする

# Migrations are atomic - cannot use gradual rollout

❌ 既存の KV ベースの DO で SQLite を有効にする

// Not supported - must create new DO class instead

❌ ハイバネーションを期待して標準の WebSocket API を使用する

ws.accept();  // ❌ No hibernation
this.ctx.acceptWebSocket(ws);  // ✅ Hibernation enabled

❌ ロケーションヒントが保証されていると想定する

// Location hints are best-effort only

既知の問題の防止

このスキルは、文書化された 20 件の問題を防ぎます。

問題 #1:クラスがエクスポートされていない

エラー: "binding not found" または "Class X not found" ソース: https://developers.cloudflare.com/durable-objects/get-started/ 原因: DO クラスが Worker からエクスポートされていません 防止策:

export class MyDO extends DurableObject { }
export default MyDO;  // ← Required
問題 #2:マイグレーションがない

エラー: "migrations required" または "no migration found for class" ソース: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ 原因: マイグレーションエントリーを追加せずに DO クラスを作成しました 防止策: 新しい DO クラスを作成するときは、必ずマイグレーションを追加します

{
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyDO"] }
  ]
}
問題 #3:マイグレーションの種類が違う(KV と SQLite)

エラー: スキーマエラー、ストレージ API の不一致 ソース: https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/ 原因: new_sqlite_classes ではなく new_classes を使用しました 防止策: SQLite バックエンドには new_sqlite_classes を使用します(推奨)

Issue #4: コンストラクターのオーバーヘッドがハイバネーションからの復帰を妨げる

エラー: ハイバネーションからの復帰に時間がかかる 参照元: https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/ 原因: コンストラクター内で負荷の高い処理を実行している 対策: コンストラクターを最小限にし、blockConcurrencyWhile() を使用します

constructor(ctx, env) {
  super(ctx, env);
  ctx.blockConcurrencyWhile(async () => {
    // Load from storage
  });
}
Issue #5: setTimeout がハイバネーションを妨げる

エラー: DO がハイバネーションせず、実行時間に対する料金が高くなる 参照元: https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ 原因: setTimeout/setInterval がハイバネーションを妨げる 対策: 代わりにアラーム API を使用します

// ❌ WRONG
setTimeout(() => {}, 1000);

// ✅ CORRECT
await this.ctx.storage.setAlarm(Date.now() + 1000);
Issue #6: ハイバネーション中にメモリー上の状態が失われる

エラー: WebSocket のメタデータが失われ、状態が予期せずリセットされる 参照元: https://developers.cloudflare.com/durable-objects/best-practices/websockets/ 原因: ハイバネーション時にクリアされるメモリー上の状態に依存していた 対策: WebSocket のメタデータには serializeAttachment() を使用します

ws.serializeAttachment({ userId, username });

// Restore in constructor
ctx.getWebSockets().forEach(ws => {
  const metadata = ws.deserializeAttachment();
  this.sessions.set(ws, metadata);
});
Issue #7: 送信 WebSocket はハイバネーションできない

エラー: ハイバネーション API を使用しているにもかかわらず、料金が高くなる 参照元: Cloudflare Docs | GitHub Issue #4864 原因: new WebSocket('url') を使用して外部 WebSocket サービスへの永続的な接続を維持する Durable Objects はハイバネーションできず、メモリー上に無期限に保持されます 影響を受けるユースケース:

  • リアルタイムデータベースのサブスクリプション(Supabase、Firebase)
  • メッセージブローカー(Redis Streams、Apache Kafka)
  • 外部リアルタイムサービスへの WebSocket 接続
  • サービス間通信 対策: ctx.acceptWebSocket() を使ったサーバー側(受信)の WebSocket にのみハイバネーションを使用してください。new WebSocket(url) で作成した送信 WebSocket 接続はハイバネーションを妨げます。ハイバネーションが必要な場合は、Durable Objects から送信 WebSocket 接続を行わないようにアーキテクチャを再設計してください。
Issue #8: グローバルな一意性に関する誤解

エラー: DO クラス名の予期しない競合 参照元: https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness 原因: DO クラス名はアカウントごとにグローバルで一意です 対策: DO クラス名は、アカウント内のすべての Workers で共有されることを理解してください

Issue #9: deleteAll に関する問題

エラー: ストレージが完全に削除されず、課金が継続する。または、アラームハンドラーで内部エラーが発生する 参照元: KV Storage API | GitHub Issue #2993 原因:

  • KV バックエンドの deleteAll() は一部だけ失敗する場合があります(アトミックではありません)
  • SQLite:アラームハンドラー内で deleteAll() を呼び出すと、内部エラーと再試行ループが発生します(ランタイムで修正済み) 対策:
  • アトミックな deleteAll を行うには SQLite バックエンドを使用します
  • アラームハンドラーでは、deleteAll() の前に deleteAlarm() を呼び出します:
async alarm(info: { retryCount: number }): Promise<void> {
  await this.ctx.storage.deleteAlarm();  // ← Call first
  await this.ctx.storage.deleteAll();    // Then delete all
}
Issue #10: バインディング名の不一致

エラー: DO バインディングへのアクセス時にランタイムエラーが発生する 参照元: https://developers.cloudflare.com/durable-objects/get-started/ 原因: wrangler.jsonc 内のバインディング名がコードと一致していない 対策: 名前が一致していることを確認します

{ "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] }
env.MY_DO.getByName('instance');  // Must match binding name
Issue #11: 状態サイズの上限超過

エラー: "state limit exceeded" またはストレージエラー 参照元: https://developers.cloudflare.com/durable-objects/platform/pricing/ 原因: 1GB(SQLite)または 128MB(KV)の上限を超過した 対策: ストレージサイズを監視し、アラームを使ったクリーンアップを実装します

Issue #12: マイグレーションがアトミックではない

エラー: 段階的デプロイがブロックされる 参照元: https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/ 原因: マイグレーションで段階的なロールアウトを試みた 対策: マイグレーションはすべてのインスタンスに対してアトミックにデプロイされます

Issue #13:ロケーションヒントが無視される

エラー:DO が誤ったリージョンに作成される 参照元:https://developers.cloudflare.com/durable-objects/reference/data-location/ 発生理由:ロケーションヒントはベストエフォートであり、保証されません 予防策:厳密な要件には管轄区域を使用します

Issue #14:アラームの再試行失敗

エラー:アラームの失敗後にタスクが失われる 参照元:https://developers.cloudflare.com/durable-objects/api/alarms/ 発生理由:アラームハンドラーでエラーが繰り返し発生する 予防策:冪等なアラームハンドラーを実装します

async alarm(info: { retryCount: number }): Promise<void> {
  if (info.retryCount > 3) {
    console.error('Giving up after 3 retries');
    return;
  }
  // Idempotent operation
}
Issue #15:fetch によってハイバネーションが妨げられる

エラー:ハイバネーション API を使用しているにもかかわらず、DO がハイバネーションしない 参照元:https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ 発生理由:進行中の fetch() リクエストによってハイバネーションが妨げられる 予防策:アイドル期間に入る前に、すべての非同期 I/O が完了するようにします

Issue #16:SQLite でブール値が文字列としてバインドされる

エラー:ブール値のカラムに整数 0/1 ではなく、文字列 "true"/"false" が格納されるため、ブール値の比較を行う SQL クエリが失敗する 参照元:GitHub Issue #9964 発生理由:JavaScript のブール値は Durable Objects SQLite で文字列としてシリアル化されます(D1 の動作と一貫していません) 予防策:ブール値を手動で整数に変換し、STRICT テーブルを使用します

// Convert booleans to integers
this.sql.exec('INSERT INTO test (bool_col) VALUES (?)', value ? 1 : 0);

// Use STRICT tables to catch type mismatches early
this.sql.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id INTEGER PRIMARY KEY,
    bool_col INTEGER NOT NULL
  ) STRICT;
`);
Issue #17:RPC の ReadableStream のキャンセルによって誤ったネットワークエラーがログに記録される

エラー:正しくキャンセルされているにもかかわらず、RPC 経由で ReadableStream をキャンセルすると、Wrangler dev のログに「Network connection lost」と表示される 参照元:GitHub Issue #11071 発生理由:RPC 経由で Durable Object から返された ReadableStream をキャンセルすると、Wrangler dev に誤解を招くエラーログが記録されます(ランタイムのバグではなく、表示上の問題です) 予防策:回避策はありません。キャンセルは正しく機能するため、Wrangler dev の誤ったエラーログは無視してください。この問題は本番環境や workerd のみのセットアップでは発生しません。

Issue #18:ローカル開発で blockConcurrencyWhile がブロックしない(修正済み)

エラー:コンストラクターの blockConcurrencyWhile がローカル開発でリクエストをブロックせず、開発中には見えない競合状態が発生する 参照元:GitHub Issue #8686 発生理由:古いバージョンの @cloudflare/vite-plugin と wrangler におけるバグ 予防策:この問題が修正された @cloudflare/vite-plugin v1.3.1 以降および wrangler v4.18.0 以降にアップグレードします

Issue #19:複数の wrangler dev セッション間の RPC は未サポート

エラー:"Cannot access MyDurableObject#myMethod as Durable Object RPC is not yet supported between multiple wrangler dev sessions" 参照元:GitHub Issue #11944 発生理由:複数の wrangler dev インスタンス(例:モノレポ内の個別の Workers)から RPC 経由で Durable Object にアクセスすることは、ローカル開発ではまだサポートされていません 予防策:wrangler dev -c config1 -c config2 を使用して 1 つのセッションで複数の Worker を実行するか、ローカル開発中の Worker 間の DO 通信には RPC ではなく HTTP fetch を使用します

Issue #20:コンストラクターで state.id.name が undefined になる(vitest のリグレッション)

エラー:@cloudflare/vitest-pool-workers 0.8.71 を使用すると、コンストラクターで DurableObjectState.id.name が undefined になる 参照元:GitHub Issue #11580 発生理由:vitest-pool-workers 0.8.71 でのリグレッション(0.8.38 では動作していました) 予防策:@cloudflare/[email protected] にダウングレードするか、この問題が修正された後続のバージョンにアップグレードします


設定と型

wrangler.jsonc:

{
  "compatibility_date": "2025-11-23",
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] },
    { "tag": "v2", "renamed_classes": [{ "from": "Counter", "to": "CounterV2" }] }
  ]
}

TypeScript:

import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';

interface Env { MY_DO: DurableObjectNamespace<MyDurableObject>; }

export class MyDurableObject extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }
}

公式ドキュメント


ご質問や問題がありますか?

  1. よくある問題については references/top-errors.md を確認してください
  2. 動作するサンプルについては templates/ をご覧ください
  3. 公式ドキュメントを参照してください:https://developers.cloudflare.com/durable-objects/
  4. マイグレーションの設定を慎重に確認してください

最終確認日: 2026-01-21 | スキルのバージョン: 3.1.0 | 変更点: 新しい問題を5件追加(boolean binding、RPC ストリームのキャンセル、ローカル開発での blockConcurrencyWhile、RPC の複数セッション、vitest のリグレッション)、問題 #7(送信 WebSocket のユースケース)と問題 #9(deleteAll とアラームの相互作用)を拡充、STRICT テーブルのベストプラクティスを追加、@cloudflare/actors のベータ版に関する警告を更新

コメント