PostgreSQLの並列クエリ:その「甘美な誘惑」と「深淵」について
PostgreSQLのパフォーマンスチューニングにおいて、並列クエリ(Parallel Query)は多くのエンジニアが一度は夢見る「特効薬」だ。巨大なテーブルを前にしたとき、`max_parallel_workers_per_gather` を引き上げれば、CPUのコアたちが一斉に牙を剥いてデータを蹂躙してくれる――そんな幻想を抱いたことはないだろうか。
しかし、現場で数多のクエリと対峙してきた経験から言わせてもらえば、並列クエリは単なる「設定の変更」で終わるほど甘い代物ではない。今回は、この強力な兵器を正しく御すために、内部アーキテクチャの深層と、現場で遭遇しがちな罠について少し語ろうと思う。
並列クエリの深層:Gatherとプロセスのダンス
PostgreSQLの並列クエリは、Leaderプロセスと複数のWorkerプロセスによる共同作業だ。クエリが実行されると、Leaderが「Gatherノード」を介してWorkerを召喚する。ここでのポイントは、それぞれのWorkerが独自の「Executor」を持っているということだ。
彼らは共有メモリ(Dynamic Shared Memory)を介して通信し、スキャン結果をキューに投げ込む。Leaderはそれらを回収(Gather)してクライアントに返す。この美しいダンスの背後には、実は非常にシビアなコスト計算が働いている。
オプティマイザは、並列化によるオーバーヘッド(プロセスの起動コストやプロセス間通信のコスト)と、データスキャン時間の短縮分を比較して、並列化すべきかどうかを決める。問題は、この「コストモデル」が常に現実の負荷と合致するとは限らないことだ。
なぜ「並列化してほしいのに動かない」のか?
現場でよくある相談の一つが「これだけ巨大なテーブルなのに、なぜ並列スキャンが走らないんだ?」というものだ。これにはいくつかの典型的な理由がある。
- コスト見積もりの壁: `min_parallel_table_scan_size` を超えていても、プランナが「シーケンシャルスキャン+インデックス」の方が速いと判断すれば並列化は却下される。
- 関数の不適合: クエリ内で呼ばれている関数に `PARALLEL UNSAFE` な属性がついていないか? あるいは、暗黙的に並列化を阻害する操作が含まれていないかを確認する必要がある。
- 共有メモリの枯渇: 並列化には `dynamic_shared_memory_type` や `max_worker_processes` の制限が付きまとう。設定を欲張りすぎて、そもそもプロセスを起動するリソースが確保できていないケースは意外と多い。
パフォーマンストラブルシューティング:観測なき最適化は盲目
並列クエリが期待通りに動かない、あるいは逆にシステム全体を遅延させている場合、何を見るべきか。まずは `EXPLAIN (ANALYZE, VERBOSE)` の出力だ。
特に注目すべきは、各Workerがどれだけ「仕事」をしているかという点だ。
- Workerの偏り: 特定のWorkerだけが処理に時間をかけ、他が暇をしている(あるいは起動すらしていない)場合、データのスキュー(偏り)を疑うべきだ。
- Gatherノードでの待機: LeaderがWorkerからのデータ受け取りで詰まっている場合、I/Oのボトルネックか、あるいは単純にWorkerの数に対するデータの粒度が細かすぎる可能性がある。
また、`pg_stat_activity` を眺めて、並列クエリ実行中に `wait_event` がどこで発生しているかを追跡するのも重要だ。特に `ParallelWorkerMain` が待機状態にある場合、それはCPUの限界ではなく、データ取得経路やロックの競合に問題がある可能性が高い。
並列クエリとどう付き合うか
結論を言えば、並列クエリは「使いどころ」を極めるべき技術だ。
OLTPのような、短時間に大量のクエリが並行して飛んでくる環境で `max_parallel_workers_per_gather` を無闇に大きくすれば、瞬く間にCPUは飽和し、コンテキストスイッチの嵐に飲まれる。逆に、DWHのような分析基盤であれば、並列化は正義だ。
私が推奨するのは、「クエリ単位のチューニング」と「サーバー全体のサイジング」を明確に分けることだ。全体の設定をいじる前に、まずは `SET LOCAL max_parallel_workers_per_gather = 4;` のように、特定の重たいクエリに対してのみ実験的に並列度を調整し、その影響範囲を計測することから始めてほしい。
PostgreSQLは正直なデータベースだ。こちらの意図(クエリの意図)を正確にSQLに落とし込み、リソースの制約を理解してあげれば、期待以上のパフォーマンスで応えてくれる。
並列クエリという強力な牙。それを飼い慣らすか、あるいは食い殺されるか。すべては、あなたの「観測」にかかっている。
コメント