やあ。最近、PostgreSQLで「全文検索」を実装する機会はあったかな?
「LIKE検索で頑張ってるけど、データ量が増えてきてクエリが重い……」とか、「外部の検索エンジン(Elasticsearchとか)を入れるほどじゃないけど、もう少し気の利いた検索がしたい」なんて悩み、エンジニアなら一度は通る道だよね。
今日はそんな君のために、PostgreSQLが標準で持っている強力な武器、`to_tsvector` について深掘りしてみようと思う。これを知っているだけで、DBの検索パフォーマンスと体験はガラッと変わるはずだよ。
—
to_tsvector って結局何者?
簡単に言うと、「人間が読むための文章を、PostgreSQLが高速に検索するための『辞書データ』に変換する関数」だ。
例えば、`to_tsvector(‘japanese’, ‘PostgreSQLは世界最高峰のデータベースです’)` と叩くと、こんな感じの `tsvector` 型データが返ってくる。
SELECT to_tsvector(‘japanese’, ‘PostgreSQLは世界最高峰のデータベースです’);
— 結果: ‘postgresql’:1 ‘世界最高峰’:4 ‘データベース’:6
見ての通り、単語ごとに分解して、さらに位置情報(`:1` とか)を付与してくれているよね。これをインデックスに登録しておくことで、文字列の「あいまい検索」ではなく、「単語の存在確認」という非常に高速な検索が可能になるんだ。
—
実践:どうやって実務で活用するのか
単に変換するだけじゃ意味がない。実務で使うなら「インデックス」とセットにするのが基本ルールだ。
例えば、ブログの投稿テーブル(`posts`)があるとして、`content` カラムを全文検索したい場合はこんな構成にするのが定石だよ。
1. 生成列(Generated Columns)を使うのが今風
PostgreSQL 12以降なら、検索用のカラムを生成列として持たせるのが一番スマートだ。
ALTER TABLE posts ADD COLUMN content_vector tsvector
GENERATED ALWAYS AS (to_tsvector(‘japanese’, content)) STORED;
こうしておけば、`content` が更新されるたびに、自動的に `content_vector` も更新される。これにGINインデックスを貼るのが、僕らエンジニアの「勝ちパターン」だ。
CREATE INDEX idx_posts_content_vector ON posts USING GIN(content_vector);
2. 検索クエリを投げる
検索するときは `to_tsquery` とセットで使う。ここが少しコツがいるところなんだけど、こんな風に書くんだ。
SELECT FROM posts
WHERE content_vector @@ to_tsquery(‘japanese’, ‘PostgreSQL & データベース’);
`@@` 演算子を使っているのがポイント。これでインデックスがバッチリ効く。`LIKE ‘%…%’` とは比較にならないスピードが出るはずだよ。
—
現場でハマるポイント:ここだけは注意して!
ここまで読んで「よし、明日から全部これに置き換えよう!」と思った君、ちょっと待って。いくつか落とし穴があるんだ。
- 辞書ファイルの選定:
標準の `japanese` 設定も悪くないんだけど、本格的なプロダクトなら `pgroonga` や、より精度の高い形態素解析器(MeCabなど)を組み込むことも検討したほうがいい。標準設定はあくまで「入門」と割り切るのも大切だ。
- 更新のコスト:
`tsvector` は便利だけど、当然ながらデータを追加・更新するたびに変換コストがかかる。書き込みが異常に多いテーブルだと、インデックスの更新がボトルネックになる可能性があるから、そこは負荷試験で確認が必要だね。
- ストレージ容量:
インデックスはタダじゃない。巨大なテキストデータに対してGINインデックスを貼ると、それなりの容量を食う。ストレージと検索速度のトレードオフを、ビジネス要件に合わせて判断するのが「プロ」の仕事だ。
—
最後に:完璧主義にならないこと
全文検索って、突き詰めると無限に奥が深いんだ。類義語の辞書を作ったり、重み付け(Ranking)を調整したり……。でも、まずは `to_tsvector` を使って「今の検索よりちょっと速くて、ちょっと賢い」ところから始めてみてほしい。
もし実装中に「思った通りに検索がヒットしない!」という壁にぶつかったら、まずは `ts_debug` 関数で、その文章がどう解析されているのかを覗いてみるといいよ。
SELECT FROM ts_debug(‘japanese’, ‘PostgreSQLは世界最高峰のデータベースです’);
これを使えば、辞書がどうトークンを認識しているか一発でわかる。これぞ、デバッグの近道だ。
それじゃあ、また現場で会おう。いい設計を!
コメント