reduce-unoptimized-query-oracle について
unoptimized-query-oracle の失敗を、より小さな SQL 再現例に縮小する CockroachDB 向けテストスキルです。テスト成果物の確認、失敗の再現、ツールによる縮小、非決定的なケースを考慮した手動簡略化を案内します。
主な機能
- 01roachtest のログとパラメーター確認
- 02oracle などの失敗の再現
- 03reduce ツールによる SQL の縮小
- 04再現性を確認しながら簡略化
- 05非決定的な失敗への対応
活用シーン
- 01oracle テスト失敗の SQL を最小化
- 02CockroachDB のアサーション失敗や panic の調査
- 03SQL の簡略化後も断続的な失敗が再現するか確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cockroachdb/ cockroach --skill reduce-unoptimized-query-oracleインストール後、Claude Code で/reduce-unoptimized-query-oracleを入力して読み込まれたか確認します。
詳細
最適化されていないクエリオラクルのテスト失敗を再現する
最適化されていないクエリオラクルの失敗ログを、可能な限り単純な再現ケースまで削減します。
unoptimized-query-oracle roachtest は、一連のランダムな SQL 文を実行してランダムなデータセットを作成し、その後、ランダムな「注目クエリ」を異なる最適化設定で 2 回実行します。2 回の実行結果が異なる場合、CockroachDB のバグを示しています。
使用するタイミング
次の場合にこのスキルを使用します:
- unoptimized-query-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 コミットが含まれています)
ステップ 2: テスト構成を確認する
cockroach.log から git コミットを確認します:
grep "binary: CockroachDB" cockroach.log
バージョン文字列内のコミットハッシュを探してください(例: 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>
出力が失敗ログに記載されているテストの失敗を再現しているか確認してください。 失敗のモードは多数考えられます。失敗ログと一致するはずの以下のいずれかを探してください:
- ログの終わり近くで、各種 SET および RESET 文で囲まれ、2 回繰り返される SELECT 文である「Query of Interest」(ランダムに生成された SELECT 文)の 2 回の実行間での 異なる結果。この結果は、異なる結果セットの形をとる場合もあれば、一方でエラーが発生し、もう一方ではエラーが発生しない場合もあります。これは 「オラクル」失敗です。
- または、
internal errorもしくは アサーション失敗。リデュースステップのエラーメッセージを記録してください。 - または、パニック。リデュースステップのエラーメッセージを記録してください。
- または、タイムアウト。タイムアウトした文を記録してください。
トラブルシューティング
重要: 多くの失敗は非決定的です。特にマルチリージョンテストでは顕著です。最初の実行で失敗が発生しない場合は、再現しなかったと結論づける前に最大 10 回まで試してください。
この時点で、元のテスト実行による失敗を示しているはずの failure*.log と出力を比較すると役立つ場合があります。
10 回試しても初期実行が再現しない場合は、ここで一時停止し、失敗を再現できなかったことをユーザーに報告して、試したコマンドを表示してください。 ユーザーには追加の指示がある可能性があります。
再現しているように見える場合は、次のステップに進みます。
ステップ 6: リデュースツールを使用する
リデュースツールをビルドします:
./dev build reduce
完全な SQL ログファイルを再度準備する
マルチリージョンテストの場合、\connect 行を削除してください(reduce ツールで構文エラーを引き起こすため):
grep -v '^\\connect' <full-sql-log-file> > <cleaned-log>
リデュースを実行する
重要: リデュースツールは、カレントディレクトリ内の ./cockroach を探すため、cockroach リポジトリのルートディレクトリから実行する必要があります。
マルチリージョンテストでは -multi-region オプションを使用し、シングルリージョンテストでは省略してください。
「オラクル」失敗(異なる結果)の場合:
./bin/reduce -unoptimized-query-oracle -multi-region -chunk 25 -v -file <cleaned-log> 2>&1 | tee reduce-output.log
-unoptimized-query-oracle オプションは、「Query of Interest」の 2 回の実行が同じ結果を生成するかどうかを確認します。
内部エラー/アサーション失敗/パニックの場合:
./bin/reduce -contains "<error-regex>" -multi-region -chunk 25 -v -file <cleaned-log> 2>&1 | tee reduce-output.log
エラーメッセージの特徴的な部分を -contains の正規表現として使用してください(例: "nil LeafTxnInputState")。
リデュースツールの実行には最大 1 時間かかる場合があります。
縮小された SQL を抽出する
リデュースツールは進捗行に続けて最終的な SQL を出力します。SQL の部分だけを抽出するには:
grep -A1000 "^reduction: " reduce-output.log | tail -n +2 > reduced.sql
重要: 手動での簡略化を行う前に、リデュース出力のバックアップをすぐに保存してください:
cp reduced.sql reduced_original.sql
これにより、簡略化の途中で作業ファイルが破損した場合に復旧できる地点が確保されます。
リデュースツールが再現に失敗した場合は、ここで一時停止し、その旨をユーザーに報告してください。 ユーザーには追加の指示がある可能性があります。まれに、テストの失敗が再現しない場合に、リデュースツール自体を修正する必要が生じることがあります。
ステップ 7: テストスクリプトの作成と再現率の測定
重要: 多くのバグは非決定的です。手動での簡略化を行う前に、 再利用可能なテストスクリプトを作成し、再現率を測定してください。
小さなテストスクリプトを作成します(必要に応じて調整してください):
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
「オラクル」型の失敗の場合、エラーパターンを確認する代わりに、 テストスクリプトで「対象のクエリ」の2つの実行結果を分離して差分を取る必要がある可能性が高いです。
テストスクリプトを実行して再現率を測定してください。常に100%になるとは限りません。
この再現率によって、簡略化をテストする際に必要な試行回数が決まります:
- 再現率100%: 1回の試行で十分
- 再現率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にコピーするreduced_v2.sqlに対して小さな変更を1つだけ加える./test_repro.shを実行する(reduced_v2.sqlをテストします)- 再現した場合:
reduced_v2.sqlをreduced.sqlにコピーし、簡略化を続ける - 十分な回数を試行しても再現しない場合:
reduced_v2.sqlを破棄し、別の変更を試す(つまりバックトラックする)
このワークフローにより、ファイルを復元する必要がなくなります。常に最後に動作したバージョンが reduced.sql に残ります。
重要: コピー、編集、テストは、それぞれ別々の bash コマンドとして実行してください(&& でつなげないでください)。
これにより、権限確認の回数が減ります。
削除を試すもの(おおよその順序)
- クエリの射影と集約 - SELECT リストを本質的な列だけに簡略化する
- クエリの述語 - WHERE 句を簡略化する
- インデックス - セカンダリインデックスの削除を試す
- クエリの結合 - WHERE 句を簡略化する
- CREATE TABLE の列 - 失敗しているクエリで参照されていない列を削除する
- 特殊な文字 - 名前やデータから非 ASCII 文字を削除または置換する
- その他の SQL の簡略化
「オラクル」型の失敗では、「対象のクエリ」を編集する際、2つの「対象のクエリ」の_両方_を同一になるように編集してください。 そうしないと、結果セットの差分を比較する際に条件が揃わなくなります。
一般的に必要な要素
以下は削除できないことが多いです:
- オプティマイザのランダムシード:
SET testing_optimizer_random_seed = <value>- 無効化されるオプティマイザルールを決定するため、この特定の値は変更できないことが多いです - オプティマイザのルール確率:
SET testing_optimizer_disable_rule_probability- クエリプランの選択に影響します - オプティマイザ設定(distsql や vectorize など)に関する特定の RESET/SET の順序
- 特定のインデックス(クエリプランに影響します)
- 分散クエリのバグに対するマルチノード構成(
--nodes=9)(ただし、まずはシングルノードを試してください。動作する場合があり、より単純です) CREATE STATISTICS文(クエリプランニングに影響します)
バックトラッキング
変更によって再現できなくなった場合:
reduced_v2.sqlを破棄します(reduced.sqlにコピーしないでください)reduced.sqlで引き続き再現できることを確認します。再現できない場合、再現が非決定的であることを意味します。(最初から非決定的だった可能性も、簡略化の過程で非決定的になった可能性もあります。)10 回再現を試み、新しい再現率を記録します。以降、各簡略化ステップでの再現試行回数を調整する際は、この新しい再現率を使用してください。- 別の簡略化を試します
壊れた状態から簡略化を続けないでください。
行き詰まった場合(バックトラッキング後に再現できなくなった場合)は、そこで停止し、実行しようとしていた正確なコマンドを添えてユーザーに報告してください。
ステップ 9:最終確認と出力
簡略化を約 20 分行った後、または数回バックトラッキングしてもこれ以上簡略化できなくなったら、停止するタイミングです。
- 安定性を確認し、最終的な再現率を判断するため、再現を 10 回以上実行します
- 最小限の再現手順を記録します
- 必須の要素と任意の要素を記録します
出力
最終出力には、ユーザーに提示できる次の 2 つのファイルを含めてください。
- reduced.sql - バグを再現する最小限の 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 ブランチでバグの再現を試すかどうかをユーザーに尋ねてください。
オプションのステップ 10:master でバグが修正済みか確認する
二分探索を行う前に、バグがすでに 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
不安定な挙動を考慮して、これを数回実行します。master でバグが再現するかどうかを記録してください。
オプションのステップ 11:二分探索
バグを導入または修正したコミットを見つけたい場合は、git bisect を使用します。
master ですでにバグが修正されている場合
修正コミット(バグが初めて再現しなくなったコミット)を見つけるために二分探索します。「good」コミット(master)の方が「bad」コミットより新しいため、独自の用語を使用します。
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 オプションはメインブランチ上のマージコミットのみをたどり、機能ブランチへの寄り道を避けます。二分探索スクリプトは、バグが存在しない(修正済み)場合に 0、バグが存在する(未修正)場合に 1 を返す必要があります。
master でバグが引き続き発生する場合
リグレッションコミット(バグが初めて導入されたコミット)を見つけるために二分探索します。
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
二分探索によって、バグを導入または修正したコミットが特定されます。
正常なコミットを見つける
正常なコミット(バグが存在しないコミット)がわからない場合は、時間をさかのぼって見つけることができます。
# Find a commit from ~6 months ago on the main branch
git rev-list --first-parent -1 --before="6 months ago" HEAD
そのコミットでバグが存在するかどうかをテストします。存在しない場合は、そのコミットを二分探索の正常なコミットとして使用します。バグが引き続き存在する場合は、さらにさかのぼってください。ただし、1 年より前にはさかのぼらないでください。
1 年以内に既知の正常なコミットが見つからない場合は、停止してユーザーに報告してください。