Durable Objects KV 違い
はじめに
Cloudflare Workersの永続化層にアクセスするためのDurable Object Storage APIにはSQL APIのほかに、Synchronous KV APIとAsynchronous KV APIという、非常に似た2つのKV APIが存在するため、まとめてみることにしました。
3行で分かるCloudflare Workers
- Cloudflare Workers: V8 isolatesのメモリ内でJavaScriptコードを実行できる。ステートレスなので実行後はデータが消える。
- Durable Objects: ユニークIDで識別されるJavaScriptクラスインスタンス。メモリ内に一時的な状態を保持できるほか、Durable Object Storage APIを用いてディスク上に永続化することもできる。
- Durable Object Storage API: 強力な整合性を持つ永続ストレージで、Durable Objectに紐づいて動作する。ステートフルなので実行後もディスク上にデータが残る。
Asynchronous KV API
Durable Objectsのクローズドベータが開始された2020年9月から存在するAPIです。
KV-backedおよびSQLite-backed Durable Objectsの両方で利用可能ですが、メソッドは非同期でディスクI/Oなどの遅延が発生します。
this.ctx.storage.*にあるメソッドを使う必要があり、実行時にawaitが必要になります。
// Get
const value = await this.ctx.storage.get("key"); // await Promise<any> or undefined
// Put
await this.ctx.storage.put("key", value); // await Promise<void>
// Delete
const deleted = await this.ctx.storage.delete("key"); // await Promise<boolean>
// List
const entries = await this.ctx.storage.list(); // await Promise<Map<string, any>>
for (const [key, value] of entries) { ... }
interface (2025-09-26T00:00:00Z)
interface DurableObjectStorage {
get<T = unknown>(
key: string,
options?: DurableObjectGetOptions,
): Promise<T | undefined>;
get<T = unknown>(
keys: string[],
options?: DurableObjectGetOptions,
): Promise<Map<string, T>>;
list<T = unknown>(
options?: DurableObjectListOptions,
): Promise<Map<string, T>>;
put<T>(
key: string,
value: T,
options?: DurableObjectPutOptions,
): Promise<void>;
put<T>(
entries: Record<string, T>,
options?: DurableObjectPutOptions,
): Promise<void>;
delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;
delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;
}
interface DurableObjectListOptions {
start?: string;
startAfter?: string;
end?: string;
prefix?: string;
reverse?: boolean;
limit?: number;
allowConcurrency?: boolean;
noCache?: boolean;
}
interface DurableObjectGetOptions {
allowConcurrency?: boolean;
noCache?: boolean;
}
interface DurableObjectPutOptions {
allowConcurrency?: boolean;
allowUnconfirmed?: boolean;
noCache?: boolean;
}
KV-backed Durable Objects
KV-backed Durable ObjectsとはAsynchronous KV APIと同様に2020年9月から存在するキー・バリューストアベースのストレージバックエンドのことを指します。
SQLiteではないため、Asynchronous KV APIとAlarms APIしか使えませんし、無料枠もありません。
Synchronous KV API
2025年9月に追加されたAPIです。
これにより
ctx.storage.kvが追加され、get()、put()、list()、delete()メソッドを持つ。これら4つのメソッドは、ctx.storageの同名メソッドとシグネチャが同一だが、以下の点が異なる:
- 同期的に返す。Promiseではない。
- SQLiteベースのDOを必要とする。
allowConcurrencyおよびnoCacheオプションは SQLite では意味をなさないため存在しません(ただしこれらのオプションを渡すことは可能で、無視されます)。allowUnconfirmedオプションも存在しません。これは意味がないからではなく、SQLite バックエンドの DO に対して一般に実装されていないためです。将来実装される可能性があります。- 細かい点:複数のキーを一度に
get()した場合、返されるマップはキーが指定された順序で反復処理されます。従来のアルファベット順は、追加のソート操作を必要とする歴史的な癖でした。後方互換性のために維持されてきましたが、おそらく誰も気にしないため、新インターフェースではソートを省略しています。
storage.kvという名称はstorage.sqlとの親和性を保ちつつ、後方互換性の懸念を回避しています。長期的には旧来の非同期メソッドを「非推奨化」(ただし永続的にサポート)すべきでしょう。コミットメッセージを
www.DeepL.com/Translator(無料版)で翻訳しました。
SQLite-backed Durable Objectsでのみ利用可能で、this.ctx.storage.kv.*にあるメソッドを使う必要があり、非同期I/Oを待たずに完了するため、イベントループをブロックせずに高速に操作できます。
// Get
const value = this.ctx.storage.kv.get("key"); // any or undefined
// Put
this.ctx.storage.kv.put("key", value); // void
// Delete
const deleted = this.ctx.storage.kv.delete("key"); // boolean
// List
const entries = this.ctx.storage.kv.list(); // Iterable<[string, any]>
for (const [key, value] of entries) { ... }
interface (2025-09-26T00:00:00Z)
interface SyncKvStorage {
get<T = unknown>(key: string): T | undefined;
list<T = unknown>(options?: SyncKvListOptions): Iterable<[string, T]>;
put<T>(key: string, value: T): void;
delete(key: string): boolean;
}
interface SyncKvListOptions {
start?: string;
startAfter?: string;
end?: string;
prefix?: string;
reverse?: boolean;
limit?: number;
}
SQLite-backed Durable Objects
SQLite-backed Durable Objectsとは2024年9月以降の、2025年4月に無料枠が追加された、組み込みSQLiteデータベースベースのストレージバックエンドのことを指します。
古くからあるAsynchronous KV API, Alarms APIだけではなく、SQL API, PITR API, Synchronous KV APIも使うことができます。
なお、CloudflareダッシュボードのDurable Objectsでは「SQL バックエンド」と表示されています。
(バックエンドを指しているのであって、SQL APIとKV API両方使える)
Hello, World!
要するに、今後はSQLite-backed Durable ObjectsのSynchronous KV APIを使ったほうがいいので、以下のような感じで書いたほうがいいです。
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject<Env> {
kv: SyncKvStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.kv = ctx.storage.kv;
}
async sayHello(): Promise<string> {
this.kv.put("content", "Hello, World!");
const result = this.kv.get("content");
return String(result);
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/") {
const stub = env.MY_DURABLE_OBJECT.getByName(url.pathname);
const greeting = await stub.sayHello();
return new Response(greeting);
}
return new Response("404 Not Found", { status: 404 });
},
} satisfies ExportedHandler<Env>;
もちろん、複雑なクエリを書きたい場合は以下のようにSQL APIを使いましょう。
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject<Env> {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
}
async sayHello(): Promise<string> {
this.sql.exec("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, content TEXT)");
this.sql.exec("INSERT OR REPLACE INTO posts (id, content) VALUES (1, 'Hello, World!')");
const result = this.sql.exec("SELECT id, content FROM posts WHERE id = 1").one();
return String(result.content);
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/") {
const stub = env.MY_DURABLE_OBJECT.getByName(url.pathname);
const greeting = await stub.sayHello();
return new Response(greeting);
}
return new Response("404 Not Found", { status: 404 });
},
} satisfies ExportedHandler<Env>;
まとめ
- Asynchronous KV API: KV-backedおよびSQLite-backed Durable Objectsの両方で利用可能。
ctx.storage経由でアクセスし、メソッドは非同期でPromiseを返す。awaitが必要。最終的に非推奨化になる?(ただし永続的にサポートする?) - Synchronous KV API: SQLite-backed Durable Objectsでのみ利用可能。
ctx.storage.kv経由でアクセスし、メソッドは同期的に実行される。Promiseを返さず、直接値を返す。これにより、イベントループをブロックせずに高速に操作できる。 - KV-backed Durable Objects: キー・バリューストアベース。Asynchronous KV APIとAlarms APIのみサポート。シンプルだが機能が限定的で、SQLのようなリレーショナル操作は不可。レガシーで下位互換性のために残されている。
- SQLite-backed Durable Objects: 組み込みSQLiteデータベースベース。高機能で、SQL API、PITR(Point In Time Recovery)、Synchronous KV API、Asynchronous KV API、Alarms APIをサポート。フルSQLiteサポートにより、テーブル作成、JOIN、インデックス、FTS5(フルテキスト検索)、JSON関数などが可能。PITRにより、過去30日間の任意の時点にデータベースを復元可能。
Discussion