インデックスの「設計図」を読み解く:pg_indexの深淵へ
PostgreSQLを長年触っていると、誰しも一度は `pg_class` や `pg_attribute` といったシステムカタログの迷宮に足を踏み入れることになるはずです。その中でも、インデックスの「実体」と「論理」を繋ぐ要、それが `pg_index` です。
単に `CREATE INDEX` を叩くだけならこのテーブルを意識する必要はありません。しかし、クエリプランナがなぜそのインデックスを選んだのか、あるいはなぜ「見えているはずなのに使われない」のか。その答えの多くは、`pg_index` が握っています。
今日は、PostgreSQLのインデックス管理の心臓部について、少し解像度を上げて語ってみようと思います。
—
pg_indexは「メタデータ」の交差点
`pg_index` を単なる「インデックスの一覧表」だと思っているなら、それは少しもったいない見方です。このテーブルは、PostgreSQLがインデックスをどのように評価し、どのようにスキャンすべきかを決定するための「指針」そのものです。
重要なカラムをいくつかピックアップしてみましょう。
- `indrelid` / `indexrelid`: インデックスとテーブルのOidです。ここを `pg_class` とJOINするのは基本中の基本ですが、結合の方向性を間違えると、大規模DBでは統計情報取得時に妙な負荷をかけることがあります。
- `indkey`: これこそが `pg_index` の真髄です。インデックスがどのカラムで構成されているかを示す `int2vector` 型の配列ですが、ここに `0` が含まれている場合、それは「式インデックス(Expression Index)」を意味します。
- `indisunique` / `indisprimary`: 一意性制約のフラグです。ここで注意すべきは、`pg_index` の状態が、物理的なインデックスの生成状態と乖離していないかを確認する必要があるケースです(例えば、`CREATE INDEX CONCURRENTLY` が失敗して放置された場合など)。
「使われないインデックス」の正体を暴く
パフォーマンスチューニングの現場で一番多い相談が「インデックスを貼ったのにクエリが速くならない」というものです。ここで `pg_index` を活用したデバッグが役に立ちます。
特に注目すべきは `indpred` です。これは部分インデックス(Partial Index)の述語を格納しているカラムですが、ここが適切に設定されていないためにプランナが「コストが高い」と判断しているケースをよく見かけます。
もし、特定のクエリでインデックスが無視されるなら、以下の視点でシステムカタログを掘ってみてください。
1. データ型の不一致: `indkey` で参照されている列の型と、クエリで投げられている値の型が一致しているか?
2. 演算子クラスのミスマッチ: `pg_index` の `indclass` を見れば、そのインデックスがどの演算子クラス(オペレータクラス)を使っているかが分かります。デフォルトのB-tree以外の演算子を使っている場合、プランナがインデックススキャンを選択できないケースがあります。
3. 統計情報の鮮度: `pg_stats` と `pg_index` を突き合わせ、インデックスの選択率(Selectivity)が妥当か確認します。`pg_index` は定義しか持っていませんが、プランナはその定義に基づき `pg_stats` を参照して実行計画を立てるからです。
熟練エンジニアへのアドバイス:カタログの直接参照は「劇薬」
たまに、「システムカタログを直接書き換えてインデックスの順序をいじれないか?」といった相談を受けます。
絶対にやめてください。
`pg_index` はPostgreSQLの内部整合性の守護神です。ここを弄ることは、データベースの整合性を自ら破壊する行為に等しい。もしインデックスの構成を変えたいなら、一度 `DROP` し、定義し直すのが唯一にして正当な道です。
ただし、読み取り専用の分析としては非常に強力です。例えば、以下のクエリは、本番環境で「使われていないインデックス」を特定するための第一歩になります(`pg_stat_user_indexes` と組み合わせるのがポイントです)。
SELECT
t.relname AS table_name,
i.relname AS index_name,
idx.indisunique,
idx.indisprimary
FROM pg_index idx
JOIN pg_class t ON t.oid = idx.indrelid
JOIN pg_class i ON i.oid = idx.indexrelid
WHERE t.relkind = ‘r’
AND idx.indisready = true
AND idx.indisvalid = true
— ここに pg_stat_user_indexes をJOINして idx_scan 数を確認する
最後に
`pg_index` を理解するということは、PostgreSQLの「インデックスがどう世界を見ているか」を理解することと同義です。派手な機能ではありませんが、この小さなシステムカタログこそが、数千万レコードの海からミリ秒単位でデータを取り出すための地図なのです。
もし皆さんの現場で、理解不能な実行計画に遭遇したら、ぜひ一度 `pg_index` を覗いてみてください。そこには、あなたがSQLで書いた「意図」が、PostgreSQLの「解釈」として克明に記されているはずです。
それでは、また次回の深掘りでお会いしましょう。Happy Hacking!
コメント