关于 reduce-unoptimized-query-oracle
此 CockroachDB 测试技能用于将 unoptimized-query-oracle 故障缩减为更小的 SQL 复现用例。它指导开发者检查测试产物、重现故障、运行自动缩减,并在处理非确定性故障时谨慎手动简化。
主要功能
- 01检查 roachtest 日志与参数
- 02重现 oracle 及其他故障
- 03使用 reduce 工具缩减 SQL
- 04验证复现后逐步简化
- 05处理非确定性故障
适用场景
- 01缩减 oracle 测试故障的 SQL
- 02调查 CockroachDB 断言失败或 panic
- 03确认 SQL 简化后间歇性故障仍可复现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cockroachdb/ cockroach --skill reduce-unoptimized-query-oracle安装后在 Claude Code 中输入/reduce-unoptimized-query-oracle确认已加载。
详细介绍
减少未优化查询 Oracle 测试失败日志
将未优化查询 Oracle 测试失败日志缩减为尽可能简单的复现案例。
未优化查询 Oracle roachtest 会运行一系列随机 SQL 语句来创建随机数据集,然后使用不同的优化设置执行两次随机“感兴趣的查询”。如果两次执行返回的结果不同,则说明 CockroachDB 存在错误。
何时使用
在以下情况下使用此技能:
- 你遇到了未优化查询 Oracle roachtest 的测试失败。
- 你需要找到能够复现测试失败的最精简 SQL。
步骤 1:定位构件
询问用户构件目录的位置。
在构件目录中查找相关文件:
- 测试参数:
params.log(roachtest 的参数) - 测试日志:
test.log(roachtest 的日志) - 失败日志:
failure*.log(roachtest 的失败日志) - 完整 SQL 日志:
unoptimized-query-oracle*.log(导致失败的 SQL 语句) - 感兴趣的查询日志:
unoptimized-query-oracle*.failure.log(包含感兴趣的查询,以及可能的其他失败信息) - Cockroach 日志:
logs/1.unredacted/cockroach.log或logs/unredacted/cockroach.log(包含 git commit)
步骤 2:确定测试配置
从 cockroach.log 中确定 git commit:
grep "binary: CockroachDB" cockroach.log
在版本字符串中查找 commit 哈希(例如:cb94db961b8f55e3473f279d98ae90f0eeb0adcb)。
通过检查以下内容,确定是否启用了运行时断言:
params.log中的"runtimeAssertionsBuild": "true"- 或
test.log中的Runtime assertions enabled
通过查找以下内容,确定是否适用变形测试设置:
params.log中类似以下内容的行:"metamorphicBufferedSender": "true", "metamorphicWriteBuffering": "true",- 或
test.log中类似以下内容的行:metamorphically setting "kv.rangefeed.buffered_sender.enabled" to 'true' metamorphically setting "kv.transaction.write_buffering.enabled" to 'true'
从 cockroach.log 开头确定环境变量:
grep -A10 "using local environment variables:" cockroach.log
重要的环境变量包括:
COCKROACH_INTERNAL_CHECK_CONSISTENCY_FATALCOCKROACH_INTERNAL_DISABLE_METAMORPHIC_TESTINGCOCKROACH_RANDOM_SEEDCOCKROACH_TESTING_FORCE_RELEASE_BRANCH但可能还有其他重要的环境变量,因此最好全部获取。
通过以下方式判断这是多区域测试还是单区域测试:
- 测试名称(例如,
test.log中的seed-multi-region表示多区域) - 或完整 SQL 日志中是否存在
\connect行 如果两者都不存在,则这是单区域测试。
步骤 3:检出并构建
进行普通构建时使用:
git checkout <commit-hash>
./dev build short
如果启用了运行时断言,则改用测试构建:
git checkout <commit-hash>
./dev build short -- --crdb_test
注意: 只有当复现案例使用了地理空间函数(BOX2D、 geometry、geography 等)时,才构建 libgeos:
./dev build libgeos
步骤 4:准备完整 SQL 日志文件
首先,检查完整 SQL 日志文件顶部是否包含以下语句。如果没有,请添加:
SET statement_timeout='1m0s';
SET sql_safe_updates = false;
如果使用了变形测试设置,也要将其添加到完整 SQL 日志文件顶部:
SET CLUSTER SETTING kv.rangefeed.buffered_sender.enabled = true;
SET CLUSTER SETTING kv.transaction.write_buffering.enabled = true;
在构件目录或仓库根目录中创建一个合适的目录,用于存放临时文件。
第 5 步:初步复现
根据测试类型确定正确的演示命令:
- 多区域测试:使用
--nodes=9 - 单区域测试:省略
--nodes选项
使用类似下面的命令,尝试根据完整 SQL 日志文件复现测试失败。该命令最多可能需要 20 分钟才能完成。
<env vars> ./cockroach demo --multitenant=false --nodes=9 --insecure --set=errexit=false --no-example-database --format=tsv -f <full-sql-log-file>
检查输出是否复现了失败日志中描述的测试失败。 失败模式有很多种。请查找以下其中一种,并确认它与失败日志相符:
- 两次执行“关注的查询”时结果不同(该查询是日志末尾附近重复两次的随机生成的 SELECT 语句,外层包含各种 SET 和 RESET 语句)。结果不同可能表现为结果集不同,也可能表现为其中一次报错而另一次没有报错。这属于 “oracle”失败。
- 或者,出现
internal error或 断言失败。记下用于 reduce 步骤的错误消息。 - 或者,出现 panic。记下用于 reduce 步骤的错误消息。
- 或者,出现 超时。记下超时的语句。
故障排查
重要: 许多失败都具有非确定性,多区域测试尤其如此。如果首次运行没有出现失败,请最多尝试 10 次,再判断无法复现。
此时可以将输出与 failure*.log 进行比较,该文件应显示原始测试运行中的失败。
如果初步运行尝试 10 次后仍无法复现,请暂停并告知用户无法复现该失败,同时提供尝试过的命令。 用户可能会提供其他指示。
如果看起来已经复现,就可以继续下一步。
第 6 步:使用 Reduce 工具
构建 reduce 工具:
./dev build reduce
再次准备完整 SQL 日志文件
对于多区域测试,请删除 \connect 行(这些行会导致 reduce 工具出现语法错误):
grep -v '^\\connect' <full-sql-log-file> > <cleaned-log>
运行 Reduce
重要: 必须从 cockroach 仓库根目录运行 reduce 工具,因为它会在当前目录中查找 ./cockroach。
多区域测试使用 -multi-region 选项;单区域测试则省略该选项。
对于“oracle”失败(结果不同):
./bin/reduce -unoptimized-query-oracle -multi-region -chunk 25 -v -file <cleaned-log> 2>&1 | tee reduce-output.log
-unoptimized-query-oracle 选项会检查两次执行“关注的查询”是否产生相同结果。
对于内部错误、断言失败或 panic:
./bin/reduce -contains "<error-regex>" -multi-region -chunk 25 -v -file <cleaned-log> 2>&1 | tee reduce-output.log
使用错误消息中有辨识度的部分作为 -contains 正则表达式(例如,"nil LeafTxnInputState")。
reduce 工具最多可能需要一个小时才能运行。
提取精简后的 SQL
reduce 工具会先输出进度行,然后输出最终 SQL。只提取 SQL:
grep -A1000 "^reduction: " reduce-output.log | tail -n +2 > reduced.sql
重要: 手动简化之前,请立即保存 reduce 输出的备份:
cp reduced.sql reduced_original.sql
如果工作文件在简化过程中损坏,这可以作为恢复点。
如果 reduce 工具无法复现,请暂停并告知用户。 用户可能会提供其他指示。如果测试失败无法复现,我们有时需要修改 reduce 工具本身。
步骤 7:创建测试脚本并确定复现率
重要: 许多 Bug 都具有非确定性。手动简化之前,请先创建可复用的测试脚本,并确定复现率。
创建一个简短的测试脚本(按需调整):
cat > test_repro.sh << 'EOF'
#!/bin/bash
# Test if reduced_v2.sql reproduces the error (exits on first success, up to 10 attempts)
for i in {1..10}; do
if ./cockroach demo --multitenant=false --nodes=9 --insecure \
--set=errexit=false --no-example-database --format=tsv \
-f reduced_v2.sql 2>&1 | grep -q "<error-pattern>"; then
echo "Run $i: REPRODUCED"
exit 0
else
echo "Run $i: no error"
fi
done
echo "FAILED"
EOF
chmod +x test_repro.sh
对于“oracle”故障,测试脚本可能需要隔离并比较两次执行“关注查询”的结果,而不是检查错误模式。
运行测试脚本以确定复现率。复现率不一定总是 100%。
复现率决定了测试简化方案时需要尝试的次数:
- 复现率为 100%:尝试一次即可
- 复现率为 50%:通常需要尝试 2-3 次
- 复现率为 10%:需要尝试约 10 次才能有把握
- 复现率低于 5%:可能需要尝试 20 次以上
请注意,在某些情况下,可能需要将以下设置加回精简后的文件才能复现:
SET statement_timeout='1m0s';
SET sql_safe_updates = false;
如果精简后的 SQL 尝试 10 次后仍无法复现,请在此暂停并向用户报告。他们可能有额外说明。
步骤 8:手动简化
现在,逐步简化 SQL,同时确保能够复现。
关键: 对于非确定性故障,必须根据复现率,通过足够多次的尝试来测试每项简化。单次尝试失败并不意味着简化破坏了复现条件——也可能只是非确定性导致的。
每项简化的工作流
- 将
reduced.sql复制为reduced_v2.sql - 对
reduced_v2.sql做一项小改动 - 运行
./test_repro.sh(此脚本会测试reduced_v2.sql) - 如果能够复现:将
reduced_v2.sql复制到reduced.sql,继续简化 - 如果尝试足够多次后仍无法复现:丢弃
reduced_v2.sql,尝试其他改动(即回退)
此工作流无需还原文件——你始终会在 reduced.sql 中保留上一个可正常复现的版本。
重要: 请将复制、编辑和测试作为单独的 bash 命令运行(不要用 && 链接)。
这样可以减少权限检查的次数。
可以尝试移除的内容(大致按顺序排列)
- 查询投影和聚合——将 SELECT 列表简化为仅包含必要列
- 查询谓词——简化 WHERE 子句
- 索引——尝试移除辅助索引
- 查询连接——简化 WHERE 子句
- CREATE TABLE 中的列——移除失败查询中未引用的列
- 特殊字符——移除或替换名称和数据中的非 ASCII 字符
- 其他 SQL 简化
编辑“关注查询”以处理“oracle”故障时,请务必同时编辑“关注查询”的两个副本,确保它们完全相同。 否则,比较结果集时就无法做到公平对比。
常见的必要元素
以下内容通常无法移除:
- 优化器随机种子:
SET testing_optimizer_random_seed = <value>——通常无法更改此特定值,因为它决定了哪些优化器规则会被禁用 - 优化器规则概率:
SET testing_optimizer_disable_rule_probability——会影响查询计划的选择 - 优化器设置中的特定 RESET/SET 序列,例如 distsql 和 vectorize
- 某些索引(会影响查询计划)
- 多节点设置(
--nodes=9),用于分布式查询 Bug(不过请先尝试单节点——可能可行,而且更简单) CREATE STATISTICS语句(会影响查询规划)
回溯
如果某项更改导致无法复现:
- 丢弃
reduced_v2.sql(不要将其复制到reduced.sql) - 确认
reduced.sql仍能复现。如果不能,说明复现具有非确定性。(它可能一开始就具有非确定性,也可能在简化过程中变得具有非确定性。)尝试复现 10 次,并记录新的复现率。之后根据新的复现率,调整每个简化步骤中的复现尝试次数。 - 尝试不同的简化方式
绝不要从无法复现的状态继续简化。
如果陷入困境(即回溯后仍无法再次复现),请停止并向用户报告你尝试运行的确切命令。
第 9 步:最终验证和输出
简化约 20 分钟后,或回溯几次后已没有更多简化空间时,就该停止了。
- 运行 10 次或更多次复现,以确认稳定性并确定最终复现率
- 记录最小复现步骤
- 说明哪些元素是必需的,哪些是可选的
输出
最终输出应包含两个可展示给用户的文件:
- reduced.sql - 用于复现 Bug 的最小 SQL 脚本
- bisect_run.sh - 用于
git bisect run的脚本
编写输出时,应确保内容可以直接复制并粘贴到终端中。
输出格式示例
(此输出中的命令应根据复现所需的内容进行修改。)
# Minimal Reproduction
# reduced.sql
cat > reduced.sql << 'EOF'
CREATE TABLE t ();
SET testing_optimizer_random_seed = 1234567890;
SET testing_optimizer_disable_rule_probability = 0.5;
SELECT ...;
EOF
# bisect_run.sh
cat > bisect_run.sh << 'EOF'
#!/bin/bash
# Git bisect run script
# Exit codes: 0=good (bug not present), 1=bad (bug present), 125=skip (build failed)
REPO_DIR="/path/to/cockroach"
REPRO_SQL="/path/to/reduced.sql"
cd "$REPO_DIR" || exit 125
echo "=== Testing commit $(git rev-parse --short HEAD) ==="
# Build (use --crdb_test if runtime assertions were enabled in the original test)
if ! ./dev build short -- --crdb_test 2>&1 | grep -q "Successfully built"; then
echo "BUILD FAILED - skipping"
exit 125
fi
# Test for bug (try 3 times for flaky bugs)
for i in {1..3}; do
if ./cockroach demo --multitenant=false --insecure \
--set=errexit=false --no-example-database --format=tsv \
-f "$REPRO_SQL" 2>&1 | grep -q "<error-pattern>"; then
echo "BUG PRESENT - marking as BAD"
exit 1
fi
done
echo "Bug not present - marking as GOOD"
exit 0
EOF
chmod +x bisect_run.sh
# Command to reproduce
git checkout <commit-hash>
./bisect_run.sh
# Command to bisect
git bisect start ...
git bisect run bisect_run.sh
# Failure
# <paste stacktrace or relevant failure details here>
# Repro rate: ~X% (may need multiple attempts)
展示此输出后,询问用户是否想尝试在 master 分支上复现该 Bug。
可选第 10 步:检查 Bug 是否已在 Master 上修复
开始二分查找之前,检查 Bug 是否已在 master 上修复。
git stash # if needed
git checkout master
./dev build short -- --crdb_test
./cockroach demo --multitenant=false --insecure --set=errexit=false --no-example-database --format=tsv -f reduced.sql
多运行几次,以排除偶发性影响。记录 Bug 是否能在 master 上复现。
可选第 11 步:二分查找
如果用户想查找引入或修复该 Bug 的提交,请使用 git bisect。
如果 Bug 已在 Master 上修复
使用二分查找定位修复提交(Bug 不再复现的第一个提交)。由于“好”提交(master)比“坏”提交更新,因此请使用自定义术语:
git bisect start --first-parent --term-old=broken --term-new=fixed
git bisect broken <commit-where-bug-exists> # e.g., the original failing commit
git bisect fixed master # master is fixed
git bisect run ./bisect_run.sh
# When done
git bisect reset
注意: --first-parent 选项只沿主分支上的合并提交查找,避免绕入功能分支。二分查找脚本必须在 Bug 不存在(已修复)时返回 0,在 Bug 存在(未修复)时返回 1。
如果 Bug 在 Master 上仍然存在
使用二分查找定位回归提交(引入 Bug 的第一个提交):
git bisect start --first-parent
git bisect good <known-good-commit> # e.g., a previous release tag
git bisect bad master # master has the bug
git bisect run ./bisect_run.sh
# When done
git bisect reset
二分查找将找出引入或修复该 Bug 的提交。
查找好提交
如果不知道一个好提交(即 Bug 不存在的提交),可以回溯历史以找到一个。
# Find a commit from ~6 months ago on the main branch
git rev-list --first-parent -1 --before="6 months ago" HEAD
测试该提交是否存在 Bug。如果不存在,就将其用作二分查找的好提交。如果 Bug 仍然存在,请尝试继续回溯,但不要超过 1 年。
如果在 1 年内找不到已知的好提交,请停止并向用户报告。