こんにちは。DBエンジニアの視点から、今日はPostgreSQLの「裏の主役」とも言えるGIN(Generalized Inverted Index)インデックスについて深掘りしていこうと思います。
現場で「JSONBのクエリが遅いんだよね」とか「全文検索をサクッと実装したい」という相談を受けたとき、真っ先に僕が提案するのがこのGINです。ただ、こいつはB-treeインデックスと違って少しだけ「クセ」がある。その仕組みと、実務でハマらないためのコツを共有しますね。
—
GINって結局なんなの?
一言でいうと、「中身をバラバラにして逆引き辞書を作る仕組み」です。
通常のB-treeは「値そのもの」をソートして保持しますが、GINは違います。例えば、あるカラムに「{ “tags”: [“ruby”, “postgres”, “fast”] }」というJSONデータが入っていたとしましょう。
B-treeならそのデータ全体をひとつの塊として扱いますが、GINは「ruby」「postgres」「fast」という各要素を分解して、「どの行にこの単語が含まれているか」というリスト(転置インデックス)を内部で構築します。
だからこそ、「配列の中に特定の要素が含まれているか?」とか「JSONBの中の特定のキーがあるか?」といった、部分一致や包含関係の検索で爆速なパフォーマンスを発揮するんです。
—
実践:JSONBとGINの黄金コンビ
最近のシステムでJSONBを使わない日はありませんよね。でも、JSONBの中身を検索するなら、インデックスを貼らないとフルスキャン一直線です。
例えば、こんなテーブルがあるとします。
CREATE TABLE items (
id serial PRIMARY KEY,
attributes jsonb
);
— 普通にインデックスを貼ろうとすると…
CREATE INDEX idx_items_attributes ON items (attributes); — これはB-tree。実はこれではJSON内の検索には効かないことが多い!
ここで登場するのが`gin`クラスです。
CREATE INDEX idx_items_attributes_gin ON items USING GIN (attributes);
これで、以下のクエリが劇的に速くなります。
SELECT FROM items WHERE attributes @> ‘{“color”: “red”}’;
`@>`(包含演算子)を使うことで、GINインデックスが内部の「転置辞書」を引いて、一瞬で該当行を特定してくれます。これぞGINの真骨頂ですね。
—
現場で気をつけるべき「負の側面」
「じゃあ、とりあえず全部GINにしとけばいいじゃん!」と思うかもしれませんが、ここが落とし穴。GINには明確な弱点があります。
1. 更新がとてつもなく重い
B-treeはデータ更新時にそのノードを書き換えるだけですが、GINは「バラバラにした要素」それぞれを更新し、転置リストを再構築する必要があります。頻繁に`UPDATE`や`INSERT`が走るカラムにGINを貼ると、書き込み負荷でDBが悲鳴を上げます。
2. インデックスサイズが大きい
要素を分解して保持する以上、どうしてもインデックスの容量は膨れ上がります。ストレージのコストと相談が必要です。
解決策:`fastupdate` を活用する
PostgresのGINにはデフォルトで`fastupdate = on`というオプションが付いています。これは、更新を一度保留して、ある程度溜まってから一気にインデックスに反映させる仕組みです。
書き込みが多い環境で、もしインデックスのせいでDBが重いなと感じたら、まずはこのオプションの状態を確認してみてください。
—
全文検索での活用
GINは全文検索エンジンとしてもかなり優秀です。`tsvector`型と組み合わせることで、Googleのような検索機能がPostgresだけで実装できます。
— 全文検索用のGINインデックス
CREATE INDEX idx_fts_content ON posts USING GIN (to_tsvector(‘japanese’, content));
— クエリ
SELECT FROM posts
WHERE to_tsvector(‘japanese’, content) @@ to_tsquery(‘japanese’, ‘PostgreSQL & インデックス’);
外部の検索エンジン(Elasticsearchなど)を入れるほどではないけれど、`LIKE ‘%検索語%’`だと遅すぎてどうしようもない……そんな「中規模な悩み」を解決するのに、GINは最強の武器になります。
—
今日のまとめ
- GINは「逆引き辞書」。 JSONBや配列、全文検索の救世主です。
- 読み込みは爆速、書き込みは慎重に。 頻繁に更新されるカラムに貼る場合は、パフォーマンスへの影響を必ず計測しましょう。
- `USING GIN`を忘れずに。 `CREATE INDEX`でデフォルトのB-treeに逃げないよう注意してくださいね。
DB設計は、こういった「仕組みの特性」を知っているかどうかで、数年後のシステムの寿命が大きく変わります。ぜひ、次のプロジェクトで「ここ、GINでいけるんじゃないか?」と試してみてください。
また現場でお会いしましょう!何か詰まったことがあれば、いつでも聞いてくださいね。
コメント