Durable Object Storage API 允许 Durable Objects 访问事务性且强一致的存储。Durable Object 的附加存储对其唯一实例私有,其他对象无法访问。
Durable Object Storage API 提供多种方法,包括 SQL、时间点恢复(PITR)、键值(KV)和 alarm API。可用 API 方法取决于 Durable Objects 类的存储后端,即 SQLite 或 KV。
| 方法 1 | SQLite 支持的 Durable Object 类 | KV 支持的 Durable Object 类 |
|---|---|---|
| SQL API | ✅ | ❌ |
| PITR API | ✅ | ❌ |
| 同步 KV API | ✅ 2, 3 | ❌ |
| 异步 KV API | ✅ 3 | ✅ |
| Alarms API | ✅ | ✅ |
脚注
1 每个方法都隐式包装在事务中,使其结果具有原子性,并与所有其他存储操作隔离,即使访问多个键值对也是如此。
2 像 get()、put()、delete() 或 list() 这样的 KV API 方法将数据存储在隐藏的 SQLite 表 __cf_kv 中。请注意,列出所有表时可以查看此表,但无法通过 SQL API 访问其内容。
3 SQLite 支持的 Durable Objects 还使用 ctx.storage.kv 的同步 KV API 方法,而 KV 支持的 Durable Objects 仅提供异步 KV API 方法。
Durable Objects 通过 DurableObjectStorage 接口访问 Storage API,并通过 DurableObjectState::storage 属性访问。通常通过传递给 Durable Object 构造函数的 ctx 参数的 this.ctx.storage 访问。
以下代码片段展示如何使用 Durable Object Storage API 存储和检索数据。
export class Counter extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
}
async increment() {
let value = (await this.ctx.storage.get("value")) || 0;
value += 1;
await this.ctx.storage.put("value", value);
return value;
}
}export class Counter extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
async increment(): Promise<number> {
let value: number = (await this.ctx.storage.get('value')) || 0;
value += 1;
await this.ctx.storage.put('value', value);
return value;
}
}from workers import DurableObject
class Counter(DurableObject):
def __init__(self, ctx, env):
super().__init__(ctx, env)
async def increment(self):
value = (await self.ctx.storage.get('value')) or 0
value += 1
await self.ctx.storage.put('value', value)
return valueJavaScript 是一种单线程和事件驱动的编程语言。这意味着 JavaScript 运行时默认允许请求相互交错,可能导致并发 bug。Durable Objects 运行时使用 input gate 和 output gate 的组合,在执行存储操作时避免此类并发 bug。在我们的博客文章 ↗中了解更多。
SqlStorage 接口封装修改 Durable Object 内嵌入式 SQLite 数据库的方法。SqlStorage 接口可通过 DurableObjectStorage 类的 sql 属性 访问。
例如,使用 sql.exec() 用户可以创建表并插入行。
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject 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 artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');
`);
}
}from workers import DurableObject
class MyDurableObject(DurableObject):
def __init__(self, ctx, env):
super().__init__(ctx, env)
self.sql = ctx.storage.sql
self.sql.exec("""
CREATE TABLE IF NOT EXISTS artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');
""")- 使用
ctx.storage.sql访问的 SQL API 方法仅允许在具有 SQLite 存储后端的 Durable Object 类上使用。如果在具有 KV 存储后端的 Durable Object 类上调用,将返回错误。 - 写入数据时,索引的每次行更新都会计为额外的一行。然而,索引可能有利于读密集型的用例。请参阅针对 SQLite Durable Objects 的索引。
- 将数据写入 SQLite 虚拟表 (virtual table) ↗ 也会计入写入的行数。
Durable Objects 支持 支持 SQLite 扩展的子集,以提供额外功能,包括:
有关支持函数的完整列表,请参阅源代码 ↗。
exec(query: : string, ...bindings: any[])SqlStorageCursor
query:string- 要执行的 SQL 查询字符串。
query可以包含用于参数绑定的?占位符。在query中可以执行由分号分隔的多个 SQL 语句。如果包含多个 SQL 语句,任何参数绑定仅应用于query中的最后一个 SQL 语句,并且返回的游标也仅针对最后一个 SQL 语句。
- 要执行的 SQL 查询字符串。
...bindings:any[]可选 (Optional)- 与
query中的?占位符相对应的可选的可变数量的参数。
- 与
用于将查询行结果作为对象进行迭代的游标 (SqlStorageCursor)。SqlStorageCursor 是一个 JavaScript Iterable ↗,支持使用 for (let row of cursor) 进行迭代。SqlStorageCursor 也是一个 JavaScript Iterator ↗,支持使用 cursor.next() 进行迭代。
SqlStorageCursor 支持以下方法:
next()- 返回一个表示游标下一个值的对象。返回的对象具有符合 JavaScript Iterator ↗ 规范的
done和value属性。当存在下一个值时,done设置为false,且value设置为查询结果中的下一行对象。当整个游标被消费完毕时,done设置为true,且不设置value。
- 返回一个表示游标下一个值的对象。返回的对象具有符合 JavaScript Iterator ↗ 规范的
toArray()- 迭代剩余的游标值并返回返回的行对象的数组。
one()- 如果查询结果恰好只有一行,则返回行对象。如果查询结果有零行或多于一行,
one()将抛出异常。
- 如果查询结果恰好只有一行,则返回行对象。如果查询结果有零行或多于一行,
raw():Iterator- 返回针对相同查询结果的 Iterator,其中每行作为列值数组(无列名)而不是对象。
- 返回的 Iterator 支持上述
next()和toArray()方法。 - 返回的游标和
raw()迭代器迭代相同的查询结果,并且可以组合使用。例如:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let rawResult = cursor.raw().next();
if (!rawResult.done) {
console.log(rawResult.value); // 输出 [ 123, 'Alice' ]
} else {
// 查询返回零个结果
}
console.log(cursor.toArray()); // 输出 [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]cursor = self.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;")
raw_result = cursor.raw().next()
if not raw_result.done:
print(raw_result.value) # 输出 [ 123, 'Alice' ]
else:
# 查询返回零个结果
pass
print(cursor.toArray()) # 输出 [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]SqlStorageCursor 具有以下属性:
columnNames:string[]- 按其在
raw迭代器返回的每行数组中出现的顺序排列的查询列名。
- 按其在
rowsRead:number- 作为此 SQL
query的一部分,到目前为止读取的行数。随着您迭代游标,该值可能会增加。最终值用于 SQL 计费。
- 作为此 SQL
rowsWritten:number- 作为此 SQL
query的一部分,到目前为止写入的行数。随着您迭代游标,该值可能会增加。最终值用于 SQL 计费。
- 作为此 SQL
- 列中的任何数值都会受到 JavaScript 的 52 位数字精度的影响。如果您存储一个非常大的数字(在
int64中),然后检索该值,返回的值可能会比您的原始数字精度更低。
以下 SQL API 示例使用以下 SQL schema:
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject 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 artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);INSERT INTO artist (artistid, artistname) VALUES
(123, 'Alice'),
(456, 'Bob'),
(789, 'Charlie');`
);
}
}将查询结果迭代为行对象:
let cursor = this.sql.exec("SELECT * FROM artist;");
for (let row of cursor) {
// Iterate over row object and do something
}将查询结果转换为行对象数组:
// Return array of row objects: [{"artistid":123,"artistname":"Alice"},{"artistid":456,"artistname":"Bob"},{"artistid":789,"artistname":"Charlie"}]
let resultsArray1 = this.sql.exec("SELECT * FROM artist;").toArray();
// OR
let resultsArray2 = Array.from(this.sql.exec("SELECT * FROM artist;"));
// OR
let resultsArray3 = [...this.sql.exec("SELECT * FROM artist;")]; // JavaScript spread syntax将查询结果转换为行值数组的数组:
// Returns [[123,"Alice"],[456,"Bob"],[789,"Charlie"]]
let cursor = this.sql.exec("SELECT * FROM artist;");
let resultsArray = cursor.raw().toArray();
// Returns ["artistid","artistname"]
let columnNameArray = this.sql.exec("SELECT * FROM artist;").columnNames.toArray();获取查询结果的第一行对象:
// Returns {"artistid":123,"artistname":"Alice"}
let firstRow = this.sql.exec("SELECT * FROM artist ORDER BY artistname DESC;").toArray()[0];检查查询结果是否恰好有一行:
// returns error
this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;").one();
// returns { artistid: 123, artistname: 'Alice' }
let oneRow = this.sql.exec("SELECT * FROM artist WHERE artistname = ?;", "Alice").one()返回的 cursor 行为:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.next();
if (!result.done) {
console.log(result.value); // prints { artistid: 123, artistname: 'Alice' }
} else {
// query returned zero results
}
let remainingRows = cursor.toArray();
console.log(remainingRows); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]返回的 cursor 和 raw() 迭代器遍历相同的查询结果:
let cursor = this.sql.exec("SELECT * FROM artist ORDER BY artistname ASC;");
let result = cursor.raw().next();
if (!result.done) {
console.log(result.value); // prints [ 123, 'Alice' ]
} else {
// query returned zero results
}
console.log(cursor.toArray()); // prints [{ artistid: 456, artistname: 'Bob' },{ artistid: 789, artistname: 'Charlie' }]sql.exec().rowsRead():
let cursor = this.sql.exec("SELECT * FROM artist;");
cursor.next()
console.log(cursor.rowsRead); // prints 1
cursor.toArray(); // consumes remaining cursor
console.log(cursor.rowsRead); // prints 3databaseSize: number
当前 SQLite 数据库的大小(以字节为单位)。
let size = ctx.storage.sql.databaseSize;size = ctx.storage.sql.databaseSize对于基于 SQLite 的 Durable Objects,可以使用以下时间点恢复 (PITR) API 方法将 Durable Object 嵌入的 SQLite 数据库恢复到过去 30 天内的任何时间点。这些方法适用于整个 SQLite 数据库内容,包括对象存储的 SQL 数据和使用键值 put() API 存储的键值数据。本地开发不支持 PITR API,因为本地不会存储数据更改的持久日志。
PITR API 使用"书签" (bookmark) 来表示时间点。书签是一个主要由字母数字组成的字符串,例如 0000007b-0000b26e-00001538-0c3e87bb37b3db5cc52eedb93cd3b96b。书签被设计为在词法上是可比较的:使用常规字符串比较,表示较早时间点的书签比表示较晚时间点的书签小。
ctx.storage.getCurrentBookmark(): Promise<string>
- 返回一个表示该对象历史记录中当前时间点的书签。
ctx.storage.getBookmarkForTime(timestamp: : number | Date)Promise<string>
- 返回一个表示大约给定时间点的书签,该时间点必须在过去 30 天内。如果时间戳表示为数字,则会将其转换为日期,就像使用
new Date(timestamp)一样。
ctx.storage.onNextSessionRestoreBookmark(bookmark: : string)Promise<string>
- 配置 Durable Object,以便在其下一次重启时,将其存储恢复为与给定书签处存储包含的内容完全匹配。调用此方法后,应用程序通常应调用
ctx.abort()重启 Durable Object,从而完成时间点恢复。
该方法返回一个特殊的书签,表示恢复发生之前的紧邻时间点(即使在技术上该时间点仍处于未来)。因此,在恢复完成后,可以通过对此书签执行第二次恢复来撤销它。
const DAY_MS = 24*60*60*1000;
// 恢复到 2 天前
let bookmark = ctx.storage.getBookmarkForTime(Date.now() - 2 * DAYS_MS);
ctx.storage.onNextSessionRestoreBookmark(bookmark);from datetime import datetime, timedelta
now = datetime.now()
# 恢复到 2 天前
bookmark = ctx.storage.getBookmarkForTime(now - timedelta(days=2))
ctx.storage.onNextSessionRestoreBookmark(bookmark)ctx.storage.kv.get(key:string)Any, undefined- 获取与给定键关联的值。返回值的类型为之前为该键写入的类型;如果键不存在,则返回 undefined。
ctx.storage.kv.put(key:string, valueany)void-
存储值并将其与给定键关联。值可以是 structured clone algorithm ↗ 支持的任何类型,大多数类型都满足此要求。
有关键和值的大小限制,请参阅 SQLite 支持的 Durable Object 限制
-
ctx.storage.kv.delete(key:string)boolean- 删除键及其关联的值。如果键存在则返回
true,不存在则返回false。
- 删除键及其关联的值。如果键存在则返回
ctx.storage.kv.list(options:Objectoptional)Iterable<string, any>-
返回与当前 Durable Object 关联的所有键和值,按键的 UTF-8 编码升序排序。
-
Iterable↗ 中每个返回值的类型为之前为对应键写入的类型。 -
在调用不带 options 的
list版本之前,请注意 Durable Object 中可能存储的数据量,因为所有数据都将加载到 Durable Object 的内存中,可能会达到其限制。如果对此有顾虑,请按下方文档向list传递 options。
-
startstring- 列表结果应从此键开始(包含该键)。
startAfterstring- 列表结果应在此键之后开始(不包含该键)。不能与
start同时使用。
- 列表结果应在此键之后开始(不包含该键)。不能与
endstring- 列表结果应在此键结束(不包含该键)。
prefixstring- 将结果限制为仅包含键以该前缀开头的键值对。
reverseboolean- 如果为 true,则按降序而非默认升序返回结果。
- 启用
reverse不会改变start、startKey或endKey的含义。start仍然定义可按字典序返回的最小键(包含),在降序列表中实际作为终点。end仍然定义列表应考虑的最大键(不包含),在降序列表中实际作为起点。
limitnumber- 返回的键值对的最大数量。
ctx.storage.get(key:string, optionsObjectoptional)Promise<any>- 检索与给定键关联的值。返回值的类型将是先前为该键写入的任何类型,如果键不存在则为 undefined。
ctx.storage.get(keys:Array<string>, optionsObjectoptional)Promise<Map<string, any>>- 检索与每个提供的键关联的值。
Map↗ 中每个返回值的类型将是先前为对应键写入的任何类型。Map中的结果按 UTF-8 编码升序排序,任何不存在的请求键将被省略。一次最多支持 128 个键。
- 检索与每个提供的键关联的值。
allowConcurrency:boolean- 默认情况下,系统在存储操作进行时暂停向 Object 传递 I/O 事件,以避免意外的竞态条件。传递
allowConcurrency: true以选择退出此行为并允许传递并发事件。
- 默认情况下,系统在存储操作进行时暂停向 Object 传递 I/O 事件,以避免意外的竞态条件。传递
noCache:boolean- 如果为 true,则键/值不会插入内存缓存。如果键已在缓存中,将返回缓存值,但其 last-used 时间不会更新。当你预期此键在近期不会再次使用时使用。此标志仅为提示。此标志永远不会改变代码的语义,但可能影响性能。
put(key:string, valueany, optionsObjectoptional)Promise-
存储值并将其与给定键关联。值可以是 structured clone algorithm ↗ 支持的任何类型,大多数类型都符合。
键和值的大小限制取决于你使用的 Durable Object 存储后端。请参阅:
在 KV 支持的 Durable Object 上,如果序列化值超过 128 KiB(131072 字节)值大小限制,
put()在应用写入之前抛出RangeError(例如Values cannot be larger than 131072 bytes.)。
-
put(entries:Object, optionsObjectoptional)Promise- 接受 Object 并将其每个键和值存储到 storage。
- 每个值可以是 structured clone algorithm ↗ 支持的任何类型,大多数类型都符合。
- 一次最多支持 128 个键值对。键和值的大小限制取决于你使用的 Durable Object 类型。请参阅:
delete(key:string, optionsObjectoptional)Promise<boolean>- 删除键及关联值。如果键存在则返回
true,否则返回false。
- 删除键及关联值。如果键存在则返回
delete(keys:Array<string>, optionsObjectoptional)Promise<number>- 删除提供的键及其关联值。一次最多支持 128 个键。返回删除的键值对数量。
-
put()、delete()和deleteAll()支持以下 options: allowUnconfirmedboolean-
默认情况下,系统将暂停 Durable Object 的传出网络消息,直到所有先前的写入都已确认刷新到磁盘。如果写入失败,系统将重置 Object、丢弃所有传出消息,并向任何客户端返回错误。
-
这样,Durable Objects 可以与写入操作并行继续执行,而无需担心过早确认写入,因为除非写入实际成功,否则任何外部方都无法观察 Object 的操作。
-
任何写入后,后续网络消息可能会略有延迟。某些应用可能认为基于未确认写入进行通信是可以接受的。某些程序可能希望立即允许网络流量。在这种情况下,将
allowUnconfirmed设置为true以选择退出默认行为。 -
如果你希望某些传出网络消息立即继续但不希望其他消息继续,可以使用 allowUnconfirmed 选项避免阻塞你希望继续的消息,然后单独调用
sync()方法,该方法返回的 promise 仅在所有先前的写入成功持久化到磁盘后才 resolve。
-
noCacheboolean-
如果为 true,则键/值在完成写入磁盘后立即从内存中丢弃。
-
如果键在近期不会再次使用,请使用
noCache。noCache永远不会改变代码的语义,但可能影响性能。 -
如果你在写入完成之前使用
get()检索键,将返回写入缓冲区中的副本,从而确保与最新put()调用的一致性。
-
list(options:Objectoptional)Promise<Map<string, any>>
startstring- list 结果应开始的键(含)。
startAfterstring- list 结果应在其后开始的第一键(不含)。不能与
start同时使用。
- list 结果应在其后开始的第一键(不含)。不能与
endstring- list 结果应结束的键(不含)。
prefixstring- 将结果限制为仅包含键以该前缀开头的键值对。
reverseboolean- 如果为 true,以降序而非默认升序返回结果。
- 启用
reverse不会改变start、startKey或endKey的含义。start仍定义可按字典序返回的最小键(含), effectively 作为逆序 list 的端点。end仍定义 list 应考虑的最大键(不含), effectively 作为逆序 list 的起点。
limitnumber- 返回的最大键值对数量。
allowConcurrencyboolean- 与上述
get()的 option 相同。
- 与上述
noCacheboolean- 与上述
get()的 option 相同。
- 与上述
getAlarm(options:Objectoptional)Promise<Number | null>- 获取当前 alarm 时间(如果已设置),以自 epoch 起的整数毫秒表示。如果 alarm 尚未开始,或已失败且任何重试尚未开始,则视为已设置 alarm。如果未设置 alarm,
getAlarm()返回null。
- 获取当前 alarm 时间(如果已设置),以自 epoch 起的整数毫秒表示。如果 alarm 尚未开始,或已失败且任何重试尚未开始,则视为已设置 alarm。如果未设置 alarm,
- 与
get()相同的选项,但不包含noCache。
setAlarm(scheduledTime:Date | number, optionsObjectoptional)Promise- 设置当前 alarm 时间,接受 JavaScript
Date或自 epoch 起的整数毫秒。
如果使用等于或早于
Date.now()的时间调用setAlarm(),alarm 将被安排在近期异步执行。如果此时 alarm 处理程序正在执行,它不会被取消。Alarm 可精确到毫秒级,通常会在设定时间后几毫秒内执行,但由于维护或故障转移期间的故障,可能会延迟最多一分钟。- 设置当前 alarm 时间,接受 JavaScript
deleteAlarm(options:Objectoptional)Promise- 如果存在 alarm 则删除。如果 alarm 处理程序当前正在执行,不会取消该处理程序。
setAlarm()和deleteAlarm()支持与put()相同的选项,但不包含noCache。
deleteAll(options:Objectoptional)Promise- 删除所有存储的数据,实际释放 Durable Object 使用的所有存储。对于键值存储后端的 Durable Objects,
deleteAll()删除单个 Durable Object 的所有键和关联值。对于 SQLite 存储后端 的 Durable Objects,deleteAll()删除 Durable Object 私有 SQLite 数据库的全部内容,包括 SQL 数据和键值数据。 - 对于键值存储后端的 Durable Objects,进行中的
deleteAll()操作可能失败,可能留下部分未删除的数据。SQLite 存储后端的 Durable Objects 没有部分deleteAll()问题,因为deleteAll()操作是原子的(全有或全无)。 - 对于兼容日期为
2026-02-24或更晚的 Workers,deleteAll()还会删除任何活动的 alarm。对于较早的兼容日期,deleteAll()不会删除 alarm。请单独使用deleteAlarm(),或启用delete_all_deletes_alarm兼容性标志。
- 删除所有存储的数据,实际释放 Durable Object 使用的所有存储。对于键值存储后端的 Durable Objects,
transactionSync(callback):any-
仅在使用 SQLite 支持的 Durable Objects 时可用。
-
在事务中包装
callback()并调用,返回其结果。 -
如果
callback()抛出异常,事务将回滚。 -
回调必须同步完成,即不应声明为
async或以其他方式返回 Promise。只有同步存储操作可以是事务的一部分。这旨在与使用ctx.storage.sql.exec()的 SQL 查询一起使用,这些查询同步完成。
-
transaction(closureFunction(txn)):Promise-
在单个事务中运行
txn上调用的存储操作序列,该事务要么成功提交要么中止。 -
显式事务不再必要。在没有中间
await的情况下调用的任何一系列写入操作将自动原子提交,系统在await读取操作时将阻止并发事件执行(除非你使用allowConcurrency: true)。因此,一系列读取后接一系列写入(中间没有其他 I/O)自动具有原子性,行为类似事务。
-
-
txn-
提供对上面记录的
put()、get()、delete()和list()方法的访问,以在当前事务上下文中运行。要在事务闭包中获得事务行为,必须在txnObject 上调用方法,而不是在顶层ctx.storageObject 上。
还支持rollback()函数,确保事务期间所做的任何更改将被回滚而非提交。调用rollback()后,txnObject 上的任何后续操作都将失败并抛出异常。rollback()不接受参数且不向调用方返回任何内容。 -
使用 SQLite 存储引擎 时,
txn对象已过时。直接在ctx.storageObject 上执行的任何存储操作,包括使用ctx.storage.sql.exec()的 SQL 查询,都将被视为事务的一部分。
-
sync():Promise-
将任何待处理的写入同步到磁盘。
-
这类似于自动写入合并的正常行为。如果写入缓冲区中有任何待处理的写入(包括通过
allowUnconfirmed选项 提交的写入),返回的 promise 将在它们完成时 resolve。如果没有待处理的写入,返回的 promise 将已 resolve。
-
sql 是 DurableObjectStorage 类型的只读属性,封装了 SQL API。