地図が間違っていたとき:SingleStoreはいかにして自らの失敗から学ぶか

1 min read

地図が間違っていたとき:SingleStoreはいかにして自らの失敗から学ぶか

遅いクエリとその実行プランの診断

あなたもきっと経験があるでしょう。重要なダッシュボードを支える低速なクエリが、本来よりも遅くなり始めるのです。明らかな原因を探ってみます。スキーマは正しく、インデックスも適切に設定されており、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のフィードバックストアに蓄積されます。これらのデータは、次回の再最適化試行に役立てられるだけでなく、同じ結合パターンを持つ他のクエリのフィードバックを相互参照するためにも利用できます。スキップされたプランは失敗した実験ですが、その実験から得られたデータは保持されます。

ストーリー3:実際の生産事例

最近、ある顧客が自社で管理するケースで、大規模な参照テーブルを介して約10個のテーブルを結合する繰り返し実行される分析クエリが、著しく順序の誤った実行プランで実行されました。重要な内部結合において、オプティマイザは約2億1100万行と推定しましたが、実際の出力は約7億1800万行でした。この過小評価により、オプティマイザは右結合の順序変更を行い、ハッシュテーブルを大きな側に構築し、約7億1800万行の中間テーブルを作成しました。その結果、実行時間は14秒、メモリ使用量は約232GBに達しました。これは、手動でSTRAIGHT_JOINヒントを使用した場合の約5秒、セッションレベルの結合順序回避策を使用した場合の約30GBのメモリ使用量と比較して大幅に長い時間です。

最初のFR実行ではクエリ全体を解決できませんでした。ベーステーブルとサブクエリの推定値はいくつか修正されましたが、新しい結合順序によってFRがこれまで見たことのない結合サブセットが明らかになりました。新たに探索された結合の1つで、オプティマイザはヒューリスティックな推定値である約100行にフォールバックしましたが、実際には約7億6200万行が生成されました。そのため、この反復処理では依然として下流の処理で誤った判断を下してしまいました。重要な教訓は、FRは学習していたものの、1回のラウンドで処理を完了するには結合ツリーの網羅性がまだ十分ではなかったということです。

その後、サポート担当者は顧客に同じセッション内でFRを繰り返し実行するように指示しました。3回目のFR実行時には、クエリはより優れた実行プランに収束しました。これは、元のプランや手動による回避策プロファイルよりも優れたパフォーマンスを発揮し、メモリ使用量も少なくて済みました。これはまさにFRが想定している運用パターンです。複雑で繰り返し実行される分析クエリの場合、最初のフィードバックラウンドでは、オプティマイザが探索空間のより良い領域に移動するだけで済む場合があります。その後の数回のラウンドで、十分な実行時データが蓄積され、より優れたプランで安定します。


3行で始めてみよう

SingleStore 9.0以降では、FRはデフォルトで有効になるように設計されています。コアFRの推奨ベースラインは次の3つの設定です。

1-- Step 1: Enable auto-profiling on complex queries2SET GLOBAL enable_auto_profile = TRUE;3SET GLOBAL auto_profile_type = SMART;4
5-- Step 2: Enable the FR execution pipeline6SET GLOBAL enable_automatic_feedback_reoptimization = ON;7
8-- Step 3: Let the engine automatically identify candidates9SET GLOBAL optimizer_feedback_reoptimization_auto_marking = AUTO;

新しくデプロイされた9.0以降のクラスタを使用している場合は、これらの設定は既に完了しているはずです。上記のチェックリストで、何も無効になっていないことを確認できます。

特定の低速クエリのチューニング

既にFRに処理させたいクエリがある場合は、自動検出を待つ必要はありません。

1-- Find the plan ID for your query2SHOW PLANCACHE;3
4-- Mark it for reoptimization on next execution5REOPTIMIZE MARK <plan_id>;6
7-- After the query runs, inspect what changed8SELECT plan_id, reoptimize_state, optimizer_notes9FROM information_schema.PLANCACHE10WHERE plan_id = <plan_id>;

このoptimizer_notes列には、旧計画と新計画の間でFRがどのように変化したか、測定された改善度合い、そして計画が収束したかどうかが示されています。

システム全体のFRアクティビティを監視する

1SHOW FEEDBACK REOPTIMIZATION STATUS;

これにより、クラスターレベルでの状況を把握できます。つまり、いくつの計画がマークされ、いくつが収束し、ワークロード全体で改善がどのように分布しているかがわかります。

9.1以降では、アイドル時間にもっと多くの作業を任せる

SingleStore 9.1以降を実行している場合は、アイドルクラスタフィードバック再最適化(ICFR)を有効にすることもできます。

1SET GLOBAL enable_idle_cluster_feedback_reoptimization = ON;

ICFRを有効にすると、個々のプランを監視する必要がなくなります。通常のワークロードを実行し続け、クラスターがアイドル状態を利用して、負荷の高いクエリをバックグラウンドで静かに再最適化します。これにより、定期的に実行されるダッシュボード、レポート、およびサービスサイド分析の背後にあるプランは、停止して手動で調整することなく、時間の経過とともに改善されていきます。

FRの他の部分と同様に、安全性が最優先事項です。ユーザーのワークロードが優先され、新たにテストされたプランが実際のトラフィックで機能しない場合は、以前に検証されたプランが引き続き利用可能です。

FRが役に立たない場合

FRは、問題が推定誤差である場合に適切なツールです。問題がそれ以外のものである場合は、適切なツールではありません。

  • 入力値ごとに異なる実行プランが必要となる、パラメータに依存するクエリの場合、FRはクエリ形状ごとに1つのプランを選択します。代わりに、クエリの書き換えやプランの固定化を検討してください。
  • 非常に短いクエリ― FRは意図的に非常に短いクエリを避けます。オーバーヘッドに見合う価値がないからです。
  • スキーマまたは設計上の問題— インデックスの欠落、シャードキーの不正、または本質的にコストのかかるクエリパターンが原因でクエリが遅い場合、FR は同じ設計内での代替案しか検討できません。まず根本的な問題を解決してください。

次は?

SingleStore 9.0 のフィードバック再最適化機能は、見積もりの​​誤りによって計画が不適切になるという根本的な問題に対処します。これにより、オプティマイザは古い仮定に頼るのではなく、実際の実行結果から学習できるようになります。

しかし、やるべきことはまだたくさんあります。

次に検討している分野は、メモリ負荷に基づく再最適化です。現在、FRは、カーディナリティの推定ミスによって結合順序や分散の選択が悪くなるクエリに焦点を当てています。中間要素の推定不足によるメモリ不足エラーは、関連するものの異なる問題です。この問題では、より深刻な影響(クエリが単に遅くなるだけでなく失敗する)が生じ、実行エンジンからのフィードバックによって異なる種類の実行プラン変更が促される可能性があります。これは現在、活発に研究が進められている分野です。

また、クエリ間のフィードバックを拡張し、より多くのクエリ形状に対応させ、プランキャッシュとの連携を強化することで、あるクエリからのカーディナリティ観測結果がワークロード内の関連クエリに、より迅速かつ広範囲に伝播するように取り組んでいます。

より広いビジョンとしては、すべてのクエリ実行を学習の機会と捉えるオプティマイザを実現することです。つまり、そのクエリだけでなく、ワークロード全体について学習するということです。統計情報はオプティマイザの出発点となり、フィードバックによってオプティマイザは時間とともに改善されていきます。

SingleStore 9.0以降をご利用の場合は、既に必要な設定は整っています。ワークロードを実行して、学習させてください。


Share