mcpField
R
reduce-unoptimized-query-oracle

reduce-unoptimized-query-oracle

github.com/cockroachdb测试与质量32.6k68 次安装5 周前更新
代码仓库

关于 reduce-unoptimized-query-oracle

此 CockroachDB 测试技能用于将 unoptimized-query-oracle 故障缩减为更小的 SQL 复现用例。它指导开发者检查测试产物、重现故障、运行自动缩减,并在处理非确定性故障时谨慎手动简化。

主要功能

  1. 01检查 roachtest 日志与参数
  2. 02重现 oracle 及其他故障
  3. 03使用 reduce 工具缩减 SQL
  4. 04验证复现后逐步简化
  5. 05处理非确定性故障

适用场景

  1. 01缩减 oracle 测试故障的 SQL
  2. 02调查 CockroachDB 断言失败或 panic
  3. 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_FATAL
  • COCKROACH_INTERNAL_DISABLE_METAMORPHIC_TESTING
  • COCKROACH_RANDOM_SEED
  • COCKROACH_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>

检查输出是否复现了失败日志中描述的测试失败。 失败模式有很多种。请查找以下其中一种,并确认它与失败日志相符:

  1. 两次执行“关注的查询”时结果不同(该查询是日志末尾附近重复两次的随机生成的 SELECT 语句,外层包含各种 SET 和 RESET 语句)。结果不同可能表现为结果集不同,也可能表现为其中一次报错而另一次没有报错。这属于 “oracle”失败。
  2. 或者,出现 internal error 或 断言失败。记下用于 reduce 步骤的错误消息。
  3. 或者,出现 panic。记下用于 reduce 步骤的错误消息。
  4. 或者,出现 超时。记下超时的语句。
故障排查

重要: 许多失败都具有非确定性,多区域测试尤其如此。如果首次运行没有出现失败,请最多尝试 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,同时确保能够复现。

关键: 对于非确定性故障,必须根据复现率,通过足够多次的尝试来测试每项简化。单次尝试失败并不意味着简化破坏了复现条件——也可能只是非确定性导致的。

每项简化的工作流
  1. 将 reduced.sql 复制为 reduced_v2.sql
  2. 对 reduced_v2.sql 做一项小改动
  3. 运行 ./test_repro.sh(此脚本会测试 reduced_v2.sql)
  4. 如果能够复现:将 reduced_v2.sql 复制到 reduced.sql,继续简化
  5. 如果尝试足够多次后仍无法复现:丢弃 reduced_v2.sql,尝试其他改动(即回退)

此工作流无需还原文件——你始终会在 reduced.sql 中保留上一个可正常复现的版本。

重要: 请将复制、编辑和测试作为单独的 bash 命令运行(不要用 && 链接)。 这样可以减少权限检查的次数。

可以尝试移除的内容(大致按顺序排列)
  1. 查询投影和聚合——将 SELECT 列表简化为仅包含必要列
  2. 查询谓词——简化 WHERE 子句
  3. 索引——尝试移除辅助索引
  4. 查询连接——简化 WHERE 子句
  5. CREATE TABLE 中的列——移除失败查询中未引用的列
  6. 特殊字符——移除或替换名称和数据中的非 ASCII 字符
  7. 其他 SQL 简化

编辑“关注查询”以处理“oracle”故障时,请务必同时编辑“关注查询”的两个副本,确保它们完全相同。 否则,比较结果集时就无法做到公平对比。

常见的必要元素

以下内容通常无法移除:

  • 优化器随机种子:SET testing_optimizer_random_seed = <value>——通常无法更改此特定值,因为它决定了哪些优化器规则会被禁用
  • 优化器规则概率:SET testing_optimizer_disable_rule_probability——会影响查询计划的选择
  • 优化器设置中的特定 RESET/SET 序列,例如 distsql 和 vectorize
  • 某些索引(会影响查询计划)
  • 多节点设置(--nodes=9),用于分布式查询 Bug(不过请先尝试单节点——可能可行,而且更简单)
  • CREATE STATISTICS 语句(会影响查询规划)
回溯

如果某项更改导致无法复现:

  1. 丢弃 reduced_v2.sql(不要将其复制到 reduced.sql)
  2. 确认 reduced.sql 仍能复现。如果不能,说明复现具有非确定性。(它可能一开始就具有非确定性,也可能在简化过程中变得具有非确定性。)尝试复现 10 次,并记录新的复现率。之后根据新的复现率,调整每个简化步骤中的复现尝试次数。
  3. 尝试不同的简化方式

绝不要从无法复现的状态继续简化。

如果陷入困境(即回溯后仍无法再次复现),请停止并向用户报告你尝试运行的确切命令。

第 9 步:最终验证和输出

简化约 20 分钟后,或回溯几次后已没有更多简化空间时,就该停止了。

  1. 运行 10 次或更多次复现,以确认稳定性并确定最终复现率
  2. 记录最小复现步骤
  3. 说明哪些元素是必需的,哪些是可选的
输出

最终输出应包含两个可展示给用户的文件:

  1. reduced.sql - 用于复现 Bug 的最小 SQL 脚本
  2. 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 年内找不到已知的好提交,请停止并向用户报告。

评论

支持 Markdown