十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Rivet Actors 引擎的嵌入式 KV 选型:基于 RocksDB 的 Actor 状态存储设计决策

Rivet Actors 引擎的嵌入式 KV 选型:基于 RocksDB 的 Actor 状态存储设计决策 Rivet Actors 引擎的嵌入式 KV 选型基于 RocksDB 的 Actor 状态存储设计决策【免费下载链接】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 引擎中“嵌入式 KV”的设计决策文档为什么引擎在 RocksDB、LMDB、SQLite 与 Rust 原生 KVsled / redb / fjall之间选择了 RocksDB以及这一决策如何在引擎源码中落地——包括universaldb包的 RocksDB 驱动、RocksDB 打开选项的具体取值以及为补齐 RocksDB 缺失的 MVCC 与冲突检测而自行实现的 FoundationDB 风格冲突解析器。读完后你将理解一个面向 Actor以小型写入为主的有状态工作负载的嵌入式存储如何在“稳定无聊的基础组件”与“类 FDB 的事务语义”之间做权衡并能对照源码定位其实现位置。设计目标嵌入式、类 FDB、高写入吞吐该决策文档EMBEDDED_KV.md开宗明义地列出了三条硬性目标嵌入式Embedded无需多进程KV 必须运行在引擎进程内部不依赖独立数据库进程。这与 Rivet 的单机引擎形态直接相关——Actor 的状态KV、队列、计数器、SQLite 文件等都以文件形式落在同一节点上引擎需要一把“本地钥匙”来维护状态一致性事务语义向 FoundationDB 看齐不是简单要求“支持事务”而是要求在冲突检测、重试等事务行为上尽量贴近 FDB 的模型。这意味着冲突范围conflict range、版本窗口、可重试错误等概念都必须有一致语义支持高写入吞吐Actor 工作负载是写入密集的——每次 Actor 调用可能触发多次 KV 写入写入路径上的延迟和 fsync 开销会被放大。文档同时列出一条“nice to have”更好的磁盘压缩。这说明存储体积是次要考量稳定性与写吞吐优先。决策结论选择 RocksDB文档的最终结论是选择 RocksDB理由浓缩为两点稳定、无聊stable boring成熟度高、行为可预期不会在磁盘格式或 API 上带来惊喜支持高写入吞吐满足第三条硬目标。这一结论并非停留在纸面。在引擎源码中嵌入式 KV 的实现落在universaldb包内其 Cargo.toml 直接依赖工作区的rocksdbcrate第 25 行rocksdb.workspace true与 PostgreSQL 驱动并列共同实现统一的DatabaseDriver抽象见 driver/mod.rs。也就是说“RocksDB 作为嵌入式 KV”的决策在代码层面体现为universaldb下的rocksdb与postgres两个驱动实现二者共享同一套类 FDB 的事务接口。候选方案逐项评估下面完整继承原文档的评估内容并按“为什么被排除/选中”的逻辑展开。RocksDBLSM 树 WAL代价是复杂度RocksDB 使用LSM 树Log-Structured Merge-Tree存储需要后台 compaction并且为了表现得像一个“真正的数据库”而携带不少额外机制。优点提供WALWrite-Ahead Log可用于日志记录与写入缓冲buffering writes——这正是高写吞吐的关键写入先进内存与 WAL批量刷盘而不必每次提交都同步落盘。缺点显著更复杂存在后台线程管理 compaction的开销不提供原生的 MVCC 冲突范围conflict ranges必须自行重新实现——这是文档中唯一一条“需要额外工程量”的缺点也是本文后文源码佐证的重点。LMDB简单但写密集负载下是硬伤LMDB 使用B 树存储通过mmap映射数据库文件。文档明确点出它的关键缺点在写入密集负载下LMDB 的性能会受到两方面的拖累(a) 单写者锁single writer lock(b) 使用 mmap 而非缓冲写入buffering writes。优点实现简单几乎零额外开销。缺点逐条列出注意第 3、5 条与 Rivet 负载特征直接相关没有 compaction单写者锁每次事务提交都要一次 fsync而 RocksDB 可以通过 WAL 批量写入来优化使用页式 mmap比 WAL 批量写入更慢对小写入不友好因为它是按页复制的——而文档特别强调Rivet 的写入几乎全是小写入Actor 的 KV 写入、元数据更新大多是字节级的小 key-value这一点直接判了 LMDB 出局。sled、redb、fjallRust 原生但不够成熟这三个 Rust 原生库各不相同但被排除的首要原因是成熟度sled 自称处于 “beta” 阶段、尚未成熟redb 和 fjall 虽声称拥有稳定的磁盘格式但整体积累不足。文档在此给出了一句极具代表性的选型哲学这不是一个我们需要创新的地方因此更有道理去选一个经过验证的嵌入式 KV。This is not a place we need to innovate, so it makes more sense to go with an established embedded KV.它们本应有的优点也被如实记录Rust 原生——构建与链接building linking的潜在问题更少理论上比 RocksDB 编译更快C 依赖在 Rust 工程中确实是编译时间的常见痛点。SQLite最简单但“我们不需要它的开销”文档指出从支持面看SQLite 是最简单、最直接的选择并给出一个有趣的历史注脚FoundationDB 在 7.0 之前的存储引擎正是基于 SQLite 的 B-tree 实现。但结论是在 RocksDB、LMDB 这类选项存在时SQLite 提供了我们不需要的开销。优点支持面极广构建没有障碍团队已经拥有深厚的 SQLite 使用经验Rivet 中 SQLite 也确实被用于 Actor 的数据库功能见docs-internal下多篇 SQLite 相关文档如 SQLITE_OPTIMIZATIONS.md。也就是说四个候选中LMDB 输在写路径Rust 原生库输在成熟度SQLite 输在多余的 SQL 层开销最终由“稳定 高写吞吐”的 RocksDB 胜出。文档还附了一条参考资料marvin-j97/rust-storage-benchRust 存储引擎基准测试作为各候选性能对比的外部依据。源码佐证一RocksDB 驱动的真实打开选项决策文档只讲了“选谁”而引擎源码展示了“怎么开”。universaldb的 RocksDB 驱动构造时database.rs使用OptimisticTransactionDB打开数据库第 51 行——即 RocksDB 的乐观事务接口事务内读写乐观进行提交时才校验冲突这与“类 FDB”的语义方向一致打开选项逐项设定其中值得注意的取值set_max_open_files(10000)放宽文件描述符上限容纳更多 SST 文件set_keep_log_file_num(10)保留 10 个 WAL 日志文件set_max_total_wal_size(64 * 1024 * 1024)WAL 总量上限 64MiB——这正对应文档中“RocksDB 支持 WAL 用于日志与写入缓冲”的优点落地写入先落 WAL而非像 LMDB 那样每次提交 fsyncset_write_buffer_size(256 * 1024 * 1024)写缓冲区 256MiB源码注释写明其用途是“for conflict detection”服务于冲突检测。此外驱动在析构时调用cancel_all_background_work(true)database.rs主动停掉 compaction 等后台工作——这是对文档所说“后台线程管理 compaction 的开销”这一缺点的工程化管理。源码佐证二自行实现 MVCC 与冲突检测文档对 RocksDB 最重要的批评是“不提供原生 MVCC 冲突范围必须自行实现”。仓库中这笔“债”的偿还位置非常清晰统一的类 FDB 事务接口。DatabaseDriver/TransactionDriver两个 traitdriver/mod.rs定义了get/get_range/set/clear_range/commit/add_conflict_range等操作并带有明确的 FDB 注释例如retry_limit方法注释中直接对标 FoundationDB 的TransactionRetryLimit语义“limit of 0 时首次错误直接返回而不重试”。进程内 FoundationDB 风格冲突解析器。conflict_tracker.rs 中的TransactionConflictTracker被注释为“In-process FoundationDB-style resolver”它保留最近TXN_CONFLICT_TTL10 秒注释说明事务存活不超过 5 秒因此 10 秒足够内已提交事务的写范围冲突是有向的与 FDB 一致只有“我读过的 key 在我开始之后被写”才导致本事务中止盲写对写blind write-vs-write不冲突conflict_tracker.rs 的注释明确说明了这一点版本号由进程内global_version原子计数器生成next_global_version供 RocksDB 单进程驱动同时分配 start/commit 版本Postgres 驱动则由持久化的 leader 序列分配版本以容忍主从切换。重试循环。RocksDB 驱动的run方法database.rs实现了完整的事务重试语义默认最大重试 10 次、指数退避calculate_tx_retry_backoff、maybe_committed状态跟踪防止在“可能已提交”状态上盲目重试、事务超时TXN_TIMEOUT——这些与 FDB 客户端的重试模型逐一对应。可以看到文档中的每一条优缺点评估都能在源码中找到对应物WAL 选项对应“写入缓冲”的优点TransactionConflictTracker对应“必须自行实现 MVCC 冲突范围”的缺点后台工作取消对应 compaction 开销的治理。从源码结构看选型如何服务于 Actor 负载从universaldb的整体结构看RocksDB 驱动承担的是单进程嵌入式场景单机引擎中的状态持久化而 Postgres 驱动则面向多节点部署两者共用同一套DatabaseDriver抽象。可以推断这种“同一事务接口、可替换后端”的设计正是文档第一条目标类 FDB 事务语义带来的红利上层 Actor 代码面对的是统一的事务原语底层既可以用进程内 RocksDB也可以换成外部 Postgres而冲突检测、重试、版本分配的差异被隔离在驱动层。对于读者而言理解本文档最有价值的三点是选型标准先于候选清单三条硬目标嵌入式、类 FDB、高写吞吐 一条软目标压缩每个候选都是按这套标准被逐项对账的负载特征决定胜负手LMDB 的出局不是因为“差”而是因为“与负载不匹配”——Rivet 几乎全是小写入页级复制与每次提交 fsync 恰好踩中短板“不创新”是成熟的工程判断在存储引擎这一层选择久经考验的组件把自研成本投到真正差异化的一层冲突检测与事务语义是这份决策文档最值得借鉴的方法论。延伸阅读仓库内路径决策文档原文EMBEDDED_KV.md统一数据库抽象driver/mod.rsRocksDB 驱动与打开选项driver/rocksdb/database.rs进程内冲突解析器conflict_tracker.rs驱动依赖声明universaldb/Cargo.tomlRocksDB 集成测试tests/rocksdb.rs、冲突语义一致性测试tests/conflict_parity.rs【免费下载链接】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),仅供参考
返回列表