【テクニカル・上級編】 並列インデックス構築 – PostgreSQL

「待機時間」という名の負債をどう削るか:PostgreSQL並列インデックス構築の深淵

データベースの運用を長く続けていると、ある日突然、巨大なテーブルと向き合わなければならない瞬間が訪れます。「この数億行のテーブルに、今すぐインデックスを貼らなければならない」。そんな時、シングルスレッドでせっせとインデックスを構築している余裕なんてありませんよね。

PostgreSQLの並列インデックス構築(Parallel Index Build)は、まさにそんな現場の救世主です。今回は、この機能の裏側で何が起きているのか、そしてなぜ時として期待通りのパフォーマンスが出ないのか。その深層を紐解いていきましょう。

並列構築のアーキテクチャ:協調作業の舞台裏

PostgreSQLが `CREATE INDEX` を並列で行うとき、実は単に「複数のワーカーがデータを拾う」という単純な構造ではありません。

1. リーダープロセスとパラレルワーカー: リーダープロセスが指示を出し、設定された `max_parallel_maintenance_workers` の範囲内でワーカープロセスが立ち上がります。
2. フェーズの分離:

  • スキャンフェーズ: テーブルのページをスキャンし、インデックスキーを抽出します。ここが並列化の最大の恩恵を受ける場所です。
  • ソートフェーズ: 各ワーカーが抽出したキーをソートします。
  • マージ・ビルドフェーズ: ソートされたデータを統合し、B-tree構造を構築します。
  • 最終化: リーダープロセスがインデックスをカタログに登録し、状態を書き換えます。

ここで重要なのは、「メモリの共有」と「競合の制御」です。各ワーカーは `maintenance_work_mem` を分け合う形で動作します。つまり、ワーカーを増やせば増やすほど、一人あたりに割り当てられるメモリが減るというトレードオフが常に存在します。

「あれ、速くないぞ?」と思った時に確認すべきこと

現場でよくある失敗談として、「CPUコアは余っているのに、並列構築が全く加速しない」というケースがあります。これにはいくつかの犯人がいます。

  • I/Oのボトルネック: 結局のところ、データはストレージから読み込まなければなりません。NVMe SSDならまだしも、低速なHDDや帯域が制限されたネットワークストレージ環境では、いくらCPUを並列化してもI/O待ちで詰まります。`iostat` でデバイスの使用率をチェックするのは基本中の基本ですね。
  • maintenance_work_mem の罠: 並列数を増やしすぎて、各ワーカーのメモリが枯渇すると、インデックス構築は「ディスクへのスピル(外部ソート)」を強いられます。これが発生した瞬間、構築時間は劇的に悪化します。`pg_stat_progress_create_index` ビューを眺めてみてください。ソートの状況がリアルタイムで追跡可能です。
  • インデックスの種類: B-tree以外、例えばGINやGiSTインデックスなどでは並列構築のサポート状況や効率が異なります。特に複雑なデータ型を扱う場合は、並列化によるオーバーヘッドが効くこともあります。

トラブルシューティングの勘所

もし構築が極端に遅い、あるいはシステム全体のパフォーマンスを著しく低下させているなら、以下の指標を疑ってください。

1. `pg_stat_progress_create_index` の活用:
このビューには、現在どれだけの「タプル」が処理され、どれだけの「ソート」が終わったかが刻まれています。ここを見て、「フェーズ」がどこで止まっているのかを特定してください。ソートフェーズで止まっているなら、メモリ不足の可能性が高いです。
2. ロック競合の確認:
`CREATE INDEX` は `SHARE` ロックを取得します。DDLや高頻度の書き込みが走っているテーブルでこれを実行すると、構築プロセス自体がロック待ちでストールします。構築時間そのものよりも、「何分間テーブルがロックされているか」という視点が、実戦では重要になります。
3. `max_parallel_maintenance_workers` の再考:
デフォルト設定のままで満足していませんか? 本番環境のデータ量とメモリ容量に合わせて、このパラメータをチューニングすることは、DBAとしての腕の見せ所です。

最後に:道具を使いこなすのは人間

並列インデックス構築は強力な武器ですが、銀の弾丸ではありません。大規模なテーブルほど、構築時の負荷がバックエンドのクエリに影響を与えます。

「速く終わらせる」ことは重要ですが、同時に「サービスに影響を与えない」バランスを見極めるのが、我々エンジニアの仕事です。構築前にテスト環境で同じデータ量・同じ設定でシミュレーションを行い、`maintenance_work_mem` のスイートスポットを探る。そんな当たり前の積み重ねが、結局は一番の近道になるはずです。

もし次に `CREATE INDEX` を叩くとき、裏側で繰り広げられるプロセスたちの協調作業に思いを馳せてみてください。きっと、DBの挙動が今までより少しだけ鮮明に見えてくるはずです。

コメント

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