【テクニカル・上級編】 パーティションプルーニング – PostgreSQL

なぜあなたのクエリは全パーティションを走査してしまうのか? —— PostgreSQLパーティションプルーニングの深淵

データベースの規模がテラバイトを超えてくると、もはや「インデックスを貼る」だけのチューニングでは太刀打ちできなくなります。そんな時、私たちの頼もしい味方が「パーティショニング」です。しかし、設計図通りにパーティションを切ったはずなのに、なぜかクエリが全パーティションを舐め回し、I/O負荷で悲鳴を上げている――そんな経験はありませんか?

今回は、PostgreSQLの最適化の要、「パーティションプルーニング(Partition Pruning)」の内部機構と、それが期待通りに動かない時のトラブルシューティングについて、少し深掘りしてみましょう。

静的プルーニング:プランナの「先読み」

まず基本となるのは「静的プルーニング」です。これはクエリの解析フェーズ、つまりプランナが実行計画を作成する段階で完了します。

例えば、`WHERE created_at = ‘2023-10-01’` といった定数での絞り込みがあれば、プランナはカタログ情報を参照し、該当するパーティション以外の情報を実行計画から完全に切り離します。

ここで陥りやすい罠:
定数だと思っていた値が、実は関数呼び出し(`now()` や `current_date`)だったり、型キャストが不適切でインデックスが効かないケースです。プランナが「定数として評価できない」と判断すれば、安全側に倒して「全パーティション走査」を選択します。Explainの計画を見て、`Subplans Removed` の行が期待通りに出ていないなら、プランナが条件を定数として扱えていない可能性を疑ってください。

動的プルーニング:実行時の「賢い取捨選択」

真に面白いのは「動的プルーニング」です。これは、サブクエリの結果やパラメータなど、クエリの実行フェーズに入らないと絞り込み条件が確定しない場合に発動します。

PostgreSQLは、実行計画に「InitPlan」や「SubPlan」を組み込み、実際のデータが流れてくるタイミングで、「どのパーティションにアクセスすべきか」を判断します。特に、大規模な結合(JOIN)のキーとしてパーティションキーを使う場合、この動的プルーニングが効くかどうかが、レスポンスを数秒から数ミリ秒に変える決定打になります。

パフォーマンストラブルシューティング:ここを疑え

もし「明らかにパーティションを絞り込めるはずなのに、スキャンが止まらない」という事態に遭遇したら、以下の3点をチェックしてみてください。

  • 型不一致の暗黙変換:

例えば、パーティションキーが `TIMESTAMP` なのに、クエリで `VARCHAR` として比較していないか? 暗黙の型変換が発生すると、プランナはプルーニングの条件判定を諦めることがあります。`EXPLAIN` の出力に、不要な `Type Cast` が紛れ込んでいないか確認しましょう。

  • プランナの定数化限界:

複雑な関数や、外部テーブルを参照するサブクエリが条件に含まれると、プランナは「安全のためにプルーニングしない」という保守的な判断を下します。クエリを単純化し、`WHERE` 句に直接的なリテラルを渡す形へリファクタリングするだけで、劇的に改善することがあります。

  • 継承の深さとパーティション数:

数千のパーティションが存在する場合、プルーニングの判断そのものがオーバーヘッドになることがあります。`constraint_exclusion` の設定を確認し、極端に細分化しすぎていないか、アーキテクチャを見直す勇気も必要です。

最後に:データベースは「正直」である

PostgreSQLのプランナは非常に優秀ですが、魔法使いではありません。私たちが与える条件が「論理的にどのパーティションを排除していいか」を明確に示していない限り、彼らは全力を尽くして(=全てをスキャンして)答えを出そうとします。

チューニングとは、データベースの内部エンジンが何を考え、どこで迷っているのかを読み解く「対話」のようなものです。プルーニングが効かない時は、プランナの視点に立って、「今の条件で、どのパーティションを落とせるはずか?」を一緒に考えてあげてください。

データが巨大化するほど、こうした小さな最適化の積み重ねが、夜中のアラートを減らし、あなたのエンジニアとしての平穏を守ってくれるはずです。

それでは、良いチューニングライフを。

コメント

タイトルとURLをコピーしました