Hotdry.

Article

SQLite 实现持久化工作流状态机:单文件事务边界与乐观并发控制

基于 SQLite 单文件特性构建工作流状态机,通过 WAL 模式与版本号乐观并发控制实现轻量级持久化方案,给出可落地的 schema 设计与事务参数。

2026-05-29systems

当构建持久化执行引擎(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 存储parametersreturn_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 文件恢复未提交的事务。

事务边界与幂等性

持久化工作流面临一个经典问题:执行与记录的间隙。若步骤执行成功但崩溃发生在结果写入之前,重启后该步骤会被重复执行。

应对方案:

  1. 幂等性设计:为每个步骤生成唯一幂等键(如 flow_id:step:attempt),外部 API 调用时携带该键,服务端忽略重复请求。

  2. 预写日志模式:先写入 PENDING 状态,再执行步骤,最后更新为 COMPLETE。这种 "意图先行" 模式确保崩溃后可识别已启动但未完成的步骤。

  3. 单文件原子性:利用 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

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com