免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Rivet Depot:可分支 SQLite 存储的 PITR 设计与 Neon、Durable Objects、Snowflake 等系统对比

Rivet Depot:可分支 SQLite 存储的 PITR 设计与 Neon、Durable Objects、Snowflake 等系统对比 Rivet Depot可分支 SQLite 存储的 PITR 设计与 Neon、Durable Objects、Snowflake 等系统对比【免费下载链接】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 引擎内部设计文档 SQLite PITR Comparison To Other Systems系统梳理 Rivet 自研 SQLite 存储层Depot在点-in-time 恢复PITR 数据库分支fork问题上的设计取舍它从 Neon、Cloudflare Durable Objects、Snowflake、LiteFS、Litestream、mvSQLite、Turso/libSQL 七类相邻系统中各借了什么、在哪些地方刻意偏离、以及为什么偏离。读完后你能理解为什么回滚rollback不在存储层实现这一核心决策背后的完整论证链并能结合源码定位fork_database/fork_bucket的真实实现。一、先看懂前提Rivet 存储的四个硬约束对比任何外部系统之前原文开篇就点明了本设计与它们的根本差异所在——不是谁的功能更多而是约束条件不同。Rivet 的约束是单写者数据库所有权single-writer database ownership由 Pegboard 的排他性机制保证每个数据库同时只有一个写入者存储层因此不需要实现多写者冲突解决没有本地 SQLite 文件no local SQLite files持久化状态全部落在 FoundationDBFDB中本地文件会让存储变得有状态、不可迁移FDB 作为热层hot tier与唯一可信数据源S3 等对象存储承担冷分层存储层暴露的是 fork 原语而不是存储级 rollback。这四点约束在 约束与设计决策文档 中被列为 Binding Constraints并补充了两条关键决策分支branch不可变——bucket id 就是 bucket 分支 id、database id 就是 database 分支 id回滚由引擎层engine负责——存储只暴露 fork 原语由引擎决定某个 database 当前映射到哪个 database id。存储结构的键布局详见 SQLite Storage Structure组件职责划分见 SQLite Storage Components。理解这四个约束是读懂后文所有偏离的前提每个偏离都是约束的必然推论而不是随意的功能取舍。二、逐系统对比借了什么、偏在哪、为什么原文的核心是一张七行对比表System / What We Share / What We Diverge On / Why下表完整继承其内容并译为中文。系统借鉴之处What We Share偏离之处What We Diverge On偏离原因WhyNeon分层模型image 层 delta 层、分支、依赖图 GC默认提供粗略 PITRrough PITR而非处处精确 PITRFDB 是热的可信数据源而非 pageserver分支记录不可变由引用计数/pin GC 回收精确 PITR 对 Postgres 工作负载有价值但对这类数据库工作负载作为默认项太昂贵FDB 已经承担了热持久层角色Cloudflare Durable Objects SQLite类似 RestorePoint 的时间令牌思想以及快照可以从日志状态构建的思想Durable Objects 依赖 follower quorum且不暴露 fork 原语本设计用 FDB S3并暴露fork_database与fork_bucketFDB 替代了多副本 WAL quorumfork 与 bucket 克隆在这里是一等目标SnowflakeTime travel 与仅靠元数据实现零拷贝克隆的思路Snowflake 是面向 OLAP/表模型的本存储层是每个 SQLite 数据库粒度向引擎暴露更低层级的原语仅元数据的克隆思想可以沿用但身份的单位是数据库分支database branch而不是仓库/表抽象LiteFSLTX 文件格式、高水位high-water-markpending 标记LiteFS 使用本地 SQLite 文件 WAL 复制本设计禁止本地数据库文件PITR 围绕分支构建无状态数据库托管不能依赖本地文件可分支存储需要的是图式保留graph retention而不只是副本追赶LitestreamLTX 风格的增量备份、滚动 post-apply 校验和、S3 保留策略Litestream 只备份单个 SQLite 数据库流它没有分支图、没有 bucket fork、没有热 FDB 层Litestream 回答的是这个数据库能否恢复本设计回答的是这个数据库或 bucket 能否被廉价地 forkmvSQLite把 versionstamp 意识作为一个概念mvSQLite 的多写者 PLCC/DLCC/MPC 机制与内容寻址去重被刻意不采用Pegboard 已保证每库单写者多写者冲突机制只会增加成本而不带来正确性收益Turso / libSQL面向用户的时点 fork / 分支原语Turso 用本地 SQLite 文件 复制并把 rollback 当作存储操作本设计把 rollback 推到引擎层只暴露 fork / delete / restore_point 原语把 rollback 移出存储层可以消除可变指针交换mutable pointer swaps、指针历史、冻结状态frozen states、以及 commit 与 rollback 之间的竞态从这张表可以提炼出三条贯穿全文的设计主线Fork 是一等公民rollback 是引擎层的事——对比对象里最接近的竞品Durable Objects、Turso、Neon都把回滚语义做进了存储Rivet 反其道而行原因正是表中 Turso 一行写明的指针交换带来的历史、冻结态、竞态问题全部被甩出存储层无本地文件直接排除了 LiteFS/Turso 整条技术路线本地文件 WAL 复制把副本追赶式保留替换为分支图 pin 的图式保留单写者让 mvSQLite 的多写者正确性机制失去存在意义versionstamp 只保留为一种时间定位概念而不是多版本冲突仲裁工具。三、回滚所有权存储层的唯一简单规则原文单独用一节讲Rollback Ownership这是全文最核心的主张Cloudflare Durable Objects、Turso、Neon 都在存储里暴露回滚语义而 Rivet 存储不暴露。因为引擎拥有数据库生命周期和database → 当前 database的映射回滚被实现为四个步骤解析一个 restore_point 或 AS-OF versionstamp调用fork_database把引擎层的数据库映射指向新的 database id对该 database 重启或重新连接。做完这四步存储层只需要守一条简单规则分支 id 终身不可变branch ids are immutable for life。这个规则在约束文档里被展开为四条存储不变量见 Why Immutable Branch Ids 一节一个DatabaseId之后永远不会指向另一个分支一个BucketId之后永远不会指向另一个分支分支记录一次写入之后只能被引用计数/pin GC 移除存储因此不需要指针历史审计日志也不需要回滚缓存失效机制。可以推断这正是对比表中反复出现的消除 mutable pointer swaps的落地形式早期基于指针DBPTR 换行的设计需要维护指针历史与冻结分支状态v4 模型把外部 id 直接等同于分支 id 后引擎层回滚就退化为fork 新分支 改引擎映射两个无竞态动作。Depot crash course 也印证了这一点Fork 和 restore 使用同一个原语解析快照选择器、在该精确点派生分支然后由调用方决定是保留 fork 还是移动数据库指针。四、源码印证fork_database与fork_bucket长什么样对比表中最激进的声明是暴露fork_database和fork_bucket。这两个原语在 Depot 的 conveyer 路径中真实存在实现于 fork.rs。fork_database的函数签名为fork_database(udb, source_bucket, source_database_id, target, target_bucket)其中target可以是已解析的 versionstampResolvedVersionstamp或快照选择器SnapshotSelector如按时间戳/restore point 解析。它在一个 FDB 事务depot_branch_fork内完成解析源/目标 bucket 分支并按 target 解析出源 database 分支与目标 versionstamp调用derive_branch_at派生新分支写入指向新分支的数据库指针DBPTR ... /cur与 bucket 目录标记BUCKET_CATALOG返回新生成的fork-{uuid}形式的 database id。derive_branch_at同文件 L194 起体现了fork 是纯元数据操作的论断——它不拷贝任何数据页只做四件事校验fork_depth未超过MAX_FORK_DEPTH链式分支有深度上限校验目标 versionstamp 未被保留窗口淘汰若 restore point pin 或源分支 GC pin 高于目标 versionstamp直接返回ForkOutOfRetention通过VTXversionstamp → txid 反查索引定位目标点并读取对应COMMITS行把head_txid、db_size_pages、post_apply_checksum快照为 fork 分支的META/head_at_fork首个本地提交前的冻结头部写入新的DatabaseBranchRecordparent指向源分支、parent_versionstamp记录 fork 点、fork_depth 1。数据本身则靠懒读在读取时按需从 FDB 水合lazy reads读路径先走 PIDX/DELTA未命中再落到不超过读 txid 的最新 SHARD 版本见 storage-structure.md 的热数据键布局。这正对应 LiteFS/Turso 一行的差异说明——无本地文件意味着 fork 之后不存在追赶本地 WAL的问题分支保留走的是图式 GC 而非副本复制。fork_bucketL147-L192则是更纯粹的元数据操作生成新的BucketIdbucket id 即 bucket 分支 id派生 bucket 分支并写入指针行完全不拷贝目录条目——目录继承通过BUCKET_CATALOG的父链行走实现约束文档称之为 lazy bucket catalog inheritance使 fork 耗时与 bucket 大小无关。测试用例为这些原语提供了行为级证据tests/fork_database.rs、tests/fork_bucket.rs按时间戳选择器 fork 时使用解析出的快照fork_database_from_timestamp_selector_uses_resolved_snapshotL109按 restore point 选择器 fork 时保留对应快照L161fork 后立即重开、与父分支后续写入隔离fork_database_immediate_reopen_isolated_from_parent_later_writesL196过期时间戳被拒绝且不产生目标写入L258restore point pin 竞争时返回ForkOutOfRetentionL293分支深度允许 16 层、拒绝第 17 层L355bucket 侧同样有 depth 16/17 测试。五、Rough PITR 与 Restore Point为什么默认粗略、精确可选对比表中 Neon 一行rough PITR by default instead of exact PITR everywhere与 Durable Objects 一行RestorePoint-like time tokens共同指向本设计的 PITR 策略约束文档给出了其完整表述默认路径rough PITR不为每个 commit 写完整 image而是保留足够的历史以支持在某个位置分支/恢复代价低精确路径opt-in restore points显式创建的 restore point 会写 FDB 历史 pin工作流压缩workflow compaction必须保住这些 pin 对应的历史。这个取舍在 Depot 压缩流程文档 中有对应机制PITR 默认关闭sqlite.pitr缺省不存在启用后热压缩会为保留点写PITR_INTERVAL覆盖行未过期的覆盖行构成软 pin直到 reclaim 阶段 compare-clear 掉它们restore point 则是硬 pinDB_PIN(kindRestorePoint)删除时才移除并重算分支 pin 下限。depot.md 的 PITR and restore 一节同样写明创建 restore point 会把SnapshotSelector解析为精确的分支、txid、versionstamp 与墙钟元数据再写RestorePointRecord与硬 pin。从这套机制可以推断出与对比结论的一致性精确恢复点不是免费的处处可用而是按需付费的显式购买——这正是exact PITR is valuable for Postgres workloads but too expensive as the default一句在实现层面的落点。六、小结一张对比表背后的统一答案回到原文的立论这套 PITR/fork 设计借鉴了相邻系统中被验证过的思想但约束不同。把七行对比与源码证据合起来看Rivet Depot 的统一答案可以概括为借 Neon 的分层与分支 GC 思想但把 pageserver 换成本就存在的 FDB把处处精确降级为默认粗略 显式精确借 Durable Objects 的时间令牌思想但用 FDB 事务替代 follower quorum并补上 Durable Objects 没有的一等原语fork_database/fork_bucket继承 Snowflake 的元数据克隆把身份单位从表/仓库换成SQLite 数据库分支吸收 LiteFS/Litestream 的 LTX 格式、增量与校验和实践但整体从本地文件 副本复制/备份流迁移到无本地文件 分支图保留拒绝 mvSQLite 的多写者机制与 Turso 的存储级 rollback把单写者交给 Pegboard 保证、把回滚交给引擎的fork 改映射让存储层只剩下分支 id 终身不可变这一条简单不变量。对希望在自研托管平台里做 SQLite 多租户、分支或时点恢复的读者而言这份对比的价值在于它把每个候选方案能抄什么、必须不抄什么、以及为什么都钉在了明确的约束上且每一条论断都能在 engine/packages/depot 的源码与测试中找到落点而不只是停留在设计文档层面。【免费下载链接】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),仅供参考
返回列表