PostgreSQLの全文検索、GINインデックスなしで戦うのはもうやめよう
現場でエンジニアをしていると、「検索機能が重い」という相談をよく受けます。DBのログを覗いてみると、案の定 `LIKE ‘%キーワード%’` でガリガリとテーブルをフルスキャンしているケースが後を絶ちません。
「最初は数万件だったから良かったけど、データが百万件を超えたら急に死んだ」
これ、あるあるですよね。でも大丈夫。PostgreSQLには、最初から強力な武器が備わっています。それが GIN (Generalized Inverted Index) インデックス です。今日は、現場で即戦力になる全文検索の設計術を伝授します。
—
なぜ普通のインデックスじゃダメなのか?
まず前提として、B-treeインデックスは「値の大小関係」を管理するのは得意ですが、テキストの「途中一致」や「文章の中の特定の単語」を探すのには向いていません。
そこで登場するのが「転置インデックス(Inverted Index)」です。これは、本の後ろにある「索引」をイメージしてください。どの単語が、どのドキュメントに含まれているかを逆引きできるようにする仕組みです。PostgreSQLでこれを実現するのがGINです。
ステップ1:tsvector列を用意する
PostgreSQLで全文検索をするには、検索対象のテキストを「トークン(単語)」のリストである `tsvector` 型に変換する必要があります。
例えば、こんなテーブルがあるとします。
CREATE TABLE articles (
id serial PRIMARY KEY,
title text,
content text,
— 検索用にtsvector型の列を用意する
search_vector tsvector
);
もちろん、都度 `to_tsvector()` で変換してもいいんですが、大規模データならあらかじめ計算して列に持たせておくのが鉄則です。更新時に自動で更新されるようにトリガーを仕込むのが、現場のスタンダードですね。
ステップ2:GINインデックスを貼る
ここが本題です。`search_vector` 列に対してインデックスを作成します。
CREATE INDEX idx_articles_search_vector ON articles USING GIN(search_vector);
これだけで、検索速度は劇的に変わります。数百万件のデータでも、コンマ数秒で結果が返ってくるようになるはずです。
実践:クエリの書き方
検索時は `@@` 演算子を使います。
SELECT title
FROM articles
WHERE search_vector @@ to_tsquery(‘english’, ‘PostgreSQL & indexing’);
`&` はAND条件です。`|` ならOR条件。これだけで、MySQLの `FULLTEXT` インデックス顔負けの高度な検索が実現できます。
—
現場でハマらないための「3つの心得」
ただ、教科書通りにやるだけだと、たまに落とし穴にハマります。先輩からのアドバイスだと思って覚えておいてください。
1. 更新コストを意識する
GINは強力ですが、インデックスの更新コストが高いです。頻繁に更新されるテーブルに貼ると、`UPDATE` が遅くなる原因になります。もし「更新はそこまで多くない」なら、`fastupdate` オプション(デフォルトでON)を調整して、書き込み負荷を抑えるのも手です。
2. 言語設定(辞書)を合わせる
`to_tsvector(‘english’, …)` のように、言語を指定するのを忘れないでください。日本語の場合、標準の `simple` 辞書だと助詞や接続詞まで検索対象になってしまい、精度が出ません。日本語を扱うなら `pg_bigm` などの拡張機能を入れるか、MeCab等で形態素解析してからインデックスする設計が必要になるケースが多いです。
3. 「とりあえずGIN」ではない
もし「前方一致だけでいい」という要件なら、`pg_trgm`(トリグラム)インデックスの方が適している場合もあります。要件に合わせてインデックスを使い分けるのが、一流のエンジニアの嗜みです。
—
まとめ:まずは試してみるのが一番
理屈をこねるより、まずは自分の開発環境で数万行のダミーデータを突っ込んで、`EXPLAIN ANALYZE` を叩いてみてください。`Seq Scan` が `Bitmap Index Scan` に変わった瞬間のあの快感、ぜひ味わってほしいですね。
もし「日本語の検索で詰まった」「インデックスサイズが肥大化して困っている」といった悩みがあれば、いつでも相談してください。DBのチューニングは奥が深くて面白いですよ。
それでは、良いDBライフを!
コメント