当构建持久化执行引擎(Durable Execution Engine)时,大多数方案选择 PostgreSQL 或外部状态存储。然而,SQLite 作为嵌入式数据库,其单文件架构天然契合特定场景下的工作流状态机需求 —— 尤其是自包含的 Agent 系统或单租户工作流场景。
为什么选择 SQLite
SQLite 的核心优势在于零配置与单文件存储。每个工作流实例可以拥有独立的 .db 文件,天然实现租户隔离,避免了多租户环境下的并发冲突。这种设计特别适合以下场景:
- AI Agent 的本地状态持久化,确保 LLM 调用不会因进程崩溃而重复
- 边缘设备上的离线工作流执行
- 单用户 / 单租户的业务流程编排
与客户端 / 服务器数据库不同,SQLite 不需要网络连接,状态与业务数据共存于同一文件,事务边界清晰可控。
单文件事务边界设计
工作流状态机的核心是 ** 执行日志(execution log)** 表,它记录了每个步骤的调用意图和结果。推荐的 schema 设计如下:
CREATE TABLE IF NOT EXISTS execution_log (
flow_id TEXT NOT NULL,
step INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
class_name TEXT NOT NULL,
method_name TEXT NOT NULL,
status TEXT CHECK(status IN ('PENDING','WAITING_FOR_SIGNAL','COMPLETE')) NOT NULL,
attempts INTEGER NOT NULL DEFAULT 1,
parameters BLOB,
return_value BLOB,
version INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (flow_id, step)
);
关键设计要点:
复合主键:(flow_id, step) 确保同一工作流实例内的步骤顺序唯一。flow_id 建议使用 UUID,便于分布式场景下的唯一标识。
状态枚举:PENDING 表示步骤已记录但未执行,COMPLETE 表示执行成功并记录结果,WAITING_FOR_SIGNAL 用于人机交互场景。
BLOB 存储:parameters 和 return_value 使用二进制存储序列化后的数据,保持 schema 的稳定性。
版本字段:version 用于乐观并发控制,每次状态变更时递增。
乐观并发控制实现
SQLite 采用单写者模型,多个写入者会串行执行。乐观并发控制(OCC)通过版本号检测冲突,避免长时间锁定:
-- 读取当前状态
SELECT status, version, return_value
FROM execution_log
WHERE flow_id = ? AND step = ?;
-- 尝试更新(乐观并发)
UPDATE execution_log
SET status = 'COMPLETE',
return_value = ?,
version = version + 1,
timestamp = strftime('%s', 'now')
WHERE flow_id = ?
AND step = ?
AND version = ?;
若 UPDATE 返回影响行数为 0,说明期间有其他进程修改了记录,当前事务应回滚并重试。这种设计假设冲突罕见,适合工作流步骤顺序执行的场景。
可落地的配置参数
为确保单文件事务的可靠性,需配置以下 SQLite PRAGMA:
| 参数 | 推荐值 | 说明 |
|---|---|---|
journal_mode |
WAL |
启用 Write-Ahead Logging,提升并发读取性能,崩溃后可恢复 |
synchronous |
NORMAL |
平衡性能与持久化,每次 checkpoint 同步 WAL 文件 |
busy_timeout |
5000 |
写入冲突时等待 5 秒,配合指数退避重试 |
foreign_keys |
ON |
启用外键约束,维护数据完整性 |
WAL 模式是核心:它允许读取操作与写入操作并发执行,写入先记录到 .wal 文件,checkpoint 时再合并到主数据库。这不仅提升了性能,还确保了崩溃后可通过 WAL 文件恢复未提交的事务。
事务边界与幂等性
持久化工作流面临一个经典问题:执行与记录的间隙。若步骤执行成功但崩溃发生在结果写入之前,重启后该步骤会被重复执行。
应对方案:
-
幂等性设计:为每个步骤生成唯一幂等键(如
flow_id:step:attempt),外部 API 调用时携带该键,服务端忽略重复请求。 -
预写日志模式:先写入
PENDING状态,再执行步骤,最后更新为COMPLETE。这种 "意图先行" 模式确保崩溃后可识别已启动但未完成的步骤。 -
单文件原子性:利用 SQLite 的事务特性,将状态更新与业务数据修改置于同一事务内,确保工作流状态与应用数据一致。
局限性与应对策略
SQLite 单写者模型是主要瓶颈。当需要高并发写入时,考虑以下策略:
分片存储:按 flow_id 哈希分片,每个分片独立 SQLite 文件。例如 workflows/aa/bb/aabbcc.db,将并发压力分散到多个文件。
异步队列:写入操作先入内存队列,单线程消费者批量提交到 SQLite,降低锁竞争。
读多写少场景:WAL 模式支持多读者与单写者并发,适合以读取历史状态为主的场景。
总结
SQLite 作为工作流状态机的存储层,在单文件事务边界、零依赖部署方面具有独特优势。通过 WAL 模式提升并发能力,配合版本号乐观并发控制处理冲突,可构建轻量级、可靠的持久化执行引擎。关键在于理解其单写者特性,通过分片或队列化写入规避瓶颈,同时利用事务原子性确保状态与业务数据的一致性。
参考来源
- Gunnar Morling, "Building a Durable Execution Engine With SQLite", 2025
- SQLite Documentation: Write-Ahead Logging
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。