PostgreSQLのパラレルクエリと「良い塩梅」の科学
PostgreSQLのパラレルクエリ。これ、使いこなせば強力な武器になりますが、設定を間違えるとシステム全体を巻き込む「自爆スイッチ」にもなりかねません。
最近、現場で「クエリが遅いからパラレル設定をガッツリ上げた」という相談を受けることが増えました。結論から言うと、「CPUリソースが空いているからといって、無闇に並列度を上げるのは悪手」です。
今回は、PostgreSQLの内部アーキテクチャに踏み込みつつ、この「並列実行」という諸刃の剣をどう御すべきか、僕なりの知見を共有したいと思います。
—
パラレルクエリの裏側で起きていること
まず、基本のおさらいですが、PostgreSQLのパラレルクエリは「プロセスモデル」を採用しています。
クエリが並列化されると、リーダープロセスがフォロワー(ワーカー)プロセスを立ち上げ、`Gather` ノードを境界として、各ワーカーが独立してスキャンやジョインを行い、結果をリーダーに集約します。
ここでエンジニアが意識すべきなのは、「プロセス間通信(IPC)」のオーバーヘッドです。ワーカーが増えれば増えるほど、共有メモリ(Dynamic Shared Memory)を介したデータの受け渡しや、ラッチの競合という見えないコストが確実に積み上がります。
最適化の「勘所」:パラメータの連鎖
よく調整するパラメータはいくつかありますが、これらは単独で動いているわけではありません。特に以下の関係性は常に頭に入れておくべきです。
1. `max_parallel_workers_per_gather` の罠
「とりあえず8くらいにしておこうか」……これが一番危険です。
この値は、一つのクエリが最大でいくつのワーカーを使えるかという「上限」です。しかし、これが高いと、同時に走るクエリが複数ある場合、`max_parallel_workers`(システム全体の上限)を瞬時に食い潰します。
結果、後続のクエリが並列化の恩恵を受けられず、単一プロセスで実行されることになり、システム全体のレスポンスが「ムラだらけ」になるのです。
2. `min_parallel_table_scan_size` との対話
このパラメータは、「これ以下のサイズのテーブルなら並列化してもオーバーヘッドの方が高くつくよ」という閾値です。
デフォルトの8MBは、現代のストレージ環境においては少し小さすぎるケースが多い。NVMe SSDを使っている環境なら、もう少し大きな値(例えば64MB〜128MB)に引き上げることで、無駄なプロセス生成を抑え、本当に計算リソースが必要な重いクエリに並列度を回すことができます。
パフォーマンストラブルシューティングの極意
「クエリが遅い」という相談を受けた際、僕はまず `EXPLAIN (ANALYZE, BUFFERS)` を見て、「計画された並列度」と「実際に稼働した並列度」の乖離を確認します。
- 計画と実績の乖離: もしプランナーが「並列化できる」と判断しているのに、実行時にワーカーが足りていないのであれば、`max_parallel_workers` か、そもそもOS側のリソース制限(`ulimit`等)を疑うべきです。
- Workerの待機時間: `EXPLAIN ANALYZE` の出力で、各ワーカーがどれだけ時間を消費しているか、あるいはリーダーがワーカーの結果を待っている時間が長すぎないかを見ます。もし特定のワーカーだけが異常に遅いなら、データのスキュー(偏り)を疑いましょう。Hash Joinの際、特定のキーにデータが集中していると、並列化は逆に足を引っ張ります。
最後に:データベースは「生き物」だ
パラレルクエリの設定に「黄金比」は存在しません。ある環境でベストだった設定が、データ量が増えた途端にボトルネックになることも珍しくありません。
僕が推奨するのは、「控えめなデフォルトから始め、統計情報を常に最新に保ちつつ、重いクエリにだけピンポイントで介入する」というアプローチです。`SET LOCAL max_parallel_workers_per_gather = 4;` のように、特定の複雑なクエリに対してのみヒント的に適用するのも、玄人好みのテクニックですね。
PostgreSQLというエンジンは、極めて論理的で美しい構造をしています。その構造を理解し、パラメータという名の「ツボ」を正しく押せば、驚くほどのパフォーマンスを見せてくれるはずです。
皆さんのデータベースの最適化が、少しでも快適なものになることを願っています。
コメント