TiDB BR 向后兼容测试体系解析:跨版本备份恢复的完整验证工作流
本文基于 br/COMPATIBILITY_TEST.md 展开,介绍 TiDB BR(备份恢复工具)如何验证“用当前版本 BR 恢复历史版本备份数据”这一向后兼容能力:包括历史备份数据的准备方式、基于 docker-compose 的测试集群搭建,以及恢复验证脚本的执行细节。读完后,你可以完整理解 BR 兼容测试的三段式工作流(版本选择 → 数据准备 → 恢复校验),并能按仓库提供的脚本与 Makefile 目标独立复现整个流程。
背景与目标:为什么需要兼容性测试
TiDB 的备份归档(backup archive)本质上是对物理 KV 数据和元数据的持久化封装。随着版本迭代,归档格式、元信息结构都可能演进,而历史备份数据一旦写坏无法重做,因此必须回答一个关键问题:当前版本的 BR 能否正确恢复旧版本 BR 产生的备份?
仓库文档明确说明了引入该测试的动机与目标:
- 背景:过去曾出现过不兼容问题,导致某些情况下 BR 无法恢复已备份的数据,因此需要一条专门的测试工作流来持续检查兼容性;
- 目标:确保能够向后兼容恢复最近 3 个 minor 版本(previous 3 minor versions)的备份数据。
该测试策略覆盖两类对象存储——S3 与 GCS。由于要测试最近 3 个版本、且有 2 种存储系统,共产出 6 份备份归档,对应 6 个独立的兼容性测试用例(3 版本 × 2 存储)。
工作流总览
兼容测试分为两个阶段:
- 数据准备(Data Preparation):分别拉起旧版本的 TiDB 集群,用对应旧版本 BR 执行备份,把数据写到 S3 与 GCS 上;
- 恢复验证(Test Content):拉起 nightly 版本的 TiDB 集群,用当前目录编译的最新 BR 二进制,逐个恢复历史版本的备份数据,并校验恢复结果符合预期。
下面按这两个阶段结合仓库源码逐一拆解。
阶段一:历史版本备份数据的准备
版本标签(TAG)的自动选取
数据准备的第一步是确定“哪些历史版本”参与测试。核心逻辑在 br/compatibility/get_last_tags.sh 的 getLatestTags 函数中:
# update tags
git fetch --tags
getLatestTags() {
release_5_branch_regex="^release-5\.[0-9].*$"
release_4_branch_regex="^release-4\.[0-9].*$"
TOTAL_TAGS=$(git for-each-ref --sort=creatordate refs/tags | awk -F '/' '{print $3}')
# we should filter such tags
# v5.0.2-20210628
# v5.0.2-alpha
# because these tags don't have corresponding docker images.
filter='-'
# latest tags
TAGS=$(echo $TOTAL_TAGS | tr ' ' '\n' | grep -v $filter | tail -n3)
...
}
从源码可以看到两个关键设计:
- 过滤带
-的标签:如v5.0.2-20210628、v5.0.2-alpha这类 nightly / alpha 标签没有对应的 docker 镜像,会被剔除; - 按当前分支自适应:默认取最新的 3 个 release 标签;如果当前处于
release-5.x分支,则组合“最近 3 个 v5.x 版本 + 最近 1 个 v4.x 版本”;处于release-4.x分支时则只取最近 3 个 v4.x 版本。
这种分支感知的选取策略保证了升级链路的连续性——从 4.x 升级到 5.x 的跨大版本恢复也在覆盖范围内。
逐版本建集群并灌入标准数据
拿到 TAG 列表后,br/compatibility/prepare_backup.sh 负责为每个版本并行拉起一个旧版本集群并执行备份:
runBackup() {
# generate backup data in /tmp/br/docker/backup_data/$TAG/
echo "build $1 cluster"
TAG=$1 PORT_SUFFIX=$2 docker-compose -p $1 -f br/compatibility/backup_cluster.yaml build
TAG=$1 PORT_SUFFIX=$2 docker-compose -p $1 -f br/compatibility/backup_cluster.yaml up -d
trap "TAG=$1 PORT_SUFFIX=$2 docker-compose -p $1 -f br/compatibility/backup_cluster.yaml down" EXIT
# wait for cluster ready
sleep 20
# prepare SQL data
TAG=$1 PORT_SUFFIX=$2 docker-compose -p $1 -f br/compatibility/backup_cluster.yaml exec -T control /go/bin/go-ycsb load mysql -P /prepare_data/workload -p mysql.host=tidb -p mysql.port=4000 -p mysql.user=root -p mysql.db=test
TAG=$1 PORT_SUFFIX=$2 docker-compose -p $1 -f br/compatibility/backup_cluster.yaml exec -T control br/tests/run_compatible.sh prepare
}
for tag in $TAGS; do
i=$(( i + 1 ))
runBackup $tag $i &
done
wait
要点解析:
-
每个版本一个独立 compose 项目:通过
docker-compose -p $1(以 tag 作为 project 名)与PORT_SUFFIX=$2隔离端口,使多个版本的集群可以并行准备,互不干扰;集群模板为 br/compatibility/backup_cluster.yaml; -
标准数据来自 YCSB:使用 go-ycsb 按 br/compatibility/prepare_data/workload 定义的负载写入
test库:recordcount=1000 operationcount=0 workload=core readallfields=true readproportion=0 updateproportion=0 scanproportion=0 insertproportion=0即只加载 1000 条记录到
test.usertable,不做后续读写操作。这个固定的 1000 行规模是后续恢复校验的“期望值”来源; -
真正的备份动作在容器内完成:
run_compatible.sh prepare会被传入prepare参数,进入下一步的存储备份脚本。
S3 / GCS 两套备份脚本
br/tests/run_compatible.sh 通过参数区分准备阶段与运行阶段,prepare 阶段会遍历 br/tests/docker_compatible_*/prepare.sh:
-
S3 侧:br/tests/docker_compatible_s3/prepare.sh 先启动/配置 MinIO,再执行备份 SQL:
# create bucket /usr/bin/mc config host add minio http://$S3_ENDPOINT $MINIO_ACCESS_KEY $MINIO_SECRET_KEY /usr/bin/mc mb minio/test --ignore-existing # backup cluster data run_sql_in_container "backup database test to 's3://$BUCKET/bk${TAG}?endpoint=http://$S3_ENDPOINT$S3_KEY&force-path-style=true';"归档目录为
s3://test/bk${TAG},即每个版本一个独立的备份目录;force-path-style=true是 MinIO 等 S3 兼容服务的典型要求。 -
GCS 侧:br/tests/docker_compatible_gcs/prepare.sh 通过 REST 调用在本地 GCS mock 服务上创建 bucket,再执行:
run_sql_in_container "backup database test to 'gcs://$BUCKET/bk${TAG}?endpoint=http://$GCS_HOST:$GCS_PORT';" -
注意这两处使用的都是
BACKUP DATABASE ... TO '...'的 SQL 语法,即由旧版本 TiDB 内置的 BR 完成备份,保证归档确实出自目标历史版本。
阶段二:用最新 BR 恢复并校验
启动测试集群与构建最新 BR
按照 br/COMPATIBILITY_TEST.md 的标准流程,先基于 docker-compose 拉起 nightly 集群并构建当前目录的 BR:
docker-compose -f docker-compose.yaml rm -s -v && \
docker-compose -f docker-compose.yaml build && \
docker-compose -f docker-compose.yaml up --remove-orphans
docker-compose -f docker-compose.yaml control make compatibility_test
其中 br/docker-compose.yaml 定义了 control、pd0、tikv0/1、minio、gcs(mock)等服务;control 容器把仓库 ./bin 挂载到 /go/src/github.com/pingcap/br/bin,因此容器内 bin/br 就是当前代码目录构建出来的最新 BR,这正是“当前版本 BR 恢复历史数据”的语义来源。在仓库根目录,Makefile 中也提供了对应的本地目标,无需进容器即可触发:
.PHONY: br_compatibility_test_prepare
br_compatibility_test_prepare:
@cd br && tests/run_compatible.sh prepare
.PHONY: br_compatibility_test
br_compatibility_test:
@cd br && tests/run_compatible.sh run
run_compatible.sh:测试入口与变量约定
所有兼容测试的统一入口是 br/tests/run_compatible.sh:
source ${BASH_SOURCE[0]%/*}/../compatibility/get_last_tags.sh
getLatestTags
echo "start test on $TAGS"
EXPECTED_KVS=1000
PD_ADDR="pd0:2379"
GCS_HOST="gcs"
GCS_PORT="20818"
TEST_DIR=/tmp/backup_restore_compatibility_test
mkdir -p "$TEST_DIR"
rm -f "$TEST_DIR"/*.log &> /dev/null
for script in br/tests/docker_compatible_*/${1}.sh; do
echo "*===== Running test $script... =====*"
TEST_DIR="$TEST_DIR" \
PD_ADDR="$PD_ADDR" \
GCS_HOST="$GCS_HOST" \
GCS_PORT="$GCS_PORT" \
TAGS="$TAGS" \
EXPECTED_KVS="$EXPECTED_KVS" \
PATH="br/tests/_utils:bin:$PATH" \
TEST_NAME="$(basename "$(dirname "$script")")" \
BR_LOG_TO_TERM=1 \
bash "$script"
done
这段脚本体现了测试框架的几个约定:
- 参数
${1}决定阶段:传prepare时执行各存储目录下的prepare.sh,传run时执行_run.sh(下划线前缀使其在 prepare 阶段不会被误匹配); - 环境变量注入:
TAGS(待测版本列表)、EXPECTED_KVS=1000(期望行数,与 YCSBrecordcount一致)、PD_ADDR=pd0:2379(容器内 PD 地址)、GCS mock 的 host/port; BR_LOG_TO_TERM=1使 BR 日志输出到终端,便于 CI 中排查失败原因;run_sql_in_container等辅助函数来自br/tests/_utils下的共享脚本(如 br/tests/br_test_utils.sh),通过PATH注入到子脚本中。
S3 恢复脚本:命令与校验逻辑
br/tests/docker_compatible_s3/_run.sh 是“恢复 + 校验”的完整范例:
BUCKET="test"
MINIO_ACCESS_KEY='brs3accesskey'
MINIO_SECRET_KEY='brs3secretkey'
S3_ENDPOINT=minio:24927
S3_KEY="&access-key=$MINIO_ACCESS_KEY&secret-access-key=$MINIO_SECRET_KEY"
# restore backup data one by one
for TAG in ${TAGS}; do
echo "restore ${TAG} data starts..."
# after BR merged into TiDB we need skip version check because the build from tidb is not a release version.
bin/br restore db --db test -s "s3://$BUCKET/bk${TAG}?endpoint=http://$S3_ENDPOINT$S3_KEY" --pd $PD_ADDR --check-requirements=false
row_count=$(run_sql_in_container "SELECT COUNT(*) FROM test.usertable;" | awk '/COUNT/{print $2}')
if [ $row_count != $EXPECTED_KVS ]; then
echo "restore kv count is not as expected(1000), obtain $row_count"
exit 1
fi
# clean up data for next restoration
run_sql_in_container "drop database test;"
done
逐条说明:
bin/br restore db --db test:使用 CLI 方式(而非 SQL)恢复test库,-s指向版本化目录s3://test/bk${TAG},endpoint 与访问密钥直接拼在 URI 查询参数中,这是 BR 支持的 URI 内嵌凭证方式;--check-requirements=false:脚本注释解释了原因——BR 并入 TiDB 仓库后,nightly 构建不是正式 release 版本,默认的版本要求检查(check requirements)会将其拦截,因此在兼容测试中显式跳过;- 数据正确性校验:恢复后执行
SELECT COUNT(*) FROM test.usertable,行数必须等于EXPECTED_KVS(1000),否则立即exit 1使整个测试失败; - 循环间清理:
drop database test保证下一个版本在干净环境上恢复,避免跨版本数据互相污染。
GCS 恢复脚本:额外的 OAuth 凭证细节
br/tests/docker_compatible_gcs/_run.sh 与 S3 版结构一致,差异在于 GCS 客户端需要 service account 凭证。脚本内嵌了一段 service_account JSON(token_uri 指向 compose 网络中的 oauth mock 服务 http://oauth:5000/oauth/token),保存后导出环境变量:
# save CREDENTIALS to file
echo $KEY > "br/tests/$TEST_NAME/config.json"
# export test CREDENTIALS for gcs oauth
export GOOGLE_APPLICATION_CREDENTIALS="br/tests/$TEST_NAME/config.json"
# restore backup data one by one
for TAG in ${TAGS}; do
bin/br restore db --db test -s "gcs://$BUCKET/bk${TAG}" --pd $PD_ADDR --gcs.endpoint="http://$GCS_HOST:$GCS_PORT" --check-requirements=false
...
done
这里的 --gcs.endpoint 把 GCS 客户端请求重定向到本地 mock(默认 gcs:20818),使测试完全离线可复现。
运行方式汇总与注意事项
综合文档与仓库脚本,完整运行路径如下:
-
准备阶段(生成 6 份历史版本备份归档):
# 仓库根目录 make br_compatibility_test_prepare # 等价于 cd br && tests/run_compatible.sh prepare或手动执行 br/compatibility/prepare_backup.sh,它会并行拉起各历史版本集群、灌入 YCSB 数据并触发 S3/GCS 备份。
-
验证阶段(nightly 集群 + 最新 BR 逐个恢复):
# 仓库根目录 make br_compatibility_test # 等价于 cd br && tests/run_compatible.sh run或在容器内按 br/COMPATIBILITY_TEST.md 给出的方式执行:
docker-compose -f docker-compose.yaml rm -s -v && \ docker-compose -f docker-compose.yaml build && \ docker-compose -f docker-compose.yaml up --remove-orphans docker-compose -f docker-compose.yaml control make compatibility_test
运行时的几个前提与限制值得注意:
- 依赖 git tag:版本选取依赖
git fetch --tags拉取到的 release 标签,本地浅克隆或未同步 tag 的环境会得到错误的TAGS列表;脚本中内置的正则仍面向 4.x/5.x 分支(见 br/compatibility/get_last_tags.sh),在更新的大版本分支上使用时,从源码结构看该逻辑可能需要按新的 release 分支正则调整; - 备份数据不可再生:准备阶段用的是历史版本 TiDB/BR,若归档缺失,无法用新版本重新生成语义等价的“旧版本备份”,这是该工作流必须与 CI 定时任务配合的原因;
- 校验口径:当前校验是行数一致性(COUNT(*) 等于 1000),属于快速而确定性的正确性断言,配合“逐版本恢复 + 恢复后清库”的隔离策略,构成最小完备的向后兼容信号;
--check-requirements=false只应用于测试场景:它绕过了版本要求检查,生产环境升级恢复时不应照搬该参数。
小结
BR 兼容测试用一条清晰的流水线回答“旧备份能否被新 BR 恢复”:getLatestTags 按分支选取最近 3 个 release 版本 → prepare_backup.sh 并行拉起旧版本集群,YCSB 写入固定 1000 行数据后经 SQL 备份到 MinIO 与 mock GCS → run_compatible.sh run 驱动各存储目录的 _run.sh,用当前构建的 BR 执行 restore db,以 COUNT(*) 断言校验并清库。整套流程全部由 br/tests/run_compatible.sh、br/compatibility/ 与 br/tests/docker_compatible_s3、br/tests/docker_compatible_gcs 目录下的脚本构成,配合 Makefile 的 br_compatibility_test_prepare / br_compatibility_test 目标即可在本地或 CI 中完整复现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00