mcpField
C
cloudflare-durable-objects

cloudflare-durable-objects

github.com/ovachieverクラウド・DevOps5648 回インストール48 週間前に更新
リポジトリ

cloudflare-durable-objects について

Workers で Cloudflare Durable Objects を構築するための実践的なスキルです。ストレージ、WebSocket の休止、アラーム、RPC、マイグレーション、実装パターンを解説します。

主な機能

  1. 01SQLite またはキーバリューストレージの設定
  2. 02WebSocket の休止と状態の管理
  3. 03アラームによる永続タスクのスケジュール
  4. 04RPC または HTTP fetch インターフェースの利用
  5. 05クラスとストレージのマイグレーション設定
  6. 06レート制限やセッション管理のパターン

活用シーン

  1. 01永続化する WebSocket チャットルームの構築
  2. 02クリーンアップやバッチ処理のスケジュール
  3. 03ユーザー別セッションやレート制限データの保存
  4. 04複数の Durable Objects 間での処理調整

インストール

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

$npx skills add https://github.com/ovachiever/droid-tings --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
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
wrangler-do-config.jsonc

詳細

Cloudflare Durable Objects

ステータス:本番環境対応 ✅ 最終更新日:2025-11-23 依存関係: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
  • 2025年5月:Durable Objects で Python Workers をサポート
  • 2025年4月:SQLite が 10GB ストレージで GA(ベータ版から GA、1GB から 10GB に拡大)、無料プランで利用可能に
  • 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 を使用する

キー・バリュー 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. 復帰 → メッセージを受信 → コンストラクターを実行 → ハンドラーを呼び出す

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

ハイバネーションに対応したパターン
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() — ハイバネーションを妨げる

アラーム 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

既知の問題の予防

このスキルは、ドキュメントに記載された 15 件以上の問題を防止します。

問題 #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 を使用します(推奨)

問題 #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
  });
}
問題 #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);
問題 #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 を使用しているにもかかわらず料金が高額になる 出典:https://developers.cloudflare.com/durable-objects/best-practices/websockets/ 原因:送信 WebSocket はハイバネーションをサポートしていません 防止策:ハイバネーションはサーバー側(受信)WebSocket にのみ使用します

Issue #8:グローバルな一意性に関する混乱

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

Issue #9:KV バックエンドの deleteAll が一部しか実行されない

エラー:ストレージが完全に削除されず、課金が続く 出典:https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/ 原因:KV バックエンドの deleteAll() は一部しか実行されない場合があります 防止策:アトミックな deleteAll を実行するには SQLite バックエンドを使用します

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/ 原因:ロケーションヒントはベストエフォートであり、保証されません 防止策:厳格な要件には jurisdiction を使用します

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 が完了するようにします


設定と型

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. マイグレーション設定を慎重に確認してください

コメント