ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpacetimeDB 旧版本数据兼容性测试:testdata 夹具目录的用途、结构与实现原理

SpacetimeDB 旧版本数据兼容性测试:testdata 夹具目录的用途、结构与实现原理 SpacetimeDB 旧版本数据兼容性测试testdata 夹具目录的用途、结构与实现原理【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读SpacetimeDB 是一个以开发速度如光速为目标的关系型实时数据库其数据以 commitlog提交日志与快照snapshot两种形态持久化到磁盘。随着版本迭代磁盘上的持久化格式、系统表结构都可能发生变化因此数据库必须具备读取旧版本写入的数据的能力。本文以仓库中crates/engine/testdata/目录的说明文档为线索深入解析 SpacetimeDB 如何通过固化旧版本真实数据的测试夹具构建起向后兼容性回归测试体系——读完你将掌握该夹具目录的完整结构、对应单元测试的运行逻辑以及 commitlog 格式版本、BSATN 快照存储等底层实现细节。一、testdata 目录存在的意义让单元测试检查版本兼容性仓库根目录下 crates/engine/testdata/README.md 对该目录的定位只有一句话This has some data written by older versions of spacetimedb, so our unit tests can check compatibility.这里存放着由旧版本 spacetimedb 写入的一些数据以便我们的单元测试能够检查兼容性。这句话概括了测试夹具test fixture的经典用途把某个历史版本运行后真实产生的磁盘数据固化进代码仓库当引擎后续迭代时单元测试直接用这份陈年数据打开数据库验证旧数据可以被当前代码正确读取前向读取兼容旧数据可以被升级/迁移到新结构如新增系统表后仍可读写回归防护——任何破坏旧格式读取能力的代码变更都会在cargo test阶段立即暴露而不是等到用户升级时才发现。换句话说testdata 是 SpacetimeDB 向后兼容承诺的可执行证据。二、夹具目录结构逐层解析crates/engine/testdata/下的实际数据布局如下crates/engine/testdata/ └── v1.2/ └── replicas/ └── 22000001/ # 数据库 replica_id 22000001 ├── clog/ │ └── 00000000000000000000.stdb.ofs # commitlog 日志段offsets 文件 ├── module_logs/ # 模块日志目录当前为空 ├── db.lock # 数据库目录锁 └── snapshots/ └── 00000000000000000000.snapshot_dir/ # 偏移 0 处的快照目录 ├── 00000000000000000000.snapshot_bsatn # BSATN 编码的快照主体 └── objects/ ├── 19/30ce81246a4cdc25e9024ae0065d053adb2efbe1b5b7af457331d330e481e8 ├── 41/bb11b6d2cdc488192ee70d8175307d6f205756ed163f4237c6cba2936798dc ├── 45/4d2e2c62ff5d46c5b3e6de72d6277eb285fc2d6b0a5ac6f92498e08a9e5ecc ├── 62/22df0e5ca93d3fb22762e12161246a1d5917c61ada5d81b8dcce12fd5780b3 ├── 79/4dced5633eca2ffee784d471f5203209169321083ef99de254ad24af0f6d5a ├── 95/74dd6d2857fa771a1cd16be31fdef38f83c2fd3bcc05f4934e53bdbfa21f10 └── 9a/b95f5aaed7541289faa8bc4de886ce0281f11037c3424494e58fee92411241这份布局与 crates/paths/src/lib.rs 中定义的服务器数据目录规范完全一致每个数据库副本以replicas/replica_id组织内部包含clogCommitLog 文件、module_logs模块日志、snapshots数据库快照。对应地crates/paths/src/server.rs 的ServerDataDir::replica(replica_id)与 ReplicaDir 的路径访问器 分别提供了snapshots()与commit_log()等类型安全路径构造方法。各组成部分的含义路径含义clog/00000000000000000000.stdb.ofscommitlog 的日志段文件.ofs后缀记录从事务偏移 0 开始的所有已提交事务snapshots/00000000000000000000.snapshot_dir/在事务偏移 0 处生成的一个快照目录snapshot_dir(tx_offset)的命名规则为{tx_offset:020}.snapshot_dir见 server.rs00000000000000000000.snapshot_bsatn快照主体采用 SpacetimeDB 的 BSATN 二进制序列化格式编码objects/对象存储目录树按哈希前两字节分片存放表中实际数据的不可变对象文件db.lock数据库目录互斥锁防止同一副本被并发打开module_logs/模块业务逻辑运行日志目录当前夹具中为空objects/采用内容寻址content-addressed存储每个数据对象以内容哈希命名如19/30ce8124...前缀两字符作为分片目录形成两级目录树。这种设计让快照天然支持去重与硬链接——下文会看到压缩逻辑如何利用这一点。三、四个兼容性单元测试验证了什么测试数据不是摆设crates/engine/src/relational_db.rs 的测试模块中有 4 个用例直接消费这份 v1.2 夹具核心逻辑收敛在两个函数中。3.1 load_1_2_data读旧数据 建新表load_1_2_data(use_snapshot: bool)的流程源码let data_dir PathBuf::from(env!(CARGO_MANIFEST_DIR)).join(testdata/v1.2/replicas); let tempdir copy_fixture_dir(data_dir); // 先复制到临时目录绝不直接改动仓库内的夹具 let dir ReplicaDir::from_path_unchecked(tempdir.path().join(replicas/22000001)); // ... 使用固定 identity 打开已存在的持久化数据库 ... let (db, _durability_handle) TestDB::open_existing_durable(dir, ...)?; // 1) 断言旧数据表存在 let schemas db.get_all_tables_mut(tx)?; let expected_table_names vec![message, user]; // v1.2 时代的数据表 assert_eq!(user_table_names, expected_table_names); // 2) 在旧数据之上新建一张表并提交 let table_id db.create_table(mut tx, my_table(AlgebraicType::I32))?; db.commit_tx(tx)?; // 3) 断言新表读写正常 assert_eq!(table_id, db.table_id_from_name_mut(tx, MyTable)?.unwrap());关键点env!(CARGO_MANIFEST_DIR)在编译期定位 crate 根目录从而找到 testdata 夹具copy_fixture_dir先把夹具整体复制进临时目录定义在 relational_db.rs#L4023-L4037保证测试对夹具文件本身零污染断言夹具中确实含有message、user两张 v1.2 时代的数据表先确认没有打开空目录再验证旧数据可读、新表可建。3.2 load_1_2_data_and_migrate系统表迁移的完整闭环load_1_2_data_and_migrate(use_snapshot)源码更进一步模拟了一次真实的升级迁移场景This tests adding a new system table st_connection_credentials, which was not in 1.2.打开 v1.2 旧数据再次断言message、user存在通过insert_st_client(Identity::ZERO, ConnectionId::ZERO, invalid_jwt)向1.2 版本中不存在的系统表st_connection_credentials写入数据——触发当前代码自动创建缺失系统表并完成迁移rt.block_on(db.shutdown())关闭并drop(db)后重新打开数据库用get_jwt_payload(ConnectionId::ZERO)读回刚才写入的 JWT 字符串断言jwt invalid_jwt。这个用例验证了迁移的持久性新系统表不仅能在旧数据上被创建而且写入的数据在关闭重开后依然能正确读取。这正是用户从旧版本升级后不会丢身份凭据的关键保证。3.3 测试矩阵4 个测试分别以有无快照两种路径执行测试声明测试函数覆盖场景快照路径load_1_2_quickstart_from_snapshot_test读旧数据 建新表从快照恢复load_1_2_quickstart_without_snapshot_test读旧数据 建新表纯 commitlog 重放load_1_2_data_and_migrate_with_snapshot系统表迁移 重开读回从快照恢复load_1_2_data_and_migrate_without_snapshot系统表迁移 重开读回纯 commitlog 重放use_snapshot布尔值通过TestDB::open_existing_durable(..., want_snapshot_repo)控制signature因此同一个夹具覆盖了两条完全不同的恢复链路快照 增量日志 vs 全量日志重放。这是一份数据两条路径的高性价比测试设计。四、底层原理快照与 commitlog 的格式版本4.1 快照BSATN 序列化 内容寻址对象存储快照的抓取由 crates/engine/src/snapshot.rs 中的SnapshotWorker后台任务负责。它是一个可克隆的句柄通过mpsc无界通道接收Request::TakeSnapshot/Request::ReplaceState消息Request 枚举在 tokio runtime 上用spawn_blocking调用Locking::take_snapshot_internal完成内存状态到磁盘的落盘take_snapshot最后fsync并发布快照对应的事务偏移TxOffset。快照主体00000000000000000000.snapshot_bsatn使用 SpacetimeDB 自研的BSATNBinary Spacetime Algebraic Type Notation格式编码表中数据对象则存进objects/内容寻址目录。SnapshotWorker还支持Compression::Enabled压缩模式对早于最新快照的历史快照执行压缩统计跳过数、压缩对象数、硬链接对象数CompressionMetrics——硬链接正是利用内容寻址哈希去重让多个快照共享同一份对象文件从而节省磁盘。4.2 commitlog带版本号的日志格式与快照平行的持久化通道是 commitlog。日志段文件.stdb.ofs在解码事务时会读取log_format_version字段crates/commitlog/src/commit.rs 中的逻辑表明格式版本0对应Version::V0其余版本走Version::V1解码路径。该版本号可通过服务器配置覆盖crates/engine/src/persistence.rs 的CommitlogConfig暴露了log_format_version、max_segment_size、offset-index-interval-bytes、preallocate-segments、write-buffer-size等 kebab-case 配置项。这解释了兼容性测试的意义同一份 commitlog 可能由不同格式版本的引擎写入解码端必须按段内自带的版本信息选择正确的解析逻辑。4.3 持久化装配LocalPersistenceProvider生产环境中crates/engine/src/persistence.rs 的LocalPersistenceProvider会把上面两条链路装配起来为每个副本打开快照仓库并创建启用了压缩的SnapshotWorker构建本地Durabilitycommitlog再后台运行快照驱动的 commitlog 压缩器——每当新快照产生就压缩对应的旧日志段。测试夹具中同时出现的.snapshot_bsatn与.stdb.ofs文件正是这套装配的磁盘产物。五、实战指引如何运行与扩展这些测试5.1 运行兼容性测试在仓库根目录执行需要 Rust 工具链工具链版本见根目录 rust-toolchain.toml# 只运行 v1.2 兼容性相关测试 cargo test -p spacetimedb-engine load_1_2 # 运行 engine crate 全部测试 cargo test -p spacetimedb-engine由于夹具会先复制到临时目录测试可重复执行且不会污染crates/engine/testdata/中的原始数据。5.2 为新版本添加夹具基于现有模式的推断从当前仓库的约定可以推断维护者为新版本如 v1.3添加兼容性夹具时遵循的流程会是用对应旧版本引擎实际创建并写入一个数据库含message、user等代表性业务表正常关闭确保 commitlog 与快照落盘将整个replicas/id目录固化为testdata/vX.Y/replicas/在 relational_db.rs 测试模块中仿照load_1_2_data新增读取与迁移断言。这种真实数据固化 双重打开验证的模式保证了每一条持久化格式变更都有对应的历史回归用例。六、结语crates/engine/testdata/虽然只有一行说明却承载着 SpacetimeDB 最核心的工程承诺——向前兼容。通过固化 v1.2 时代的真实磁盘数据commitlog 日志段、BSATN 快照、内容寻址对象存储配合四个覆盖快照/日志重放 × 读取/迁移矩阵的单元测试任何破坏旧格式读取或系统表迁移的改动都无法通过 CI。对于数据库类项目这套以历史真实数据为测试基准的方法论本身就值得借鉴。延伸阅读测试实现crates/engine/src/relational_db.rs#L4039-L4174数据目录布局规范crates/paths/src/lib.rs#L129-L154快照后台任务crates/engine/src/snapshot.rs持久化配置含 commitlog 格式版本crates/engine/src/persistence.rscommitlog 解码与格式版本crates/commitlog/src/commit.rs#L302-L314夹具数据本体crates/engine/testdata/v1.2/replicas/22000001【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进