変更要約: 初版(主題G3・G3.1〜G3.2)
3.2チューニングの実施
実行計画とSQLレベルのチューニングを学びます。Index Only ScanとVisibility Mapの関係、関数インデックス・部分インデックス、パーティショニング、プランナ制御のenable_*パラメータ(例enable_seqscan)・random_page_cost・hash_mem_multiplier、そしてCREATE INDEX CONCURRENTLY・FILLFACTOR・REINDEXによる無停止運用を押さえます。
パラメータ調整だけでは頭打ちになる局面では、実行計画とインデックス設計そのものに踏み込む必要があります。Gold試験では「なぜそのスキャン方式が選ばれるか」「どう設計すればプランナが望ましい計画を選ぶか」という、実行計画をコントロールする側の知識が問われます。
3.2.1Index Only Scan・関数インデックス・部分インデックス
- Index Only Scan=クエリが必要とする列がすべてインデックス内に含まれる場合に、テーブル本体(ヒープ)へのアクセスを省略できるスキャン方式。ただし可視性の確認のため、対象ページがVisibility Map上で「全行が全トランザクションから可視」と記録されていなければ結局ヒープを参照する必要がある。
- Visibility Map=各テーブルページについて「全タプルが全トランザクションに可視か」を1ビットで記録する補助構造で、VACUUM実行時に更新される。頻繁に更新される行が多いテーブルではビットが立たずIndex Only Scanの恩恵を受けにくいため、定期的なVACUUM(autovacuumの適切な動作)がIndex Only Scanの効果を左右する。
- 関数インデックス=
CREATE INDEX ... ON t (lower(col))のように式や関数の結果に対して作るインデックス。WHERE lower(email) = 'x'のような検索を高速化する(元の列そのままの索引では拾えない)。部分インデックス=CREATE INDEX ... ON t (col) WHERE status = 'active'のように条件を満たす行だけを対象に作る索引で、テーブルの一部にしか使わない検索を軽量・高速にできる。
3.2.2パーティショニング・プランナ制御・無停止運用
- パーティショニング=大テーブルを範囲(RANGE)・リスト(LIST)・ハッシュ(HASH)などの基準で複数の物理テーブルに分割する仕組み。パーティションプルーニングにより検索条件から不要なパーティションを走査対象から除外でき、また古いパーティション単位での高速な
DROPが保守作業を軽くする。 - enable_*パラメータ(例
enable_seqscan・enable_indexscan・enable_hashjoin等)=特定の実行計画手法をプランナの選択肢から一時的に外すデバッグ・検証用スイッチ。SET enable_seqscan = offで「なぜシーケンシャルスキャンが選ばれるのか」を切り分ける等の調査目的が中心で、恒久的な本番設定にはしない。 - random_page_cost=ランダムI/O(インデックス経由のアクセス等)の相対コストを表すプランナパラメータ(既定4.0、
seq_page_costの既定1.0との比)。SSD環境ではランダムアクセスがHDDほど遅くないため、1.1程度まで下げるとプランナがインデックススキャンをより選びやすくなる、というのが定番のチューニング。hash_mem_multiplier=ハッシュ系操作(Hash Join/Hash Aggregate)に対してwork_memの何倍まで使わせるかを制御する係数(既定1.0)。 - CREATE INDEX CONCURRENTLY=通常の
CREATE INDEXが取るテーブルへの書き込みロックを避け、既存の読み書きを妨げずにインデックスを作成する方式。本番稼働中のテーブルへの追加インデックスはこちらが定石だが、通常より時間がかかり、途中失敗時にINVALIDな不完全インデックスが残ることがあるため後始末(DROP INDEXしての作り直し)を要する場合がある。 - FILLFACTOR=テーブル(またはインデックス)のページに意図的に空き領域を残す割合(テーブル既定100=満杯まで使用)。HOT更新(Heap-Only Tuple)を狙って更新の多いテーブルで70〜90程度に下げておくと、更新時に同一ページ内へ新バージョンを配置しやすくなりインデックス更新やbloatを抑えられる。REINDEX=既存インデックスを作り直すコマンド(肥大化したインデックスの再構築、破損時の修復)。通常版は対象へのロックを伴うため、本番では
REINDEX CONCURRENTLY(PostgreSQL 12以降で利用可)を検討する。
「Index Only ScanはVisibility Mapが立っていないとヒープ参照が必要(=定期VACUUMが前提)」「関数インデックスは式に対する索引・部分インデックスは条件を満たす行だけの索引」「enable_*は調査用の一時スイッチで恒久設定にしない」「random_page_costはSSDなら下げてインデックススキャンを促す」「CREATE INDEX CONCURRENTLYは無停止だが失敗時にINVALIDが残りうる」「FILLFACTORを下げるとHOT更新が起きやすい」が最頻出です。
これらの手法は単発ではなく、組み合わせて初めて効く場面が多いのが実務の難しさです。例えば「特定ステータスの行だけを高頻度に検索するが、その列にBツリーインデックスを張ると更新のたびにインデックスも肥大化して困る」という相談では、部分インデックス(WHERE status = 'pending'のように対象を絞る)が第一候補になります。加えてそのテーブルが更新の多いワークロードなら、FILLFACTORを下げてHOT更新を狙うことで、部分インデックス自体の更新頻度も抑えられるという相乗効果があります。実行計画側では、「集計クエリでSELECT対象列がインデックスに含まれているのにテーブルアクセスが発生している」という不可解な挙動が報告されたら、まず疑うべきはVisibility Mapです。大量更新の直後でautovacuumがまだ走っていない場合、Visibility Mapのビットが立っておらずIndex Only Scanの恩恵が得られません。この場合はVACUUMを明示的に実行するか、autovacuum_vacuum_scale_factorのようなautovacuum関連パラメータの見直しで解決します。プランナ制御では、「SSD環境なのにインデックスが使われずシーケンシャルスキャンばかり選ばれる」という相談に対しては、random_page_costが既定の4.0のままでランダムI/OをHDD並みに高く見積もっていることが原因になりがちです。1.1程度へ下げることで統計に基づく現実的なコスト評価に近づけられますが、下げすぎるとインデックススキャンが過剰に選ばれ、逆に遅くなるケースもあるため、EXPLAIN ANALYZEで実測しながら段階的に調整します。本番への追加インデックスでは常にCREATE INDEX CONCURRENTLYを使うのが定石ですが、トランザクション内では実行できない制約があり、また途中でエラーが起きるとINVALIDな不完全インデックスが残置されるため、\dやシステムカタログで状態を確認し、必要ならDROP INDEXしてから再実行する運用が求められます。同様にREINDEX CONCURRENTLY(PostgreSQL 12以降)も肥大化したインデックスを無停止で作り直す定番手段で、通常のREINDEXが伴う排他ロックを避けたい本番環境ではこちらを優先します。
| 手法 | 目的 | 注意点 |
|---|---|---|
| 部分インデックス | 条件を満たす行だけを索引化 | 全件検索には使われない |
| CREATE INDEX CONCURRENTLY | 無停止でインデックス作成 | トランザクション内不可・失敗でINVALID残置 |
| random_page_cost | ランダムI/Oコストの見積り調整 | 下げすぎは逆効果 |
| FILLFACTOR低下 | HOT更新を促進しbloat抑制 | 空き領域分でテーブルサイズ増 |
ひっかけ: 「インデックスに必要な列がすべて含まれていれば常にIndex Only Scanになる」は誤りです。Visibility Mapでページが「全可視」と記録されていなければヒープ参照が発生します。また「CREATE INDEX CONCURRENTLYはトランザクションブロック内でも使える」も誤り=トランザクション内では実行できない制約があります。「enable_seqscan = off を本番設定として常用すればよい」も誤り=これは調査用の一時スイッチで、恒久的にシーケンシャルスキャンを禁止すると他の場面で不適切な計画を選ばせる副作用があります。
3.2.3この節のまとめ
- Index Only ScanはVisibility Map次第(=定期VACUUM前提)。関数インデックス=式に索引・部分インデックス=条件を満たす行のみ。パーティショニングはプルーニングと一括DROPが利点
- enable_*は調査用一時スイッチ・random_page_costはSSDで下げてインデックス選好・hash_mem_multiplierはハッシュ操作のwork_mem倍率。CREATE INDEX CONCURRENTLY/REINDEX CONCURRENTLYで無停止運用・FILLFACTOR低下でHOT更新促進
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. 集計クエリの選択列がすべて対象インデックスに含まれているにもかかわらず、EXPLAIN ANALYZEでテーブル本体へのアクセスが発生していることが分かった。直近に大量更新があり autovacuum がまだ完了していない状況として、最も妥当な原因は?
Q2. 本番稼働中のテーブルに新規インデックスを追加したい。書き込みロックで他のトランザクションをブロックしないために使うべき方法は?
Q3. SSDストレージのサーバでインデックスがほとんど使われず、常にシーケンシャルスキャンが選択されている。プランナの見積りを見直すために調整すべきパラメータは?

