
遅いクエリとその実行プランの診断
あなたもきっと経験があるでしょう。重要なダッシュボードを支える低速なクエリが、本来よりも遅くなり始めるのです。明らかな原因を探ってみます。スキーマは正しく、インデックスも適切に設定されており、ANALYZE TABLE を先週も実行したばかりです。それでも、クエリの実行時間は以前の3倍にもなってしまいます。
そこでクエリプランを表示してみると、オプティマイザは大きなテーブルをハッシュ化し、小さなテーブルでプローブするという、予想とは正反対の方法をとっていることがわかります。結果として、本来なら数行程度のはずの結果セットが、実際には数百万行にも及んでいます。クラスタ全体に爆発的に拡散する中間データが、すべての原因を説明しています。
最適化ツールは故障していません。設計どおり、既知の情報に基づいて最適なプランを選択しようとしています。問題は、最適化ツールが収集した統計情報が、実際のテーブルの内容を反映していないことです。つまり、最適化ツールが頼りにしている地図が古くなっているのです。地図が間違っていれば、どんなに優秀なナビゲーターでも間違った道に迷ってしまうものです。
これは、分析データベースにおい て最も一般的で厄介なパフォーマンス問題の1つです。SingleStoreに限ったことではなく、主要なDBMSはすべてこの問題に直面しています。統計情報はデータ収集時のデータのスナップショットを捉えますが、データは常に変化し続けます。分布がシフトしたり、偏りが生じたり、テーブルが不均一に成長したりします。オプティマイザは、何らかの理由で更新が必要になるまで古いスナップショットを使い続けます。統計情報と実際のデータの状態とのギャップによって、クエリプランのパフォーマンスが徐々に低下していくのです。
従来の手法(定期的な統計情報の収集、手動ANALYZEによる収集、サンプリング戦略など)は有効ですが、事後対応型です。問題が発生した後、あるいはデータの変化速度に合わないスケジュールで統計情報を更新することになります。多数のテーブルを結合する複雑なクエリの場合、古い推定値が1つあるだけで、実行計画が完全に誤ったものになってしまう可能性があります。
フィードバック再最適化(FR)は、SingleStoreが提供する自動クエリ最適化ソリューションです。FRは、実行前に収集した統計情報だけに頼るのではなく、クエリ実行時に実際に何行が処理されたか、どれだけのメモリが消費されたかなどを監視し、その観測結果に基づいてより優れた実行プランを生成します。クエリが最終的にどこに到達するかを監視することで、地図を 修正します。
この記事の残りの部分では、その仕組み、実際の運用状況、そして実際のワークロードにおける成果について詳しく解説します。
カーディナリティの推定ミスが1つでも発生すると、結合順序全体が逆転してしまう可能性がある。
統計だけでは不十分な理由
オプティマイザは、基本的に不確実性下でのプランナーです。クエリを実行して実行方法を決定するのではなく、行数、列ヒストグラム、一意の値の推定値、nullの割合といった統計情報に基づいて推論します。これらの数値を基にコストモデルを構築し、候補となるプランを評価し、最もコスト効率が良いと思われるプランを選択します。
これはほとんどの場合うまく機能します。SingleStoreは、他の主要な分析データベースと同様に、統計インフラストラクチャに多大な投資を行っています。具体的には、自動収集、ヒストグラム構築、サンプリング戦略、増分更新などです。データの状態が良好で、統計情報が定期的にメンテナンスされている場合、オプティマイザは適切な判断を下します。
しかし、統計が常に不十分な点がいくつかあります。
急激なデータ変更後の古い統計情報。統計情報の収集サイクル間に、大規模なバッチロード、夜間ETL処理、またはテーブルの入れ替えが発生すると、オプティマイザは先週の地図に基づいてナビゲートを実行します。実際の行数や分布は劇的に変化している可能性があります。
カーディナリティとは、オプティマイザが演算子によって生成される行数を推定する指標のことです。この推定が間違っていると、後続のすべてのコスト決定が不安定になります。
結合カーディナリティの推定。単一テーブルの統計情報が完璧であっても、複数テーブルの結合後にどれだけの行が残るかを推定するのは困難です。テーブル間の列の相関関係は、標準的な統計情報では捉えられません。オプティマイザの独立性に関する仮定(異なるテーブルに対するフィルタ述語は無相関であるという仮定)は、実際のデータでは成り立たず、プランに結合が追加されるたびにエラーが累積します。
極端に偏ったデータ。バケット数が固定されたヒストグラムは分布を近似的に表しますが、データがべき乗則に従う場合(少数の値が行の大部分を占める場合)、適切に管理されたヒストグラムであっても、結合計画に必要な真の形状を正確に捉えられない可能性があります。
これらは特殊なケースではありません。ほとんどの運用環境における分析ワークロードの通常の動作条件です。
見積もりがずれると何が起こるのか
これを具体的に理解するために、実際のワークロードで繰り返し現れるパターンの簡略版を考えてみましょう。2つのテーブルがあるとします。
1-- orders: 50 million rows, loaded fresh last night2-- customers: 200,000 rows, stable3 4SELECT c.region, COUNT(*) AS order_count5FROM orders o6JOIN customers c ON o.customer_id = c.id7WHERE o.order_date >= '2024-01-01'8GROUP BY c.region;
オプティマイザは統計情報を確認します。customers統計情報が最後に収集された時点では、例えば4000万行と大きなデータ量でした。これは、大規模なクリーンアップ処理によって行の98%が削除される前に測定されたためです。ヒストグラムは依然として古い形状を反映しています。
これらの古い統計情報に基づいて、オプティマイザはcustomersをプローブ側として使用し、ordersをハッシュ化することを決定します。これは正しい呼び出しとは正反対です。メモリ上に5000万行のハッシュテーブルを作成し、それを一度スキャンします。メモリ負荷が急上昇し、実行速度が低下します。
オプティマイザが知るべきことは単純でした。つまり、 customersが今や非常に小さいということです。それが分かれば、結合を反転させるでしょう。つまり、小さいcustomersテーブルをハッシュ化し、 ordersでプローブします。そうすればクエリは高速になります。
これはまさに、フィードバックで解決すべき問題の典型例です。スキーマの問題でも、インデックスの欠落でもなく、統計情報と実際のテーブルの内容との間にギャップがあるだけです。
FRは最初の実行をプロファイリングし、customersに生成された行数が4000万行ではなく20万行であることを確認すると、実際の行数に基づいて実行プランを再最適化します。次の実行では、正しい結合側が使用されます。ヒントは不要です。手動ANALYZE操作も不要です。クエリの書き換えも不要です。
それは統計が残すギャップであり、フィードバックはそのギャップを埋める ために設計されています。
フィードバック再最適化の仕組み
フィードバック再最適化は、クエリ最適化ツールを中心とした学習ループです。観察、識別、再計画、検証の4つの段階から構成されます。どの段階も重要ですが、最後の検証段階こそが、本番環境での安全な運用を保証するものです。
FRは、ガード付きのリトライループと考えることができます。何が起こったかを観察し、より良いプランを試行し、実際の実行でそれがより良いと証明された場合にのみ、そのプランを採用します。
監視: 実行時統計情報を収集する
クエリが実行されると、SingleStore は実行プラン内の各演算子の実際の実行時動作(生成された行数、CPU 消費量、メモリ使用量など)をプロファイリングできます。ただし、これにはコストがかかるため、FR は選択的にプロファイリングを行います。つまり、データが有用である可能性が最も高い複雑で負荷の高いクエリにプロファイリングを集中させます。バージョン 9.0 以降では、auto_profile_type = SMARTでこの処理は自動的に行われます。
識別:計画の見積もりミスを検出する
実行速度が遅いクエリすべてがFRの対象となるわけではありません。システムは再最適化に着手する前に、以下のチェックを行います。
- クエリは、問題になるのに十分なほど高コストである必要がある
- 推定行数と実際の行数の差は相当なものでなければならない
- このプランは、以前のFR実行から既に収束していてはならない
プランがこれらのフィルターを通過すると、再最適化候補としてマークされます。これは自動的に(エンジンが不一致を検出)または手動(REOPTIMIZE MARK <plan_id>)で発生する可能性があります。
再計画:より情報に基づいた候補を生成する
プランがマークされると、次の実行時に再最適化が実行されます。オプティマイザはプラン空間に対して再度検索を実行しますが、今回は統計情報に加えて、実際の実行からのランタイムフィードバックを追加入力として利用します。カーディナリティ推定値が修正されることで、異なる結合順序が見つかったり、異なるハッシュ結合側が選択されたり、異なる分散戦略が選択されたりする可能性があります。
重要な点として、FR実行はそれをトリガーした実行のみに限定されます。同時実行クエリは中断されることなく、現在検証済みのプランを引き続き使用します。リソースの競合を防ぐため、プランごとに一度に実行されるFR実行は1つだけです。
検証:コミット前に比較する
候補プランはすぐに採用されるわけではありません。SingleStoreはそれを実行し、CPU、ネットワーク時間、メモリの3つの側面で、元のプランと比較して測定されたパフォーマンスを評価します。新しいプランは、他の側面を著しく悪化させることなく、少なくとも1つの側面で明確な改善が見られた場合にのみ採用されます。
候補プランが劣っている場合は破棄され、元のプランが引き続き使用されます。この比較してからコミットするステップこそが、プランを盲目的に置き換えるオプティマイザとFRを区別する点です。
組み込みの安全性 ― 採用しない計画も重要
状況を悪化させるような再最適化システムは、再最適化を全く行わないよりも危険です。FRの設計はこの点を真剣に考慮しています。
候補となるプランはすべて、採用前に検証ステップを経ます。実際のワークロードで候補プランを実行し、実行時の動作を測定し、現在のプランと比較します。システムが候補プランが改善ではないと判断した場合、そのプランは破棄されます。元のプランはそのまま維持され、次回の実行では、候補プランが存在しなかったかのように、既知の良好なプランが使用されます。つまり、FRは永続的な回帰を回避するように設計されています。実際の実行で機能しない候補プランは保持されません。
重要なのは、候補が却下された場合でも、実行中に収集されたランタイム観測データ(実際の行数、結合カーディナリティ、リソース消費量など)は破棄されないということです。これらのデータはFRのフィードバックストアに保持され、次回の再最適化試行に役立てられるとともに、同じ結合パターンを持つ関連クエリにも利用できます。実行計画は破棄されますが、学習は継続されます。FRを実行するたびに、より良い実行計画が生成されるかどうかにかかわらず、システムはワークロードに関するより正確な知識を得ることができます。
評価対象となったベ ンチマーク全体を通して、97%以上のクエリで継続的なパフォーマンス低下は発生しませんでした。一時的にパフォーマンスが低下したごく少数のクエリについても、システムは後続のイテレーションで自動的に修復されました。FRは、監視を必要とせず本番環境で稼働させ続けられるように設計されています。
結果 — 地図が修正されるとどうなるか
FRを2つの標準的な分析ベンチマーク、TPC-H(クエリ数24、スケールファクター100)とJOB(IMDBデータに対する結合順序ベンチマーク、クエリ数113)で評価しました。各クエリについて、5回のコントロール実行と5回のFR実行を行い、クエリ時間、CPU、ネットワーク、メモリ使用量を測定しました。
以下の主要な数値は、経過時間の変化を示しています。クエリが少なくとも5%高速化された場合、そのクエリは改善されたとみなされます。その他の列は、改善されたクエリの平均短縮率と、単一クエリにおける最大の短縮率を示しています。
ベンチマーク | クエリ向上率 | 改善されたクエリにおける平均削減率 | 単一クエリにおける最大削減率 | データ特性 |
JOB (IMDB) | 73% | -25% | -68% | Extreme skew, power-law |
TPC-H | 42% | -15% | -21% | Uniform, synthetic |
そのパターンは明らかです。FRの効果はデータの偏りと連動しています。実世界のデータが乱雑で偏りが大きいほど、FRが処理できるデータ量が増えます。JOBのIMDBデータセット(movie_infoやmovie_keywordのようなテーブルの行のうち、ごく少数の人気映画が不均衡な割合を占めている)は、まさにオプティマイザの推定が破綻し、フィードバックが真価を発揮する環境です。
改善されたクエリのほとんどは、1回のFRラウンド後に最適なプランに到達しました。少数のクエリは追加のラウンドを必要とし、最も長いケースは3回のラウンド後に収束しました。
FRが計画を改善する場合:TPC-Hの例
TPC-H Q13は、顧客注文分布を計算するLEFT JOINとGROUP BYを組み合わせたクエリです。このベンチマークの中で最もネットワーク負荷の高いクエリの一つです。
FRを1回実行しただけで、FRは結合カーディナリティの推定値を修正し、より適切な結合順序を選択し、クラスタ間で移動するデータ量を約3分の1削減しました。すべての指標が改善されました。たった1回の実行で。
Metric | Before | After | Change |
Elapsed | 8.73s | 6.87s | -21% |
CPU | 181,932ms | 158,337ms | -13% |
Network | 225,862ms | 159,908ms | -29% |
Memory | 11.0 GB·s | 8.8 GB·s | -20% |
TPC-H Q5 (6 テーブルを等結合したチェーン) も同様のパターンを示しました。経過時間 -20%、ネットワーク -23%、メモリ -17% はすべて 1 回の FR 実行によるものです。TPC-H Q14 は、クエリに複数の推定エラーがある場合に何が起こるかを示しています。最初の FR 反復でネットワーク トラフィックが 41% 削減され、経過時間が 14% 短縮されました。フォローアップの反復では、ラウンド 1 で観測されたカーデ ィナリティを使用して集計戦略がさらに洗練されました。FR は単一の修正に限定されません。検証された各実行は、次の実行のためのシグナルを追加します。
ストーリー2:FRが採用しなかった計画 ― TPC-H Q2
すべての候補プランが改善となるわけではありません。TPC-H Q2は、特定の部品タイプに対して最小コストでサプライヤーをランク付けする相関サブクエリを備えた5テーブルの等結合です。構造的には、FRが20%改善した6テーブルの結合チェーンであるQ5と似ています。どちらも同じデータセットに対する複数テーブルの結合です。しかし、Q2の元のプランは、その統計情報によって既に十分に機能していました。
FRはQ2のプロファイリングを行い、異なる結合順序を持つ候補プランを生成、実行し、比較しました。候補プランの測定されたタスクCPU時間(約8,500ms)は、元のプランと比べて改善が見られませんでした。FRはこの候補プランをスキップしました。クエリはすべての反復処理においてベースラインのままで、変更はありませんでした。
Metric | Iteration 0 | Iteration 4 | Change |
Elapsed | 0.42s | 0.41s | unchanged |
CPU | 8,617ms | 8,626ms | unchanged |
Network | 10,447ms | 10,267ms | unchanged |
Memory | 22.7 MB·s | 22.6 MB·s | unchanged |
これは重要なニュアンスです。カーディナリティの推定値を修正しても、必ずしもより良いプランが得られるとは限りません。オプティマイザは、誤った推定値にもかかわらず、既に適切な結合順序を見つけている可能性があります。また、修正された推定値によって、他の理由でより悪い領域に探索が向かう可能性もあります。そのような場合、FRの検証ステップがそれを検知します。候補は破棄され、元のプランが有効なままとなり、システムは実際の実行で有効でない投機的な変更を保持することを回避します。
TPC-Hの24のクエリ全体を通して、FRは複数回の反復処理を経て38の候補プランを生成しました。そのうち19が採用され、上記で報告した改善点となりました。残りの19は測定の結果、改善が見られなかったため破棄されました。これらのプランが属するクエリは、変更されることなく元のプランで実行され続けました。
重要な点として、FRが候補をスキップした場合でも、学習成果は失われません。実行中に収集されたランタイム観測データ(実際の行数、結合カーディナリティ、メモリ消費量など)は、FRのフィードバックストアに蓄積されます。これらのデータは、次回の再最適化試行に役立てられるだけでなく、同じ結合パターンを持つ他のクエリのフィードバックを相互参照するためにも利用できます。スキップされたプランは失敗した実験ですが、その実験から得られたデータは保持されます。

