Rivet Actors SQLite VFS v2 设计决策全解:关键更正、kv_sqlite_* KV 协议与 P0 实施路线

Rivet Actors SQLite VFS v2 设计决策全解:关键更正、kv_sqlite_* KV 协议与 P0 实施路线 Rivet Actors SQLite VFS v2 设计决策全解关键更正、kv_sqlite_* KV 协议与 P0 实施路线【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors本文基于 Rivet Actors 仓库内部设计档案 design-decisions.md完整还原文档记录的七项关键设计更正含 v1 大事务真相、单写者 fencing 机制、无迁移策略、全新的kv_sqlite_*KV 协议族限制表、操作定义、引擎侧实现与 Rust trait 面以及按 P0/P1/P2 排序的行动项清单。读完后你可以理解 Rivet 为何要为大延迟 KV 通道重建 SQLite VFS以及 v2 方案相对 v1 的每一处决策依据。一、文档定位v2 设计日志的决策账本该文档是 SQLite VFS v2 设计三部曲的一部分与锁定约束集的 constraints.md 和设计走查 walkthrough.md 配套。其自我定位是一段持续更新的决策、更正与未决工作日志文档开头明确要求先读约束集因为下方所有内容都是 C0–C8 约束其中架构决策为 Option Dsharded LTX delta log的下游产物。从仓库结构看这份档案位于docs-internal/rivetkit-typescript/sqlite-ltx/archive/目录同目录还保存了 compaction-design.md、protocol-and-vfs.md、test-architecture.md 等后续设计文档说明该决策日志属于 v2 设计演化早期2026-04-15状态标记为 Active的权威记录后文引用其结论时应以锁定版约束文档为最终依据。二、七项关键更正推翻早期草稿的错误假设文档第一部分的价值在于逐条推翻早期草稿中七个不成立的假设。这些更正直接决定了 v2 的设计边界。2.1 v1 并不硬拒绝大事务——v2 是纯性能项目不是正确性修复早期草稿声称v1 会用SQLITE_IOERR硬拒绝超过 128 个脏页的事务。文档根据 SQLite 官方 tech note 更正当COMMIT_ATOMIC_WRITE返回错误时SQLite 会回滚并通过其常规的 rollback-journal 路径重试同一事务。事务依然成功只是因为 journal 路径产生大量小写操作而慢 3–10 倍。仓库中的实测数据印证了这一更正。BENCH_RESULTS.md 记录了一次RUST_LOGrivetkit_sqlite_native::vfsdebug的调试运行BENCH_MB1317 total KV round-trips 30 get(...) calls 287 put(...) calls 577 total keys written Aggregate traced KV time: get 63.1ms / put 856.0ms即 1 MiB 插入产生 287 次 put 调用——这正是 journal 回退路径被触发的证据而不是事务失败。对应地1 MiB 插入耗时 832.2ms、10 MiB 插入 9438.2ms而本地原生 SQLite 插入 10 MiB 仅需 45.5ms约 207 倍差距文档结论是瓶颈在 VFS/KV 通道而非 SQLite 本身。关键表述修正任何早期草稿中v2 让超过 128 页的事务得以成功的说法必须替换为v2 让超过 128 页的事务快速成功。2.2 LTX 滚动校验和并非扫描整个数据库——且被整体移除早期混淆认为LTX 对每次变更都要检查其整个数据库。事实是LTX 的PostApplyChecksum是一个滚动CRC64作为单一 8 字节标量通过 XOR 新页字节进来、旧页字节出去来维护——从不需要重新哈希整个 DB只在写页时需要 OLD 页字节把它们 XOR 出去。由于 SQLite 正常运行时读先于写热路径上成本几乎为零。但文档最终决策是彻底丢弃滚动校验和。理由是其存在目的是 LiteFS 副本校验而 Rivet 不做复制SQLite 自带页完整性UDB通用数据库层保证字节保真。v2 只是把 LTX 格式当作序列化封装使用所有校验和字段写零。这一决策移除了大量复杂度在Drop / 明确超出范围清单中已标记为 DROPPED。2.3 UDB 没有 10 MB 事务大小限制——真正的硬约束是 5 秒超时早期草稿引用FDB 事务大小 10 MBoptions.rs:140。文档更正UDB 的实际驱动只有 postgres 和 rocksdb仓库中engine/packages/universaldb/src/driver/目录下只有postgres和rocksdb两个子目录不存在fdb/驱动这一点可直接在仓库中验证。所谓的10 MB只是描述上游 FDB 行为的文档字符串类似地atomic.rs中的每值 100 KB约束仅作用于apply_append_if_fits这一个原子操作实现并非通用上限。当前 atomic.rs 中可以看到这个MAX_VALUE_SIZE 100_000常量及其注释 FoundationDB has a 100KB value limit。UDB 唯一强制执行的限制是 5 秒事务超时——对应 transaction.rs 中的pub const TXN_TIMEOUT: Duration Duration::from_secs(5);与文档所述一致。其余限制均由引擎侧 actor KV 层设定而这一层由项目自身掌控。因此文档要求全文把FDB框架改述为UDB对单次操作的绑定约束是 5 秒截止时间而非字节预算。2.4 单写者并非由引擎强制——必须引入 generation-token fencing早期草稿认为每个 actor 单写者——现状已如此可以依赖。事实是ws_to_tunnel_task.rs 中的runner_id检查if actor.runner_id ! conn.runner_id则报 actor does not belong to runner运行在与后续actor_kv::put不同的 UDB 事务中。在 runner 重新分配期间两个进程可能短暂地都相信自己拥有该 actor。没有显式 fencing 时两者都能破坏对方的提交。由此产生 v2 的核心安全决策generation-token fencing。每个kv_sqlite_*操作携带(generation, expected_head_txid)引擎侧操作是 CAS——generation 不匹配时失败关闭fail closed。启动时每个新 actor 调用kv_sqlite_takeover将 generation CAS 前推。文档特别强调这意味着 v2 设计硬依赖新的 SQLite 专用 KV 操作先行落地无法在现有kv_put路径上即使有 workaround发布。2.5 迁移故事是无迁移决策记录2026-04-15v1 actor 永远是 v1v2 actor 从 v2 开始并永远是 v2。Schema 版本分发在 actor 打开时通过读取其 KV 子空间中第一个 key 的版本字节完成。永远不存在 v1→v2 迁移代码用户若想把 v1 actor 迁到 v2只能导出再导入。注后续锁定的约束文档 constraints.md 的 C7 将 v1/v2 分发交由引擎既有的 schema-version 机制承担不再需要探测 key 或独立版本字节——这是该档案之后的架构演进方向两者不冲突v1/v2 永不混居、无迁移代码的结论保持一致。2.6 早期草稿 §4.9 的 pragma 变更被回退早期提案是journal_mode MEMORYsynchronous OFF。文档指出该组合在 SQLite 论坛有已记录的 bug写入泄漏出批处理原子组且当时没有实证表明IOCAP_BATCH_ATOMIC在其负载中真正省略了 journal 写入BENCH 中 1 MiB 287 次 put 的表现与走了 journal 回退路径一致。决策保留 v1 的 pragma 默认值journal_mode DELETE、synchronous NORMAL。v2 的性能收益来自 LTX 帧日志替代 journal 回退路径而不是来自改 pragma。2.7 KV 协议可以随意修改runner 协议是版本化的可以随时添加新 schema 版本不必硬着头皮让 v2 跑在现有kv_put操作之上反而鼓励新增操作。这一条解开了下文整个kv_sqlite_*操作族的封印。三、新的 kv_sqlite_* KV 协议族这些操作定义在一个新的 runner-protocol schema 版本post-v7中专为 SQLite VFS 设计带有比通用 actor KV 更大的限制现有 actor KV 操作保持不变。3.1 限制对比表Limit现有 actor KV新kv_sqlite_*最大 value 大小128 KiB约 1 MiB每次调用最大 key 数128约 512每次调用最大 payload976 KiB约 9 MiB最大 key 大小2 KiB2 KiB不变actor 总存储10 GiB与 actor KV 共享事务时间限制5 s5 sUDB 强制9 MiB 信封在隐性的5 秒内装进一个 UDB 事务约束下留出了余量。配合 SQLite 页的 LZ4 压缩实测约 2 倍压缩比9 MiB 压缩后的 LTX 对应每次原子提交约 4,500 个原始页——大多数应用事务都能舒适地装下。3.2 操作定义sketch文档给出了五个操作的 bare 类型定义type KvSqliteCommit struct { actor_id: ActorId generation: u64 expected_head_txid: u64 log_writes: listKvKeyValue // LOG/txid/frame LOGIDX/txid meta_write: KvValue // new META bytes range_deletes: listKvKeyRange // optional: cleanup of stale orphans } type KvSqliteCommitStage struct { actor_id: ActorId generation: u64 txid: u64 // for orphan-cleanup scoping log_writes: listKvKeyValue // LOG/txid/frame_idx only wipe_txid_first: bool // true on first stage, false otherwise } type KvSqliteMaterialize struct { actor_id: ActorId generation: u64 expected_head_txid: u64 page_writes: listKvKeyValue // PAGE/pgno range_deletes: listKvKeyRange // LOG/a..b, LOGIDX/a..b meta_write: KvValue // new META with advanced materialized_txid } type KvSqlitePreload struct { actor_id: ActorId get_keys: listKvKey // META, PAGE/1, user-specified hints prefix_scans: listKvKeyRange // LOGIDX/, optional user hints max_total_bytes: u64 // safety bound } type KvSqliteTakeover struct { actor_id: ActorId expected_generation: u64 new_generation: u64 }所有操作在适用处都是 CAS全部以显式错误变体失败关闭。从键布局可以读出 v2 的三层结构META单条头指针记录含 generation 与 materialized_txid、LOG/txid/frameLTX 帧日志LOGIDX/txid日志索引、PAGE/pgno物化后的原始页。大提交走KvSqliteCommitStage分阶段写日志wipe_txid_first标记首个 stage小提交直接KvSqliteCommit后台物化器用KvSqliteMaterialize把日志折叠为页冷启动用KvSqlitePreload预热接管用KvSqliteTakeover推进 generation。3.3 引擎侧实现一个 db.run 闭包 CAS每个操作在引擎侧是一个db.run(|tx| ...)闭包闭包内顺序固定1. Read META 2. CAS check (generation expected_head_txid) If mismatch: return KvSqliteFenceMismatch with current values 3. Optional range-delete cleanup 4. Apply writes 5. Commitput range-delete meta 更新三者合并在同一个 UDB 事务里原子完成是让物化器materializer原子地维持其不变量的关键新能力——现有kv_put做不到这一点。CAS 不匹配时返回带当前值的KvSqliteFenceMismatch使 VFS 侧可以区分被 fencing 拒绝与一般性错误。3.4 Rust 侧 trait 面文档规划在rivetkit-typescript/packages/sqlite-native/src/sqlite_kv.rs的SqliteKvtrait规划路径属待落地工作上扩展五个方法trait SqliteKv { // ... existing batch_get / batch_put / batch_delete / delete_range ... async fn sqlite_commit(self, actor_id: str, op: KvSqliteCommit) - Result(), KvSqliteError; async fn sqlite_commit_stage(self, actor_id: str, op: KvSqliteCommitStage) - Result(), KvSqliteError; async fn sqlite_materialize(self, actor_id: str, op: KvSqliteMaterialize) - Result(), KvSqliteError; async fn sqlite_preload(self, actor_id: str, op: KvSqlitePreload) - ResultKvSqlitePreloadResult, KvSqliteError; async fn sqlite_takeover(self, actor_id: str, op: KvSqliteTakeover) - Result(), KvSqliteError; }EnvoyKvrivetkit-napi的database.rs通过委托EnvoyHandle上的新 napi 方法实现它们内存测试驱动见 test-architecture.md则用进程内BTreeMapVecu8, Vecu8实现同一 trait使 VFS 可以在不依赖引擎的情况下做确定性测试与故障注入。四、行动项与优先级P0/P1/P2文档把未决工作分为P0最先做阻塞一切、P1v2 发布必需、P2锦上添花核心条目如下。验证与调查P0用RUST_LOGrivetkit_sqlite_native::vfsdebug跑examples/sqlite-rawgrep journal 标记写与主标记写的比例实证 v1 的 journal-mode 回退假设预期 1 MiB 插入期间以 journal 写为主P0写一个小测试人为突破 128-key 限制确认 SQLite 通过 journal 路径重发事务P1在真实工作负载而非合成数据的 SQLite 页上实测 LZ4 压缩比用于确定帧尺寸常量。协议与引擎P0runner-protocol 升版按 §3.2 定义kv_sqlite_*操作族P0在engine/packages/pegboard/src/actor_kv/sqlite.rs新文件实现引擎侧处理器每个操作一个db.run闭包加 CAS 与写入P0在EnvoyHandle上接好 napi 绑定并在SqliteKvtrait 上添加方法、由EnvoyKv实现。v2 VFS 实现P0新文件vfs_v2.rs以独立 VFS 名注册保持 v1 代码不动P0schema 版本分发——注册时探测 actor 子空间首 key 判定 v1/v2新 actor 在配置开关后默认走 v2P1移植 mvSQLite 预取预测器Apache-2.0需署名作读侧优化实现内存页缓存LRU可配置默认 5,000 页dirty_pgnos_in_log加读写锁保证读与物化器更新一致四层读路径含 LOG-miss 时对新状态重试的 fallback写路径BEGIN/COMMIT_ATOMIC_WRITE快路径 1 个 round trip慢路径 Phase 1 分 stage Phase 2 提交预算受限的后台物化器任务对写者有背压可配置的预加载提示。P2VFS 指标缓存命中率、预取效果、物化器滞后、日志大小。测试P0内存SqliteKv测试驱动——确定性、支持故障注入N 次操作后返回错误、模拟 fencing 失败、模拟部分写入预加载感知的测试脚手架允许用例声明初始 KV 状态与期望后置条件P1把现有 v1 驱动测试套件移植到 v2SQLite 引擎层面应当不可区分v2 专属测试启动时孤儿清理、generation fencing、churn 下物化器正确性、预加载提示行为、大事务慢路径的 round-trip 计数在 BENCH_RESULTS.md 增加 v2 列做直接对比保留 v1 数据为基线列不覆盖。明确丢弃项DROPPEDLTX 滚动校验和维护见 §2.2v1→v2 迁移见 §2.5journal_mode MEMORY/synchronous OFF见 §2.6VACUUM 支持——v2.0 直接声明不支持。五、未决设计问题文档在收尾时列出了五个仍开放的调优问题均不阻塞架构决策精确帧尺寸常量——需先取得 LZ4 压缩比实测值物化器背压阈值——LOG/ 消耗 10 GiB 配额的什么比例前开始阻塞写者倾向按绝对大小如 200 MiB而非配额比例设限预加载提示 API——仅配置期还是允许 actor 运行时追加倾向配置期 每 action 覆盖缓存默认大小——mvSQLite 每连接 5,000 页20 MiB对 actor 密度是否过大倾向可配置、更小的默认如 1,000 页 4 MiBv2 落地后 BENCH_RESULTS.md 现有数字的处理——v1 数字保留为基线列v2 并排展示不覆盖。文档还记录了一个并行工作流三个子 agent 分别评估大读取、聚合查询、点读点写三类工作负载下的 v2 表现结果落在 workload 分析文档中第四个子 agent 设计测试架构结果在 test-architecture.md——这份决策日志与它们是双向引用的。六、更新记录与阅读建议文档更新日志只有一条2026-04-15——初始决策日志回退 pragma 变更、丢弃滚动校验和、锁定无迁移策略、草拟kv_sqlite_*操作族、排定行动项顺序。对想深入 v2 设计的读者建议的阅读顺序是先读锁定版 constraints.mdC0–C8 约束与 Option D 架构决策含 20 ms RTT 假设下 v1/v2 的性能推演表再读本决策日志理解每项决策的来龙去脉然后配合 walkthrough.md 与 compaction-design.md 看具体数据流。需要留意的是该日志属于 archive 档案其中部分表述如打开时读首 key 分发 v1/v2、options.rs:140的行号引用在后续文档与当前代码中已有演进引用其结论时应以锁定约束集和当前源码为准。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考