大規模テーブルのインデックス作成で「詰んだ」経験、ありませんか?
現場でPostgreSQLを触っていると、避けて通れないのが「テーブルの巨大化」ですよね。数千万、あるいは数億行あるテーブルに対して、ふと「あ、このカラムにインデックス貼らないとパフォーマンスが死ぬわ」と気づいた瞬間。
`CREATE INDEX` を叩いて、プログレスバーが永遠に動かないのを見つめながら、「これ、終わるの明日じゃないか?」と冷や汗をかいた経験、一度はあるはずです。
今日はそんな絶望を救ってくれる、PostgreSQLの「並列インデックス構築(Parallel Index Build)」について、現場の知恵を共有しようと思います。
—
そもそも、何がそんなに遅いのか?
昔のPostgreSQLや、設定を意識していない環境だと、`CREATE INDEX` は基本的に「シングルスレッド」で動きます。せっかくサーバーが32コアのCPUを積んでいても、インデックス構築のために使われるのはたったの1コア。これじゃあ、今のハードウェアのポテンシャルをドブに捨てているようなものです。
そこでPostgreSQL 11以降に導入されたのが、この並列インデックス構築です。複数のバックエンドプロセスを立ち上げて、インデックスのソートやスキャンを分担させることで、構築時間を劇的に短縮できます。
実践:どうやって使うのか?
基本的には、PostgreSQLがいい感じに判断して自動で並列化してくれます。`max_parallel_maintenance_workers` という設定値が鍵ですね。
ただ、自分で明示的に「今は全力を出したい!」というときは、SQLのオプションで制御できます。
— 基本形:自動判断
CREATE INDEX idx_user_logs_created_at ON user_logs (created_at);
— 現場でよくやる:ワーカー数を明示的に指定する場合
— 4つのCPUコアを割り当てて構築する例
CREATE INDEX CONCURRENTLY idx_user_logs_created_at
ON user_logs (created_at)
WITH (parallel_workers = 4);
注意点:`CONCURRENTLY` との組み合わせ
実務で一番大事なのが、これ。「本番環境でテーブルをロックしたくない」という理由で `CONCURRENTLY` をつけることがほとんどだと思います。
実は、`CONCURRENTLY` を使うと、並列構築のパフォーマンスは少しだけ落ちます。なぜなら、データの整合性を保ちながら読み書きを並行して行うためのオーバーヘッドがあるからです。でも、「サービスを止めない」ことはエンジニアの鉄則。少々時間がかかっても、基本的には `CONCURRENTLY` をつける癖をつけておきましょう。
パフォーマンスを最大化するためのTips
ただ「並列化すれば速い」と脳死で設定するのは危険です。現場で調整するときは、以下の3つを気にしてみてください。
1. `maintenance_work_mem` をケチらない
並列構築では、各ワーカーがメモリを使います。この値が小さいと、せっかく並列化しても「メモリ不足でソートがディスクに溢れる(=遅くなる)」という悲劇が起きます。構築時だけ一時的に大きく設定するのが定石です。
SET maintenance_work_mem = ‘2GB’; — 構築前に一時的に増やす
CREATE INDEX CONCURRENTLY …;
2. CPUの空き状況と相談する
DBサーバーがWebアプリのレスポンス処理で忙しいときに、`parallel_workers` を8とか16に設定すると、一気にリソースを食いつぶしてアプリが重くなります。構築する時間帯、あるいは負荷を見ながらワーカー数を調整する「余裕」が、プロの仕事です。
3. `pg_stat_progress_create_index` を覗く
構築中に「今どこまで進んでるの?」と不安になったら、このシステムビューを叩きましょう。
SELECT phase, lockers_total, lockers_done FROM pg_stat_progress_create_index;
進捗が見えると、精神的な安定感が違いますよ。
—
最後に:ツールに頼りすぎない判断力を
並列インデックス構築は強力な武器です。でも、一番の最適化は「本当にそのインデックスが必要か?」を疑うことだったりします。
「とりあえずインデックスを貼る」前に、`EXPLAIN ANALYZE` で実行計画を見て、本当にフルスキャンがボトルネックになっているのか確認する。その上で、大規模データの構築という壁にぶち当たったら、迷わず並列化オプションを切る。
これが、僕が現場で大切にしているデータモデリングの流儀です。
もし「構築中にメモリが溢れてサーバーが落ちた…」なんて苦い経験をした方がいたら、次はぜひ `maintenance_work_mem` を少し多めに確保して、再チャレンジしてみてください。応援しています!
コメント