拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Turso 事务正确性深度解析:WAL 机制、检查点、并发控制与崩溃恢复

Turso 事务正确性深度解析WAL 机制、检查点、并发控制与崩溃恢复【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso导读Turso 是一个用 Rust 实现、兼容 SQLite 的嵌入式 SQL 数据库引擎其存储层完全围绕 WALWrite-Ahead Logging预写日志模式构建。理解 WAL 的写入/读取路径、检查点checkpoint策略、并发锁规则与崩溃恢复流程是掌握 Turso 事务正确性Transaction Correctness的关键。本文以仓库内.claude/skills/transaction-correctness/SKILL.md为骨架结合core/storage/wal.rs与core/storage/pager.rs的源码实现系统讲解 Turso 如何保证持久性Durability、原子性Atomicity、隔离性Isolation与无丢失更新No Lost Updates帮助读者从原理到实现完整理解 Turso 的事务存储内核。一、WAL 模式Turso 唯一的日志策略Turso 只使用 WAL 模式SKILL.md开篇即声明 Turso uses WAL (Write-Ahead Logging) mode exclusively这一点与 SQLite 的传统回滚日志rollback journal模式形成鲜明对比。在 WAL 模式下数据库目录下会存在两类文件.db主数据库文件保存已检查点checkpointed的页面数据.db-wal预写日志文件追加写入尚未合并回主库的事务帧。与 SQLite 不同Turso没有.db-shm文件——SQLite 用共享内存文件存放 WAL 索引而 Turso 因为不支持多进程访问multi-process access改为在进程内使用内存数据结构维护 WAL 索引详见下文WAL 索引一节。这一设计取舍直接影响文件布局与并发模型。二、WAL 读写路径追加写入与快照读取写路径Write PathWAL 的写入是顺序追加sequential I/O写者把修改过的页面数据打包成帧frame追加到.db-wal文件末尾事务提交时写入一个带非零db_size头部字段的帧该帧标记事务结束COMMIT frame with non-zero db_size in header在主数据库文件被检查点之前原始.db文件保持不变。这种设计保证了提交记录与数据页写入的天然顺序性——提交帧总是位于事务所有数据帧之后读侧只需扫描到该帧即可确定事务边界。读路径Read Path读者通过**读标记read mark**获得一致的数据库快照读者在开始事务时获取读标记其值为当前 WAL 最后一个有效提交帧号mxFrame读取任意页面时先在 WAL 中从min_frame到mxFrame范围内查找该页的帧WAL 中没有则回退到主数据库文件由于mxFrame在事务期间固定不变读者在整个事务内看到的都是同一个一致性快照不受后续写入影响。源码中core/storage/wal.rs的WalSnapshot结构清晰体现了这一视图它快照了max_frame、nbackfills、last_checksum、checkpoint_seq与transaction_count并通过min_frame()方法计算该快照可见的最小帧号nbackfills 1即检查点回填之后仍保留在 WAL 中的首帧。检查点Checkpointing把 WAL 搬回主库检查点的本质是将 WAL 内容回填backfill回主数据库文件。SKILL.md给出了触发模型WAL grows → checkpoint triggered (default: 1000 pages) → pages copied to DB → WAL reused当 WAL 增长到默认阈值约 1000 页时触发检查点把页面复制回.db文件后复用 WAL。core/storage/wal.rs中WalFile结构体的checkpoint_threshold: usize字段就是这一阈值的落点而CheckpointResult包含wal_max_frame、wal_total_backfilled、wal_checkpoint_backfilled等字段则记录了检查点的进度统计。core/storage/wal.rs中的CheckpointMode枚举定义了四种检查点模式其语义在源码注释中阐述得非常完整模式行为关键特征PASSIVE尽可能回填不等待读者/写者从不阻塞读者和写者仅保证没有其他检查点并发可选upper_bound_inclusive限制回填到指定帧号FULL等待写者结束、所有读者都读到最新快照后回填全部帧并同步主库检查点期间阻塞新写者但新读者不受影响RESTART与 FULL 相同此外等待所有读者只读主库文件保证下一个写者从 WAL 开头重新开始日志TRUNCATE与 RESTART 相同此外把 WAL 文件截断为零字节可选upper_bound_inclusive实现条件截断供 sync-engine 合并 WAL 时确保不丢失帧源码中should_restart_log()区分出 TRUNCATE 与 RESTART 需要重置日志而require_all_backfilled()则表明除 PASSIVE 外所有模式都要求完整回填从nbackfills 1到max_frame。此外TRUNCATE 模式有一个微妙之处即便 WAL 因日志重启而max_frame为 0只要文件中残留旧帧也会清除这些陈旧字节见CheckpointResult::should_truncate()。三、WAL 索引用内存数据结构替代.shm文件SQLite 使用共享内存文件-shm存放 WAL 索引供多进程共享。Turso 不使用.db-shm——由于 Turso 不支持多进程访问同一数据库它在进程内用内存数据结构维护 WAL 索引frame_cache一个FxHashMapu64, Vecu64把页面号映射到它存储在 WAL 中的所有帧号按升序排列便于检查点时快速定位需要回填的帧。源码注释还提到配套的frame_cache_high_water水位线用于检测帧槽复用/追加位置回退避免find_frame返回已被覆盖的陈旧映射。原子读标记WalSharedRuntime::read_locks是长度为 5 的[TursoRwLock; 5]数组每个槽位内嵌帧号值替代 SQLite 的aReadMark[]。WalSharedRuntime的注释直接给出了这一取舍的理由One difference between SQLite and limbo is that we will never support multi process, meaning we dont need WALs index file. So we can do stuff like this without shared memory.limbo 是 Turso 引擎的原始代号核心代码仍保留该命名。值得注意的是core/storage/wal.rs中还保留了host_shared_wal特性开关涉及shared_wal_coordination.rs的映射共享协调结构说明跨进程共享 WAL 协调属于可选实验路径默认的进程内模型依赖上述内存结构。四、并发规则一写多读互不阻塞WAL 模式的并发规则非常简洁也是其高性能的根源同一时刻只有一个写者write_lock: TursoRwLock保证写互斥源码注释明确 There is only one write allowed in WAL mode读者不阻塞写者写者也不阻塞读者——读者只需持有自己的读标记即可继续读取旧快照新帧追加不影响它检查点必须停在活跃读者所需的页面前——检查点器只能回填帧号不超过所有在用读标记的帧避免覆盖读者仍需要从 WAL 读取的页面。read_locks的槽位语义源码 2839-2845 行注释非常值得展开槽位 0 特殊当读者持有槽位 0共享时它完全绕过 WAL、直接读主库文件。检查点器必须获取槽位 0 的排他锁确保没有读者从部分检查点的文件中读取槽位 1–4携带帧号值可被多个读者共享其中槽位 1 是默认读锁存放 WAL 的max_frame。这套设计与 SQLite 的aReadMark[]完全同构——wal.rs中保留了从sqlite3/src/wal.c移植来的大段注释关于nBackfill、aReadMark的语义是理解该机制的第一手资料。核心规则检查点器只能把帧号 ≤ 所有在用aReadMark[j]的帧回填到数据库当nBackfill mxFrame全部回填完成时新读者选择槽位 0直接从主库读取。五、崩溃恢复首个连接重放 WAL进程崩溃后数据库一致性由恢复流程保证SKILL.md第一个连接获取排他锁从 WAL 重放有效提交valid commits——只重放以非零db_size提交帧结尾的事务未完成事务的帧被丢弃释放锁恢复正常操作。源码层面OpenSharedWal枚举的Build变体对应 in-progress recovery scan——它是一个可驱动的状态机通过poll()持续泵送直至返回Done完成对 WAL 的扫描与重建共享状态。WalSharedMetadata中的loaded、loaded_from_disk_scan、initialized三个原子标志正是这一加载过程的阶段标记。恢复完成后WAL 的有效帧重新可用连接进入正常读写模式。恢复的正确性还体现在检查点与写入的配合上CheckpointResult携带maybe_guard: OptionCheckpointLocks保证检查点期间获取的锁CheckpointLocks::Writer或Read0在出错时也能正确释放不会泄漏锁导致恢复或后续操作死锁。六、Turso 实现架构连接私有 vs 全局共享SKILL.md将 Turso 的存储对象分为每连接私有与跨连接共享两组wal.rs与pager.rs的源码结构完全印证了这一划分。Per-Connection连接私有Pagercore/storage/pager.rs页面缓存、脏页集合、savepoint 与提交状态。它负责把 SQL 层的页操作转化为 WAL 帧WalFilecore/storage/wal.rs连接自身的 WAL 快照视图包含max_frame/min_frame——本连接快照可见的帧范围max_frame_read_lock_index——本连接持有的读锁槽位索引last_checksum——滚动校验和状态高 32 位与低 32 位打包进一个原子字免锁读写checkpoint_seq——检查点代数用于回滚时校验位置是否仍有效见RollbackTo::checkpoint_seqongoing_checkpoint: RwLockOngoingCheckpoint——本连接视角的检查点进度。WalFile还持有dirty: ArcAtomicBool标志一旦追加了尚未被成功 fsync 覆盖的帧就置位WAL fsync 成功后清除——这是synchronousFULL下提交被报告为持久durable的前提。Shared跨连接共享WalFileSharedcore/storage/wal.rs进程级 WAL 全局状态分为两部分metadata: WalSharedMetadata——权威 WAL 元数据wal_headerWAL 文件头、min_frame/max_frame全局日志进度、nbackfills已回填帧数、transaction_count、last_checksum、以及loaded等恢复标志runtime: WalSharedRuntime——进程内协调与缓存frame_cache页→帧索引替代.shm、read_locks[5]读标记槽、write_lock写者互斥、checkpoint_lock检查点串行化同一时刻仅一个检查点在途、vacuum_lock原地 VACUUM 期间排斥新事务、epoch每次检查点自增防止陈旧缓存页被用于回填以及 WAL 文件句柄file。DatabaseStorage主.db文件管理BufferPool共享内存分配器所有连接复用同一缓冲池。从职责划分可以看出 Turso 的并发模型写互斥、读共享、检查点串行全部通过TursoRwLock自研读写锁实现读标记嵌入锁值本身避免了 SQLite 那套共享内存索引的复杂协议。七、正确性不变量Correctness InvariantsSKILL.md总结了四条必须始终成立的正确性不变量它们分别对应 ACID 的各个维度Durability持久性COMMIT 记录必须被 fsync 之后才能向调用方返回成功。对应WalFile::dirty标志的语义——帧追加但未 fsync 时 WAL 视为脏提交不可上报为持久Atomicity原子性未完成事务的部分帧永远不会对读者可见。因为读者只扫描到最近的有效提交帧mxFrame其后的半成品帧一律被忽略天然实现原子性Isolation隔离性每个读者在自身读标记上看到一致的快照。快照通过WalSnapshot固化了max_frame等边界配合只增不减的帧号实现No lost updates无丢失更新检查点不能覆盖未提交的更改。检查点器受read_locks槽位约束只能回填帧号 ≤ 所有在用读标记的帧且nbackfills只能由持有checkpoint_lock的线程推进与 SQLitewal.c中nBackfill的规则一致。这四条不变量贯穿读写路径、检查点与恢复的每一个环节也是 Turso 存储层测试core/storage/wal.rs内置大量单元测试以及core/mvcc/下的并发与恢复测试重点验证的对象。八、源码阅读路线图如果你希望进一步深入推荐按以下顺序阅读core/storage/wal.rs本文主题的核心实现约 1 万行。重点关注CheckpointMode枚举第 158–189 行、WalFile第 2695 行起、WalFileShared/WalSharedRuntime/WalSharedMetadata第 2808–2888 行其中 2752–2805 行保留了 SQLitewal.c关于nBackfill与aReadMark的权威注释是理解 WAL 索引协议的捷径core/storage/pager.rs页面管理、脏页跟踪、savepoint 与提交状态理解Pager如何与WalFile协同core/storage/sqlite3_ondisk.rsWAL 文件格式的磁盘布局定义WalHeader、帧头、校验和checksum_wal等对应SKILL.md参考文献中 SQLite 的 WAL File Formatcore/storage/shared_wal_coordination.rs可选的跨进程共享 WAL 协调路径理解默认进程内模型的边界配套概念文档可参考 docs/agent-guides/mvcc.md 与 docs/agent-guides/storage-format.md它们从多版本并发控制与磁盘格式角度与本主题互为补充。结语Turso 的 WAL 实现忠实继承了 SQLite 的经典设计——追加日志、读标记快照、检查点回填、崩溃重放——同时针对单进程、多连接的定位做了大胆简化用内存frame_cache和原子读标记取代共享内存索引文件用TursoRwLock显式管理写锁、读锁与检查点锁。理解连接私有 vs 全局共享的对象划分以及四条正确性不变量如何在读写、检查点、恢复三条路径上被逐条落实是深入 Turso 存储内核最有效的入口。【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门