:从 RFC 到 `cargo sqlness compat` 的跨版本验证实践)
GreptimeDB 兼容性测试框架Compatibility Test Framework从 RFC 到cargo sqlness compat的跨版本验证实践【免费下载链接】greptimedbThe open-source observability database. One columnar engine for metrics, logs, and traces, on object storage.项目地址: https://gitcode.com/GitHub_Trending/gr/greptimedb导读本文围绕 GreptimeDB 的兼容性测试框架 RFC 展开讲解如何系统化验证不同 GreptimeDB 版本之间的向后兼容与向前兼容能力。你将掌握兼容性测试用例的组织规范feature / verify / cleanup 三段式、since/till/IGNORE_RESULT/TEMPLATE等 sqlness 拦截器语义、基于cargo sqlness-runner compat的实际运行方法以及仓库中 28 个落地用例与 CI 版本窗口机制。背景为什么 GreptimeDB 需要一套兼容性测试框架GreptimeDB 是一个开源的观测性数据库observability database以单套列式存储引擎统一承载 metrics、logs 和 traces数据落在对象存储object storage之上。数据库的存储格式、元数据结构、WAL 布局会随版本持续演进因此“旧版本写入的数据能否被新版本安全打开、新版本写入的数据能否被旧版本重新读取”就成为发布流程中的关键质量关卡。在框架出现之前GreptimeDB 的兼容性保障依赖手工与临时脚本ad-hoc cases每次发版都要由 release manager 人工测试不同版本组合耗时且容易遗漏缺少发布 SoPStandard Operating Procedure中关于兼容性测试的详细指南历史上曾多次在大版本发布后立刻发布的补丁版本如v0.14.1、v0.15.1中暴露兼容性问题说明人工把关并不可靠。RFC 2025-07-04-compatibility-test-framework.md 的动机部分明确记录了这些痛点并据此提出一套易维护、易扩展、易运行的框架给定任意两个 GreptimeDB 版本既能回答向后兼容backward也能回答向前兼容forward问题。框架总体设计测试用例 专用 runnerRFC 将框架分为两个组成部分测试用例Test Cases专为兼容性测试维护的一组用例仍然沿用 GreptimeDB 集成测试生态中熟悉的.sql.result格式测试框架Test Framework一个新的 sqlness runner在既有 sqlness 基础设施之上增加集成测试不需要的新能力since/till、IGNORE_RESULT、TEMPLATE以及自动拉取版本二进制的能力。该设计基于 Sqlness 库但使用方式与普通集成测试不同兼容性测试的核心不是校验精确的输出值而是验证“旧版本能建的表、写的状态新版本能否接管并正确运行”。测试用例组织1.feature / 2.verify / 3.cleanup 三段式RFC 将用例集划分为三个按顺序执行的阶段以树形目录组织compatibility_test/ ├── 1.feature/ │ ├── feature-a/ │ ├── feature-b/ │ └── feature-c/ ├── 2.verify/ │ ├── verify-metadata/ │ ├── verify-data/ │ └── verify-schema/ └── 3.cleanup/ ├── cleanup-a/ ├── cleanup-b/ └── cleanup-c/三个阶段各司其职1.feature使用新功能在“旧版本”上使用新版本才有的特性制造带特定状态的数据与元数据2.verify验证数据库行为在“新版本”上对旧版本产生的状态进行查询验证确保行为正确3.cleanup清理环境与1.feature配对清理测试环境。这三个阶段必须严格按1.feature→2.verify→3.cleanup的顺序执行。RFC 中的示例索引选项特性RFC 以新增索引选项特性对应 GreptimeTeam/greptimedb 的 PR #6416为例给出完整用例写法1.feature阶段在旧版本上创建带新索引选项的表使用-- SQLNESS ARG since0.15.0标注特性自v0.15.0起可用并用-- SQLNESS IGNORE_RESULT声明不关心执行结果、只要求执行成功-- path: compatibility_test/1.feature/index-option/granularity_and_false_positive_rate.sql -- SQLNESS ARG since0.15.0 -- SQLNESS IGNORE_RESULT CREATE TABLE granularity_and_false_positive_rate (ts timestamp time index, val double) with (index.granularity 8192, index.false_positive_rate 0.01);3.cleanup阶段在验证完成后删除该表-- path: compatibility_test/3.cleanup/index-option/granularity_and_false_positive_rate.sql drop table granularity_and_false_positive_rate;由于该特性不需要特殊的验证逻辑2.verify阶段直接复用既有的通用验证用例。例如verify-metadata中的SHOW CREATE TABLE模板用例结合-- SQLNESS TEMPLATE TABLESHOW TABLES拦截器可对运行时发现的每一张表执行元数据验证-- path: compatibility_test/2.verify/verify-metadata/show-create-table.sql -- SQLNESS TEMPLATE TABLESHOW TABLES; SHOW CREATE TABLE $TABLE;维护策略把成本压给特性实现者RFC 提出的维护策略非常关键每次实现一个新特性若该特性需要被兼容性测试覆盖实现者必须为它在1.feature/和3.cleanup/各写一个用例并检查2.verify/中是否有可复用的现有用例。这相当于模拟一位“热情用户”在第一时间使用全部新特性——把维护负担分摊到每个特性的实现者身上为行为“定格”fixation。未来一旦出现破坏性变更框架会自动检测到而不需要 release manager 凭经验排查。这种设计使框架可以持续演进而不是一次性工程。废弃特性标记since/till版本窗口如果某个特性被废弃需要在用例中同步标记。RFC 以index.granularity和index.false_positive_rate两个索引选项为例假设它们将在v0.99.0被废弃则用例标注为-- SQLNESS ARG since0.15.0 till0.99.0 ...这告诉框架该特性只在v0.15.0含到v0.99.0不含之间的版本参与测试。对于 GreptimeDB 中大量计划未来废弃的实验性特性这是一种低成本、声明式的管理方式。框架新增的 sqlness 拦截器RFC 的第二部分定义了 runner 需要的新能力均为 sqlness 拦截器interceptor层面的扩展SQLNESS ARG sinceVERSION_STRING [tillVERSION_STRING]沿袭 sqlness 的ARG拦截器用注释声明一个特性在两个版本之间可用。since必填、till可选-- SQLNESS ARG sinceVERSION_STRING [tillVERSION_STRING]runner 依据该声明决定用例在当前版本组合下是否应被跳过例如till之后的新版本不再执行该特性用例。IGNORE_RESULT只验证执行成功IGNORE_RESULT是新增拦截器告诉 runner 忽略查询结果只检查查询是否成功执行。这与集成 sqlness 测试有本质区别兼容性测试在大多数场景下并不关心查询返回的具体值只关心“旧版本创建的状态能否被新版本正常操作”。这个设计大幅降低了用例的维护成本——实现者无需为新特性编写并持续维护精确的结果快照。TEMPLATE基于运行时数据生成查询TEMPLATE是另一个新增拦截器可从模板结合运行时数据动态生成查询。上面的SHOW CREATE TABLE $TABLE即典型场景需要对新版本上现存的所有表执行SHOW CREATE TABLE但表清单是运行时才确定的无法静态写死在用例里。TEMPLATE拦截器sqlness 中对应sqlness::interceptor::template在 tests/runner/src/cmd/compat.rs 中通过TEMPLATE_DELIMITER接入允许先用SHOW TABLES获取表清单再逐表展开模板生成验证语句。Runner 的额外要求与执行流程RFC 对 runner 本身提出三点要求顺序执行先跑1.feature/再跑2.verify/最后跑3.cleanup/自动拉取版本能够自动获取所需版本的二进制完成测试正确处理since/till根据版本声明过滤用例。其中1.feature阶段需要识别出所有需要测试的特性并按版本号标注随后 runner 使用新版本to版本重启再执行2.verify/和3.cleanup/阶段。这正是从源码结构看 compat.rs 中CompatCommand的实现思路它先启动 “from” 集群执行 setup SQL再在保留状态preserved state上用 “to” 版本重启集群最后执行 verify SQL 并与verify.result对比。RFC 设想的运行方式./sqlness run --from... --to...RFC 给出了最小化的命令行设想例如发布v0.16.0时检查v0.15.0到v0.16.0的向后兼容# check backward compatibility between v0.15.0 and v0.16.0 when releasing v0.16.0 ./sqlness run --from0.15.0 --to0.16.0 # check forward compatibility when downgrading from v0.15.0 to v0.13.0 ./sqlness run --from0.15.0 --to0.13.0同时提出两个配套实践用脚本对给定版本区间内的所有版本组合跑一遍兼容性测试快速生成全量兼容性报告仓库Cargo.toml中的版本号始终提前 bump 到下一个大版本使“下一个未发布版本”可作为本地测试等场景中的 “latest” 版本使用。仓库落地tests/compatibility与cargo sqlness-runner compatRFC 提出后已在仓库中落地实现核心位置包括用例目录tests/compatibility/cases/当前包含 28 个用例目录runner 实现tests/runner/src/cmd/compat.rs、tests/runner/src/cmd/compat_case.rs旧版 datanode 配置 overlay 处理tests/runner/src/cmd/datanode_overlay.rsCI 版本窗口配置tests/compatibility/ci.tomlCI 侧驱动脚本.github/scripts/run-compat.py 与 .github/scripts/update-compat-versions.py配套说明tests/compatibility/README.md 与 tests/compatibility/AGENTS.md。兼容性测试的定位是验证一个 GreptimeDB 版本能否在另一个版本写入的状态上重启。命令入口为cargo sqlness compat复用 sqlness-runner 基础设施。常用命令来自 tests/compatibility/README.md 的 Quick Start# 自兼容冒烟测试仅当前二进制 cargo run -p sqlness-runner -- compat # 从某个已发布版本测试到当前版本 cargo run -p sqlness-runner -- compat --from-version v0.9.5 # 在两个本地二进制目录之间测试 cargo run -p sqlness-runner -- compat --from-bins-dir ./bins/old --to-bins-dir ./bins/new # 从当前构建降级测试到已发布二进制 cargo run -p sqlness-runner -- compat --from-bins-dir ./bins/current --to-version v1.1.4 # 以 standalone 拓扑运行单个兼容性用例 cargo run -p sqlness-runner -- compat --topology standalone --test-filter downgrade_compatibility # 运行指定用例 cargo run -p sqlness-runner -- compat --test-filter basic_table # 预览将运行的用例不启动任何服务 cargo run -p sqlness-runner -- compat --dry-run --from-version v0.9.5 # 查看全部选项 cargo run -p sqlness-runner -- compat --help前置条件Docker用于 etcd分布式拓扑的 PR1 版本始终使用 Docker 启动 etcd 作为元数据存储外部元数据存储是未来工作from 二进制二选一——--from-version version自动拉取发布版或--from-bins-dir path使用本地构建greptime可执行文件必须直接位于给定目录下to 二进制默认使用当前 debug 构建target/debug/greptime可用--to-bins-dir path覆盖或用--to-version version拉取发布版自定义 target-dir若设置了非默认CARGO_TARGET_DIRdebug 二进制不在target/debug/greptime应显式通过--from-bins-dir/--to-bins-dir指向自定义 target 目录或者不使用自定义 target-dir 直接cargo build -p greptime。用例格式case.toml setup.sql verify.sql verify.result每个兼容性用例是 tests/compatibility/cases/ 下的一个目录包含四个文件my_case/ case.toml # 元数据必填 setup.sql # 在 from 版本上执行的 SQL必填 verify.sql # 在 to 版本上执行的 SQL必填 verify.result # verify.sql 的期望输出以basic_table用例为例case.tomlname basic_table reason Verify basic table create/insert/alter/select compatibility across versions. introduced_by PR1 MVP topologies [distributed] from_range [*] to_range [*] features [table] owner team必填字段包括name、reason、introduced_by、topologies、from_range、to_range、features、owner。可选字段namespace用于显式指定数据库命名空间默认取目录名的清洗结果。其 setup.sql 在 from 版本上建表、插入、加列、再插入构造一个带 schema 变更的历史状态CREATE TABLE foo(ts TIMESTAMP TIME INDEX, s STRING PRIMARY KEY, i INT); INSERT INTO foo VALUES (2024-02-02 01:00:000800, my_tag_1, 1), (2024-02-02 02:00:000800, my_tag_2, 2), (2024-02-02 03:00:000800, my_tag_3, 3); ALTER TABLE foo ADD COLUMN f FLOAT; INSERT INTO foo VALUES (2024-02-02 04:00:000800, my_tag_4, 4, 4.4), (2024-02-02 05:00:000800, my_tag_5, 5, 5.5), (2024-02-02 06:00:000800, my_tag_6, 6, 6.6);其 verify.sql 在 to 版本上查询并比对结果SELECT ts, i, s, f FROM foo ORDER BY ts;对应 verify.result 以 sqlness 快照风格给出期望输出SELECT ts, i, s, f FROM foo ORDER BY ts; --------------------------------------- | ts | i | s | f | --------------------------------------- | 2024-02-01T17:00:00 | 1 | my_tag_1 | | | 2024-02-01T18:00:00 | 2 | my_tag_2 | | | 2024-02-01T19:00:00 | 3 | my_tag_3 | | | 2024-02-01T20:00:00 | 4 | my_tag_4 | 4.4 | | 2024-02-01T21:00:00 | 5 | my_tag_5 | 5.5 | | 2024-02-01T22:00:00 | 6 | my_tag_6 | 6.6 | ---------------------------------------注意如果verify.result缺失runner 会根据实际输出生成该文件并判定失败——作者必须人工审查、提交生成的文件后重跑如果实际输出与期望不一致runner 会用实际输出更新verify.result并失败同样需要人工核对差异可参考 AGENTS.md 的说明。阶段语义setup.sqlsetup 阶段from 版本在 from 版本集群上执行语句以分号结尾普通注释用--前缀-- SQLNESS ...拦截器注释遵循普通 sqlness 语义。setup 只需成功任何错误都判用例失败输出不与任何结果文件比对verify.sqlverify 阶段to 版本在 to 版本集群上执行输出与verify.result以 sqlness 快照风格比对。从实现看compat.rs 中维护了successful_setup_indexes等状态无 fail-fast 时只验证 setup 成功的用例fail-fast 时在停止前清理当前 profile。版本区间过滤from_range / to_rangefrom_range和to_range决定用例适用于哪些二进制版本组合表项含义*匹配任意版本包括未知版本vX.Y.Z或vX.Y.Z精确匹配 X.Y.ZvX.Y.Z匹配 X.Y.Z 及之后vX.Y.Z匹配严格晚于 X.Y.Z 的版本vX.Y.Z匹配 X.Y.Z 及之前vX.Y.Z匹配严格早于 X.Y.Z 的版本区间列表按OR语义任一条目匹配即命中。实现位于 compat_case.rs 的parse_version_constraint/version_matches_range先解析、、、、、前缀无操作符时视为精确匹配。版本推断采用 best-effort 策略--from-version直接使用--from-bins-dir/--to-bins-dir或默认 debug 构建通过运行binary --version推断版本try_infer_version当版本无法确定如二进制缺失或--version失败时非通配符区间会被跳过并给出提示*通配符区间仍然匹配。一个典型例子是legacy_jsonb用例case.toml对应 PR #8323from_range [v1.1.0] to_range [v1.1.1]该用例仅在旧二进制 ≤ v1.1.0、新二进制 ≥ v1.1.1 时运行用于验证旧二进制写入的 legacy JSONB 数据能被新二进制读取且不进入 JSON2 结构化对齐路径。旧版本 datanode 配置 overlay部分用例需要在旧阶段给 datanode 叠加配置通过case.toml中的严格可选表声明[old_config] datanode old-datanode.overlay.toml规则要点只要存在[old_config]就必须提供datanode空表和未知键会被拒绝引用路径相对于用例目录且必须限定在该目录内叠加文件是原生 datanode TOMLrunner 在启动服务或创建状态前加载并预检preflight合并规则仅当两侧值都是表时递归合并标量、类型不匹配、数组、表数组则原子替换基线值region_engine无特殊合并行为runner 拥有的字段不允许被 overlay 修改mode、node_id、storage.data_home、meta_client_options.metasrv_addrs、wal.provider以及 Raft WAL 的wal.dir或 Kafka WAL 的wal.broker_endpointsrunner 会将这些字段恢复为基线值基线无值时删除并对受保护字段的覆盖给出不显示值的告警。命名空间隔离与批量行为每个用例运行在自己的数据库命名空间中避免相互干扰默认命名空间由用例目录名清洗得到[a-z][a-z0-9_]*可用case.toml的namespace覆盖重复命名空间在发现阶段版本过滤之前即被拒绝每条语句前runner 会执行一段不写入verify.result的 prelude通过 gRPC 执行CREATE DATABASE IF NOT EXISTS ns然后对 gRPC/MySQL 语句执行USE ns对 PostgreSQL 语句执行SET search_path TO ns。批量行为方面基线无 overlayprofile 先运行datanode TOML 语义等价的用例共享一个 profileprofile 串行、隔离执行每个 profile 有独立的状态与 etcd 生命周期用例串行执行PR1 无并行--dry-run只展示选中的 profiles、用例与 overlay 路径不打印配置值、不启动服务。拓扑与 PR1 限制分布式拓扑compat runner 启动 1 个 metasrv 3 个 datanode 1 个 frontend 1 个 flownodestandalone 兼容性测试无需外部元数据存储sqlness 拦截器-- SQLNESS ...注释按语句使用与普通 sqlness runner 相同的拦截器注册表含 GreptimeDB 的PROTOCOL拦截器对PROTOCOL POSTGRES命名空间 prelude 使用SET search_path而非USE。应避免以pg_开头的未限定 PostgreSQL 协议表名——当前 PostgreSQL 兼容解析器会将其改写为pg_catalog.table无注释式 compat 配置compat runner 不在 SQL 注释中定义额外兼容性配置sqlness 注释保持其普通含义。xfail 策略未来PR1 阶段所有用例预期通过未来 PR 将加入xfail支持要求必填issue与expiry字段。CI 集成滑动版本窗口与降级验证CI 通过 tests/compatibility/ci.toml 控制一个较小的滑动版本窗口from_versions [v1.1.4, v1.2.0] # Recent releases that must be able to reopen tables written by the PR build. downgrade_to_versions [v1.1.4]设计原则PR 窗口保持“最新两个稳定 minor 线的最新 patch”目标是尽早发现“从近期发布版升级到最新构建”的兼容问题而不是在每个 PR 上重测全部历史版本窗口只决定采样哪些旧二进制每个版本组合具体跑哪些用例仍由用例级from_range/to_range决定更宽的历史窗口属于 nightly 或 release-validation 工作流downgrade_to_versions可选列出在 PR 构建集群之后需要重启验证的发布版这些运行只在 distributed 与 standalone 两种拓扑下选择downgrade_compatibility用例用例级精确锚点vX.Y.Z不保留在 PR 窗口中--check-anchors校验它们是已发布 tag--nightly-window在 nightly 调度中执行它们GitHub Actions 工作流保持“薄壳”把窗口加载与 compat 调用委托给 .github/scripts/run-compat.py发布 tag 落地后运行python .github/scripts/update-compat-versions.py --update --published-only刷新窗口。降级兼容用例一个端到端参考downgrade_compatibility用例case.toml是“向前兼容/降级”方向的代表name downgrade_compatibility reason Verify v1.1.4 can reopen tables whose region WAL options or byte-stream-split (BSS) float SSTs were written by the current binary, and can read all rows of an append-only table flushed and compacted with preserve_row_sequence enabled. introduced_by fix: preserve legacy region WAL options format; preserve_row_sequence topologies [distributed, standalone] from_range [v1.2.0] to_range [v1.1.4] features [table, wal, downgrade, append, preserve_row_sequence, sst, float, byte_stream_split] owner metasrv它验证当前二进制≥ v1.2.0写入的 region WAL 选项与 byte-stream-splitBSS浮点 SST以及启用preserve_row_sequence后 flush/compaction 的 append-only 表能够被 v1.1.4 重新打开并读回全部行。from_range [v1.2.0]与to_range [v1.1.4]的组合正是 RFC 中“向前兼容downgrade”场景的落地形态也与ci.toml中downgrade_to_versions [v1.1.4]相互印证。与 RFC 设想的差异及演进对比 RFC 与仓库现状可以观察到几处演进这些属于从代码与文档推断的合理结论具体以仓库为准命令入口演进RFC 设想./sqlness run --from... --to...落地为cargo run -p sqlness-runner -- compat并增加了--from-bins-dir、--to-bins-dir、--dry-run、--test-filter、--topology等选项用例粒度演进RFC 的 “feature / verify / cleanup” 三段式演化为“目录即用例”模型case.tomlsetup.sqlverify.sqlverify.result1.feature与3.cleanup的对应关系由每个用例自身的 setup/verify 文件承载并增加了case.toml元数据、版本区间过滤、命名空间隔离、datanode overlay 等机制框架复用RFC 中since/till、IGNORE_RESULT、TEMPLATE等新能力在落地实现中通过 sqlness 拦截器注册表按语句应用见 compat.rs 的interceptor_registry其中TEMPLATE直接复用sqlness::interceptor::template的DELIMITER。结语GreptimeDB 兼容性测试框架将“版本间兼容性”从依赖 release manager 的人工排查转变为可声明、可复用、可自动化的工程实践特性实现者用少量 SQL 为行为“定格”CI 用滑动版本窗口持续采样近期发布版cargo sqlness compat在保留状态上完成跨版本重启验证。该框架覆盖了向后兼容升级与向前兼容降级两个方向其设计——用例组织、拦截器语义、版本区间过滤、命名空间隔离——对任何需要长期维护存储格式与元数据兼容性的数据库项目都有直接参考价值。进一步阅读可查看 RFC 原文、tests/compatibility/README.md、tests/compatibility/AGENTS.md 及 runner 实现 compat.rs。【免费下载链接】greptimedbThe open-source observability database. One columnar engine for metrics, logs, and traces, on object storage.项目地址: https://gitcode.com/GitHub_Trending/gr/greptimedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考