【実務・中級編】 to_tsvector関数 – PostgreSQL

やあ。最近、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は世界最高峰のデータベースです’);

これを使えば、辞書がどうトークンを認識しているか一発でわかる。これぞ、デバッグの近道だ。

それじゃあ、また現場で会おう。いい設計を!

コメント

タイトルとURLをコピーしました