ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Rivet Actors SQLite VFS v2 的设计约束全景:C1–C8 如何推导出分片 LTX + Delta Log 架构

Rivet Actors SQLite VFS v2 的设计约束全景:C1–C8 如何推导出分片 LTX + Delta Log 架构 Rivet Actors SQLite VFS v2 的设计约束全景C1–C8 如何推导出分片 LTX Delta Log 架构【免费下载链接】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 仓库内部设计文档 constraints.md状态2026-04-15 已锁定为主体完整解读 SQLite VFS v2 的八条承重约束C1–C8、被这些约束直接排除的方案清单、基于 20 ms RTT 假设的性能数学推导以及从 A/B/C/D 四种存储布局中选出分片 LTX delta logOption D的完整论证。结合仓库中的基准测试 BENCH_RESULTS.md 与后续落地的规范 SPEC.md读完后你将理解为什么 Rivet 的 actor 内 SQLite 必须跑在进程内、为什么写入是首要优化目标、以及一次 round trip 值 20 ms这一前提如何决定了整个存储格式。1. 问题起点v1 的每页 KV 布局在真实 RTT 下不可用Rivet 的 actor 是有状态工作负载的基元项目描述面向 AI agent、协作应用与持久执行actor 内嵌 SQLite 时v1 的实现是把 SQLite 页面按 4 KiB 一页拆成独立 KV key经 VFS 读写隧道访问引擎侧 KV。仓库中的基准测试直接量化了这条路径的代价。BENCH_RESULTS.md 记录了 2026-04-15 在本地开发环境约 2.9 ms RTT、端点http://127.0.0.1:6420的插入基准PayloadActor DB 插入原生 SQLite 插入Actor DB 对比原生1 MiB832.2 ms1.8 ms461×5 MiB4199.6 ms25.3 ms166×10 MiB9438.2 ms45.5 ms207×其中 1 MiB 用例的 debug trace 显示 317 次 KV round-trip30 次get 287 次put累计 577 个 key 写入。测试文档给出的结论是瓶颈明显不是 SQLite 本身而是 SQLite VFS / KV 通道 / 引擎路径即把数据库按 4 KiB 页面切块、为一次大插入发出海量 KV 读写。生产环境 10 MiB 插入实测 26.2 s本地 19.2 s比本地更差——这正是 constraints.md 选择20 ms 作为 C6 典型 RTT 假设的现实背景。v1 的另一处实现证据在 depot-client 的 VFS 实现 与 engine/sqlite-vfs.md 中4 KiB chunk 页面布局、打开时固定的 pragmajournal_modeDELETE、locking_modeEXCLUSIVE、auto_vacuumNONE等。constraints.md 的全部推导都是要回答在 20 ms RTT 的世界里这套布局该怎么改。2. 八条承重约束C1–C8逐条解读constraints.md 开篇声明所有后续设计文档protocol-and-vfs.md、compaction-design.md、key-decisions.md 及各类 workload 分析都从本文档推导而来任何一条约束一旦改变设计必须重新评估。这是它被称为canonical source of truth的原因。C1 — 热读必须零 round tripRivet SQLite actor 的主导场景是稳定的工作集 对该工作集的重复查询这个场景必须以内存速度执行。唯一实现方式是SQLite 本体连同它的页缓存都运行在 actor 进程内。该约束明确排除了三类架构引擎托管 SQLiteModel A每条查询都要付一次到引擎的 RTT即使数据是热的违反 C1混合设计Model C规范库在引擎侧、actor 侧只读缓存跨边界的缓存失效是独立的设计泥潭且热读仍会频繁命中 cache-miss RTT拦截在 SQLite pager 之上的架构在 actor 内解析 SQL、把操作分发到远端绕过了 SQLite 自己的页缓存丢掉免费热读这一性质。C2 — 写入是首要优化目标在 C1 允许的范围内VFS 首先为写入优化大原子提交信封、分片存储一个 KV value 装很多页、压缩、以及用 prefetch/preload 把冷读拉到可接受。读是次要问题因为 C1 已经免费解决了热读。这是对 v1 调优方向的刻意反转v1 用逐页 KV key 换取了冷读简单性v2 则接受稍贵的冷读取回一个分片、切出目标页换取便宜得多的写入路径。C3 — 冷读付 round trip可接受冷读缓存未命中不可能零 RTT数据总得从某处来。设计会优化它——分片摊薄每 key 开销、prefetch 合并顺序访问、preload hint 在冷启动时预热缓存——但不试图将其降为零。无法容忍冷读延迟的负载大随机访问、工作集放不进缓存超出范围需要另做运行时。C4 — 无本地磁盘KV 是唯一持久存储所有状态字节都住在 actor 的 KV 子空间中。页缓存和脏缓冲是临时的随 actor 进程消失重启后的恢复完全来自 KV而非任何本地文件。C5 — 单写入者故障转移窗口内用 fencing 防护Rivet 同一时刻最多调度一个 actor 进程。但引擎的 runner-id 检查是尽力而为的在 runner 重新分配时存在一个短暂窗口两个进程可能都认为自己拥有该 actor。v2 用代际令牌 fencing防御每个提交操作携带(generation, expected_head_txid)引擎执行 CAS不匹配则失败关闭fail closed每次冷启动都递增 generation。这让head 指针提交模式在并发写入者下是安全的。这一点在后续 SPEC.md 中落到协议层每个sqlite_*op 都带 fencing 字段收到SqliteFenceMismatch即标记 actor 死亡、拒绝后续操作并由 Rivet 干净重启——与分布式系统中 leader 丢失租约同构。key-decisions.md 还记录了动机对抗性评审发现的 4 个正确性 bug 全部追溯到缺失的 fence一次 CAS 即可全部修复。C6 — VFS 到 KV 的 RTT 高典型约 20 ms这是承重参数批量大小、本地缓存 vs 远端取回的取舍、用 CPU 换 round trip的决策全都基于它。文档特别强调了参数的鲁棒性方向如果生产实际更低设计仍然成立只是没那么关键如果更高设计的价值只会上升——每个省下 round trip 的架构决策都按比例回报。C7 — v1/v2 分发复用引擎既有 schema-version 标志v2 引擎已有在 v1/v2 actor 实现间路由的 schema-version 机制v2 SQLite VFS 直接搭车不新增分发字节、不探测 key、不在 SQLite 子空间里放独立版本标签。引擎对该 actor schema 版本的说法直接决定它拿到哪个 VFS 实现。C8 — 允许 SQLite v2 破坏性 API 兼容v1 actor 永远留在 v1v2 是新世界没有 Drizzle 兼容 shim、没有要保留的 v1 trait 面、没有要维持的磁盘格式兼容。v2 可以自由更改runner 协议上的线格式自由加新 opKV 磁盘布局分片、压缩、索引随需随改Rust 侧SqliteKvtrait 面新方法、新错误变体面向用户的 JS/TS SQL API 面如果确实显著改善设计。v2 唯一不能做的是损坏 v1 actor 的数据。由于分发发生在引擎 schema-version 层C7两者不共享 key 空间没有交叉污染风险。仓库中 SPEC.md 把这一点具体化为v1 用 UDB 前缀0x08v2 用前缀0x02两套 key 永不交集。3. 约束直接排除的方案清单constraints.md 用一张表把哪些想法被哪条约束杀死固定下来这是防止设计空间回流的关键护栏被排除的想法排除依据引擎托管 SQLiteModel A引擎跑 SQLiteactor 发 SQL 字符串C1热读永远 ≥1 RTT本地 远端混合 SQLiteModel C引擎为规范库actor 做缓存C1跨边界缓存失效、热读回退v2 用逐页 KV 布局一页一个 KV keyC2C6每 key 开销 × 20 ms × 页数 冷读与批量写代价不可接受v1→v2 迁移C7C8schema-version 标志隔离两者同一 actor 内不共存无需迁移Drizzle 兼容 shim 或任何 v1 API 保留C8维护 LTX 滚动校验和隐式v2 不复刻第三方 LTX 工具链字节保真由 SQLite UDB 保证把 LTX log 物化成逐页 KV key最初 v2 草案的 LOG→PAGE materializerC2进入时付 LTX 编码成本、出去时付逐页成本两头收益都没拿到值得注意的是最后两项v2 最终只借用 LTX 的格式LZ4 压缩页 页索引而明确放弃了 LTX 的滚动校验和维护工具链最初的actor 内把 LTX log 物化回逐页 KV方案也被 C2 否决——这正是 delta log 方案的前身教训。4. 约束的数学含义v1 数字在 20 ms 下放大 7 倍constraints.md 以 BENCH_RESULTS.md 中约 2.9 ms RTT 的实测为基线按 C6 的 20 ms 假设把 v1 所有冷路径数字放大约 7 倍工作负载v1 3 ms今日基准v1 20 ms生产目标v2-shards 20 ms1 MiB 插入832 ms287 RTT约 5.7 s约 3 RTT × 20 ms 60 ms10 MiB 插入9438 ms约 2k RTT约 65 s约 5 RTT × 20 ms 100 ms100 页冷读约 290 ms100 RTT约 2 s约 2 RTT × 20 ms 40 ms缓存内页热读约 5 µs0 RTT约 5 µs约 5 µs结论原文非常直接在 C6 之下v1 对任何非平凡写入负载都濒临不可用v2 不是可选项——它是 Rivet SQLite 在生产 RTT 下服务严肃负载的唯一路径。 需要说明的是表中 v2 列是按分片 RTT 数估算的设计值v2 尚未实现时的推导而非实测v1 列则是实测基线线性外推。5. 架构决策A/B/C/D 四种布局的诚实对比在 C1C2C6 的包围盒内设计空间很小。constraints.md 列出四个选项选项布局优点缺点A逐 keyv1 现状简单读 miss 每页 1 RTT每 key 开销 × 20 ms 任何冷负载灾难B分片原始字节每 KV value 约 64 页裸拼接每 key 开销约降 1000×格式简单每次提交都需读改写受影响的分片小提交付全分片代价无压缩C分片 LTX分片内 LZ4B 的收益 约 2× 压缩与 B 同样的每次提交 RMW 问题读解压有 CPU 成本很小D分片 LTX delta logDELTA 层装小的近期 LTX 文件SHARD 层装大的压实后 LTX 文件小提交 1 RTT 落 DELTA、无需改写分片后台压实把 DELTA 折叠进 SHARD大小提交写延迟都最优机制最多内存 delta 页索引、后台压实任务、fencing 保护的物化 op推荐Option D分片 LTX delta log。文档特别说明早期版本曾夸大 D 在大提交上的优势下表是经过修正的诚实对比。假设分片约 64 页 256 KiB 原始 / 128 KiB 压缩kv_sqlite_*op 信封约 9 MiB。逐场景推演20 ms RTT4 页小提交OLTP 主导场景B 冷路径 1 RTT 读分片 1 RTT 写分片 40 ms传输 256 KiBC 同 RTT 模式但传 128 KiBD 直接写 delta、无需读分片 20 ms仅传约 8 KiB。5,000 页提交约 80 个分片、约 10 MiB 压缩B 与 C 都是 2 RTT 读 2 RTT 写 80 msD 把全部页编码为单个 LTX delta约 10 MiB 压缩分两个信封 2 RTT 40 ms。热页重写同样 4 页更新 100 次B/C 约 200 RTT缓存后约 100约 4 s、传输 25/12.5 MiBD 100 次 delta 追加 1 次压实约 2 s、仅 1 MiB——2× RTT 优势、约 25× 带宽优势。冷单页读三者都是 1 RTT 20 ms平手。热缓存命中任何选项都是 0 RTTC1 的意义所在。汇总对比表工作负载A逐 key v1B原始分片CLTX 分片DLTX delta4 页提交、冷分片1 RTT20 ms2 RTT40 ms、256 KiB2 RTT40 ms、128 KiB1 RTT20 ms、8 KiB4 页提交、热分片1 RTT20 ms1 RTT20 ms、256 KiB1 RTT20 ms、128 KiB1 RTT20 ms、8 KiB5,000 页提交约 2k RTT journal 回退40 s4 RTT80 ms、20 MiB4 RTT80 ms、10 MiB2 RTT40 ms、10 MiB热 4 页重写 × 100100 RTT2 s约 200 RTT4 s、25 MiB约 200 RTT4 s、12 MiB约 100 RTT2 s、1 MiB冷单页读1 RTT、4 KiB1 RTT、256 KiB1 RTT、128 KiB1 RTT、128 KiB热读0000D 在每一类负载上获胜或平手最大赢面在小提交对 B/C 2× RTT 32× 带宽因为写入不必先读分片、热页重写2× RTT 约 25× 带宽delta 极小且压实惰性折叠、任意大小的冷分片提交D 跳过 B/C 必须付的先读后写罚金。而 D 在 5,000 页大提交上只有 2× 的小优势因为那个量级上两个方案都是信封受限而非 RTT 受限——文档坦承 D 的戏剧性优势在提交大小分布的小端不在大端。D 几乎不赢的场景受影响分片已热的超大提交RTT 打平、仅带宽赢、冷单页读平手。D 相对 B/C 的成本是实现复杂度内存dirty_pgnos_in_logmap让读知道哪些页在 delta 里、后台压实任务、fencing 保护的原子 op写新分片 删已折叠 delta 推进 META、崩溃后孤儿 delta 的恢复逻辑。文档评估这些代价真实但都已充分理解且分片delta 变体把压实单元收敛到单个分片 key而非多 key 范围删除 逐页写序列避开了早期设计对抗性评审发现的多数正确性隐患。6. LZ4 压缩进还是不进独立于布局选择分片/delta 内的字节要选 LZ4LTX 风格还是裸字节。推荐进。论证很简单LZ4 解压约 1 GB/s256 KiB 分片解压约 250 µs完全被 20 ms RTT 掩盖压缩省下约 50% 冷读传输字节和约 50% KV 存储成本在 20 ms RTT 下净收益为正。文档还留了退路如果实现成本成为问题可以先发 D-裸字节版后续再上 LZ4——因为格式是 v2 内部的可自由变更C8。这个分片给约 1000×、LTX 压缩在其上再给约 2×的分层量化在 key-decisions.md 中被再次确认为两个可分离的决策SPEC 层面则确认 LTX 滚动校验和显式弃用V3 格式允许置零字节保真由 UDB SQLite 保证。7. 遗留开放问题不影响架构决策constraints.md 区分了约束问题与实现调优问题后者不阻塞上面的架构决策完整继承如下分片大小约 64 页256 KiB 原始 / 128 KiB 压缩是工作假设需实测在每分片取回成本 vs 点查过取间平衡默认页缓存大小mvSQLite 用 5,000 页约 20 MiBworkload 分析提示分析型 actor 想要 50,000 页约 200 MiB倾向做成 per-actor 可配置默认值靠经验确定压实触发按时间、delta 数还是 delta 大小倾向 delta 大小为主、delta 数为上限压实并发常驻后台 vs 空闲时运行 vs 仅受压时运行各有尾延迟含义preload hint API 面配置期列表、运行时可变、还是两者都要按 key、按范围还是带标签20 ms RTT 是典型值还是最坏值若为典型值v2 是团队最高优先级项目若为最坏值多数用户 5 ms、少数跨区紧迫性是按它设计而非明天上线。两种情况下架构相同。这些开放问题在后续文档中有部分收敛SPEC.md 第 14 节给出了带默认值与测量计划的调优参数表如shard_size64建库后不可变、N_count64delta 压实阈值、B_hard200 MiB背压阈值、T_idle5 s空闲定时器并注明策略是按默认值上线、第一天起埋全量指标、用基准逐个 sweep 后定生产默认值。8. 约束在仓库中的落地与后续演化constraints.md 是约束层仓库里可以看到它向下推导出的三层产物值得作为延伸阅读协议与 VFS 层protocol-and-vfs.md 把 C1–C8 展开为sqlite_*op 家族takeover/get_pages/commit/commit_stage/commit_finalize/preload、actor 侧三层读路径写缓冲 → LRU 页缓存 → 引擎取回每次引擎取回一个 RTT以及 fencing 失败即死fail-dead语义。C5 的head 指针提交模式在并发写入者下安全在此落成每个 op 携带(generation, expected_head_txid)引擎 CASfence mismatch 无重试、进程退出。决策层key-decisions.md 对每条决策记录决策 / 为什么 / 被否掉的替代 / 何时替代会赢 / 具体收益并补了一条 constraints.md 未展开的重要决策——压实跑在引擎侧而非 actor 侧actor 侧每轮压实要付 3 RTT引擎侧因 UDB 本地而约 0 成本同时 actor 侧dirty_pgnos_in_logmap 和一层读路径逻辑整个消失。实现层SPEC.md 是后续合并出的规范文档包含DBHeadMETA结构、0x02前缀的 key 格式SHARD/shard_id_be32、DELTA/txid_be64、PIDX/delta/pgno_be32等、FDB 硬上限适配100 KB value 限制下的 10 KB 分块、10 MB 事务上限决定MAX_DELTA_BYTES 8 MiB、独立存储配额默认 10 GiB等工程细节。仓库实现侧的证据见 engine/sqlite-vfs.md 与 SQLITE_OPTIMIZATIONS.mdv2 存储 key 使用0x02子空间 大端数字后缀以维持scan_prefix的数值序slow-path staging 直接把编码后的 LTX 写在 DELTA chunk key 下测试与恢复代码不应期待/STAGEkey优化通过RIVETKIT_SQLITE_OPT_*环境/特性标志门控如读前模式、staging 缓存 TTL指标暴露在 actor 的 Prometheus 端点上。这体现了 C8 承诺的演化空间——约束锁定后具体格式与开关可以在 v2 内部自由调整而不破坏架构。9. 小结constraints.md 的方法论价值在于它先冻结八个不可协商的前提进程内 SQLite 换零 RTT 热读、写入优先、冷读可付 RTT、KV 唯一持久层、fencing 单写入者、20 ms 承重 RTT、schema-version 分发、允许破坏性变更再让一切方案在这条约束链上做可计算的取舍——被排除表让方案空间只进不出A/B/C/D 对比表用统一假设64 页分片、9 MiB 信封、20 ms RTT逐工作负载算账最终选出在所有负载类别上获胜或打平的分片 LTX delta log 布局。对于任何把本地文件数据库搬到远端 KV的系统设计这套先写约束、再做数学、后选布局的推导路径本身就是可直接复用的工程方法而 BENCH_RESULTS.md 中 461×–207× 的实测差距则持续提醒着 C6 这条承重参数的分量。【免费下载链接】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),仅供参考
返回列表