適応的クエリ実行とランタイム再最適化
推定が外れても遅くなりにくいクエリ実行の仕組みがわかります。実行中の実カーディナリティで結合方式やパーティション数を組み替える適応実行の原理を、推定誤差への耐性の観点から解説します。
- 1.適応的クエリ実行は、実行前の統計推定だけに頼らず、ステージ完了時に得た実測の行数・サイズでプランの一部を作り直す。
- 2.Spark AQE は具体的に「シャッフル後パーティションの合体」「Sort-Merge から Broadcast への結合方式切替」「歪んだパーティションの分割」を実行中に行う。
- 3.再最適化はパイプラインを区切る材料化境界(シャッフル等)でのみ可能で、推定誤差が深い結合木で増幅する問題への実務的な耐性をもたらす。
静的な最適化の限界
コストベースの最適化(→ クエリオプティマイザの内部)は、実行を始める前に統計情報から各候補プランのコストを見積もり、最小コストの物理プランを1つ確定します。問題は、この見積もりが統計の精度に完全に依存する点です。複数条件の相関、結合をまたいだ選択率の伝播、UDF やパラメータ化された述語などにより、推定行数(カーディナリティ)は容易に1桁、2桁とずれます。
しかも誤差は結合木を上に伝わるほど増幅します。下流の1つの結合で出力行数の推定が10倍外れれば、その上のすべての結合・集約のコスト見積もりが連鎖的に狂い、不適切な結合アルゴリズムやパーティション数が選ばれます。静的最適化はこの誤差をプラン確定後に修正する手段を持ちません。**適応的クエリ実行(Adaptive Query Execution, AQE)**は、実行を進めながら得た「実測値」でプランの一部を作り直すことで、この構造的な弱点に耐性を与えます。
適応実行の基本原理
核心は単純です。実行を最後まで一気に流さず、材料化境界(materialization boundary)でいったん止め、そこまでの実測統計を集めて残りのプランを再決定します。
- 観測点: パイプラインが切れて中間結果がディスクやメモリに書き出される地点。分散処理ではシャッフル(再分配)の完了点が代表例。ここでは各パーティションの実際のバイトサイズと、ステージ全体の実出力行数が確定する。
- 再最適化: 確定した実測統計で、まだ実行していない上流ステージのプランを組み直す。推定値ではなく実測値を使うため、判断が当たりやすい。
- 制約: すでに走り終えたステージはやり直さない。再最適化できるのは「観測点より上流の未実行部分」に限られる。
パイプライン実行では演算子が行を1件ずつ後段へ流すため、途中でプランを差し替えると流れている行の整合が取れません。シャッフルのように中間結果が完全に材料化される境界でだけ、「ここまでの結果は確定、ここから先は未定」という安全な切れ目が生まれ、上流を作り直せます。境界の数が再最適化の機会の数を決めます。
Spark AQE が実行中に行う3つの調整
Spark の AQE は、各シャッフル完了時の実測統計(MapStatus が持つパーティションごとのバイトサイズと、ステージ単位で集計される実出力行数)を使い、代表的に次の3つを動的に適用します。
| 最適化 | きっかけ(実測) | やること |
|---|---|---|
| パーティション合体 | シャッフル後の各パーティションが小さすぎる | 隣接パーティションを連結し、目標サイズに近い数までタスク数を削減 |
| 結合方式の切替 | 片側の実サイズが閾値以下と判明 | Sort-Merge Join を Broadcast Hash Join に差し替え、シャッフルを回避 |
| スキュー分割 | 特定パーティションだけ突出して大きい | 巨大パーティションを複数サブパーティションに分割し、相手側を複製して負荷を均す |
パーティション合体(coalesce)
シャッフルのパーティション数(spark.sql.shuffle.partitions、既定 200)は実行前に固定で決め打ちするため、実データが小さいとほぼ空のパーティションを処理する大量のタスクが無駄に起動します。AQE は各パーティションの実サイズを見て、目標サイズ(既定 64MB 程度)に届くよう隣接パーティションを連結し、タスク数を実データ量に見合った数まで減らします。スケジューリングのオーバーヘッドが下がり、小タスクの裾が消えます。
結合方式の動的切替
オプティマイザは静的にはフィルタ後のサイズを推定するしかなく、Broadcast の可否(片側が spark.sql.autoBroadcastJoinThreshold 以下か)を見誤りがちです。AQE はサブクエリやフィルタを通過した実際の出力サイズがシャッフル後に判明した時点で、片側が十分小さければ Sort-Merge Join を Broadcast Hash Join に切り替えます。これにより大きい側のシャッフルとソートを丸ごと省け、コストの効き方が大きく変わります(→ ハッシュ結合の内部:グレース法とハイブリッドハッシュ で扱う方式選択がそのまま実行時判断になる)。
スキュー結合の分割
結合キーの値分布が偏ると(ホットキー)、特定パーティションだけが巨大化し、その1タスクが全体の完了を律速します。AQE は中央値に対し突出して大きいパーティション(既定で中央値の5倍かつ 256MB 超など)を検出し、それを複数のサブパーティションに分割、相手側の対応パーティションを各サブに複製して並列度を回復します。
動的切替はタダではありません。結合方式を Broadcast に変えるには、確定済みの中間結果を読み直してブロードキャストする必要があります。スキュー分割は相手側の複製でデータ量が増えます。AQE は「再最適化で得る短縮」が「材料化済み中間結果を作り直す追加コスト」を上回ると見積もれる場合にのみ適用します。境界で既に書き出されたデータを土台にするため、ゼロからのやり直しよりは安価です。
推定誤差への耐性という観点
AQE の本質的な価値は、特定の最適化メニューそのものより**「推定が外れても致命傷になりにくい」という耐性**にあります。静的最適化が抱える誤差の増幅(→ カーディナリティ推定とヒストグラム・スケッチ)に対し、適応実行は次の構造で対抗します。
静的最適化: 推定 → プラン確定 → 実行(誤差は最後まで残る)
適応実行: 推定 → 一部実行 → 実測でプラン補正 → 続行(境界ごとに誤差をリセット)
材料化境界を通過するたびに、それより下流のカーディナリティは推定ではなく実測に置き換わります。つまり誤差が伝播する距離が「次の境界まで」に限定され、深い結合木でも累積誤差が抑え込まれます。これは、相関や偏りで統計が当てにならないクエリほど効果が大きいことを意味します。
ランタイム再最適化は Spark 固有ではありません。SQL Server の Adaptive Joins(実測行数が閾値未満なら Nested Loop、超なら Hash を実行時に選択)や Interleaved Execution(テーブル値関数の実カーディナリティを取得してから上流を最適化)、Oracle の Adaptive Plans と自動再最適化、Presto/Trino の実行時スキュー対応など、各実装が「実行中に得た事実でプランを補正する」という同じ発想を採っています。
適用範囲と注意点
| 観点 | AQE が効く | AQE が効きにくい |
|---|---|---|
| クエリ形状 | シャッフルを含む多段の結合・集約 | シャッフルが無い単純な走査のみ |
| 統計の質 | 推定が外れやすい(相関・偏り・UDF) | 統計が正確で静的プランが既に最適 |
| データ規模 | パーティション偏りや空タスクが顕著 | 小規模で再最適化の利得が出ない |
注意点として、再最適化は材料化境界の存在が前提なので、境界の少ないクエリでは出番がほとんどありません。また各調整には閾値パラメータ(目標パーティションサイズ、ブロードキャスト上限、スキュー判定倍率など)があり、これらが実データの特性とずれていると効果が出ない、あるいは過剰な分割でかえって遅くなることがあります。AQE は静的最適化や統計整備を置き換えるものではなく、推定が外れたときの保険として上に重なる層だと捉えるのが正確です。巨大表をあらかじめ適切に分割しておく設計(→ パーティショニング)と組み合わせれば、そもそもスキューや空タスクを生みにくくでき、適応実行の負担も軽くなります。
まとめ
適応的クエリ実行は、実行前の統計推定だけでプランを固定せず、シャッフルなどの材料化境界で得た実測のカーディナリティとサイズを使って、上流の未実行部分を作り直す仕組みです。Spark AQE では「小パーティションの合体」「Sort-Merge から Broadcast への結合切替」「スキューパーティションの分割」として具体化されます。最大の意義は個々の最適化ではなく、推定誤差が深い結合木で増幅するという静的最適化の構造的弱点に対し、境界ごとに誤差をリセットして耐性を与える点にあります。
データベース Article
適応的クエリ実行とランタイム再最適化を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
適応的クエリ実行
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
Spark AQE は具体的に「シャッフル後パーティションの合体」「Sort-Merge から Broadcast への結合方式切替」「歪んだパーティションの分割」を実行中に行う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「適応的クエリ実行 / ランタイム再最適化」に近いか確認する。
- 強みである「適応的クエリ実行は、実行前の統計推定だけに頼らず、ステージ完了時に得た実測の行数・サイズでプランの一部を作り直す。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。