|
| 1 | +--- |
| 2 | +title: "PostgreSQLの全文検索機能を試してみる" |
| 3 | +date: 2025/08/29 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - PostgreSQL |
| 7 | + - 全文検索 |
| 8 | + - Go |
| 9 | + - Prisma |
| 10 | + - kagome |
| 11 | +category: |
| 12 | + - Programming |
| 13 | +thumbnail: /images/20250829a/thumbnail.png |
| 14 | +author: 澁川喜規 |
| 15 | +lede: "全文検索機能がPrismaにも標準で用意されているということを知りました。PostgreSQLで全文検索はというと、PGroongaとか、pg_bigmを使うとかがトップ出てくるし、そもそも検索をしたくなったらElasticSearch使う、みたいに思っていました。標準で全文検索もできるなら運用コストもだいぶ下げられそうです。" |
| 16 | +--- |
| 17 | + |
| 18 | +<img src="/images/20250829a/top.png" alt="" width="600" height="600"> |
| 19 | + |
| 20 | +[夏の自由研究2025](/articles/20250825a/)ブログ連載の4日目です。 |
| 21 | + |
| 22 | +技術コンサルをしているお客さんとPrismaのドキュメントの読書会をしていて、全文検索機能が[Prismaにも](https://www.prisma.io/dataguide/managing-databases/intro-to-full-text-search)、[PostgreSQLにも](https://www.postgresql.org/docs/current/textsearch.html)標準で用意されているということを知りました。PostgreSQLで全文検索はというと、PGroongaとか、pg_bigmを使うとかがトップ出てくるし、そもそも検索をしたくなったらElasticSearch使う、みたいに思っていました。 |
| 23 | + |
| 24 | +標準で全文検索もできるなら運用コストもだいぶ下げられそうです。かつて、Python製ドキュメントツールの、ブラウザで動く全文検索エンジンの日本語対応をやってみたり、[転置インデックスをS3に置く検索エンジン](/articles/20200327/)を作ってみたり~~貧乏~~低コスト検索エンジンの第一人者(自称)としては試してみたいところです。 |
| 25 | + |
| 26 | +ものは試しでやってみました。 |
| 27 | + |
| 28 | +# PostgreSQLの全文検索機能 |
| 29 | + |
| 30 | +PostgreSQLの全文検索では、`LIKE`とか`ILIKE`で検索するみたいに、 `@@`で転置インデックスを検索する演算子が提供されており、それを呼び出します。検索するフィールドは`to_tsvector()`、検索ワードは`to_tsquery()`関数に渡して前処理をするところがポイントですかね。 |
| 31 | + |
| 32 | +```sql |
| 33 | +SELECT title |
| 34 | +FROM pgweb |
| 35 | +WHERE to_tsvector(title || ' ' || body) @@ to_tsquery('create & table') |
| 36 | +ORDER BY last_mod_date DESC |
| 37 | +LIMIT 10; |
| 38 | +``` |
| 39 | + |
| 40 | +テーブルの中にもtsvectorを作ってあげます。 |
| 41 | + |
| 42 | +```sql |
| 43 | +CREATE TABLE articles ( |
| 44 | + id SERIAL PRIMARY KEY, |
| 45 | + title TEXT NOT NULL, |
| 46 | + content TEXT NOT NULL, |
| 47 | + file_path TEXT NOT NULL UNIQUE, |
| 48 | + author TEXT NOT NULL DEFAULT '', |
| 49 | + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), |
| 50 | + processed_text TEXT NOT NULL, |
| 51 | + search_vector tsvector GENERATED ALWAYS AS ( |
| 52 | + to_tsvector('simple', coalesce(title, '') || ' ' || coalesce(processed_text, '')) |
| 53 | + ) STORED |
| 54 | +); |
| 55 | +``` |
| 56 | + |
| 57 | +なお、全文検索エンジンあるあるテーマが日本語対応で、大抵は英語のようにスペース区切りの単語で分割し、転置インデックスという、単語→ドキュメントのインデックスを作り、それを元に検索をするという仕組みです。その過程で、英語やドイツ語などの言語ごとに正規化(ステミング)、冠詞などのノイズになる単語(stop word)をフィルタリングなど、自然言語ごとの前処理を行います。そのあたりの設定は[ここ](https://www.postgresql.org/docs/current/textsearch-dictionaries.html)に書かれています。 |
| 58 | + |
| 59 | +標準でサポートしている言語は17.5で以下のような感じです。 |
| 60 | + |
| 61 | +```sh |
| 62 | +/usr/local/share/postgresql/tsearch_data # ls *.stop |
| 63 | +danish.stop finnish.stop hungarian.stop norwegian.stop spanish.stop |
| 64 | +dutch.stop french.stop italian.stop portuguese.stop swedish.stop |
| 65 | +english.stop german.stop nepali.stop russian.stop turkish.stop |
| 66 | +``` |
| 67 | + |
| 68 | +デフォルトでは日本語のようなスペース区切りではない言語は対応できません。当然日本語の文法ルールもなく、stop wordの辞書もありません。この機能も、昔は日本語対応のtextsearch_jaというモジュールがあったようですが、今はメンテナンスされていないようです。仮にされていたとしても、DB運用はクラウドにお任せ時代なので最初から導入されている方が運用は楽でしょう。 |
| 69 | + |
| 70 | +なんとかして使ってみる方法として、事前に、DBの外でスペース区切りにしてからテーブルに入れてみるという前処理をする方法を試してみます。 |
| 71 | + |
| 72 | +# GoでPostgreSQLの全文検索で日本語検索してみる |
| 73 | + |
| 74 | +Goでは分かち書きのライブラリとして定評のある[github.com/ikawaha/kagome/v2](https://pkg.go.dev/github.com/ikawaha/kagome/v2)を使います。 |
| 75 | + |
| 76 | +kagomeではこんな感じではこんな感じに |
| 77 | + |
| 78 | +```sh |
| 79 | +$ kagome |
| 80 | +シグナルを送信した |
| 81 | +シグナル 名詞,一般,*,*,*,*,シグナル,シグナル,シグナル |
| 82 | +を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ |
| 83 | +送信 名詞,サ変接続,*,*,*,*,送信,ソウシン,ソーシン |
| 84 | +し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ |
| 85 | +た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ |
| 86 | +``` |
| 87 | + |
| 88 | +助詞とか助動詞はひかっかりまくるので削除し、動詞などは原形にすることで、「送信する」「送信した」などの表記揺れでもひっかかるようになります。分かち書きでは品詞もわかるのでこれを使って、助詞、助動詞、副詞などを除外すれば良いでしょう。簡単ですね。 |
| 89 | + |
| 90 | +# 実装 |
| 91 | + |
| 92 | +ソースコードは[github.com/shibukawa/pgtfs](https://github.com/shibukawa/pgtfs)です。生成AIでサッと作りました。ライセンスはUnlicenseです。full text searchだとftsのはずですが、スペルを間違ったのでtfsになってます。CLIツールとなっています。実証実験のコードなので、インデックスのメンテナンスとか考えずに、一発投入のみ。 |
| 93 | + |
| 94 | +```sh |
| 95 | +# PostgreSQLの起動 |
| 96 | +$ docker compose up |
| 97 | + |
| 98 | +# DBを初期化して./articles以下のテキストファイルをDBに投入 |
| 99 | +$ ./pgtfs init |
| 100 | + |
| 101 | +# 検索 |
| 102 | +$ ./pgtfs search "検索用語" |
| 103 | +``` |
| 104 | + |
| 105 | +サンプルドキュメントも生成AIが用意してくれました。 |
| 106 | + |
| 107 | +```md golang.md |
| 108 | +# Goプログラミング言語 |
| 109 | + |
| 110 | +Goは、Googleが開発したプログラミング言語です。 |
| 111 | + |
| 112 | +## 特徴 |
| 113 | +- シンプルな文法 |
| 114 | +- 高速なコンパイル |
| 115 | +- 優れた並行処理機能 |
| 116 | +- ガベージコレクション |
| 117 | + |
| 118 | +## 活用分野 |
| 119 | +Goは以下の分野で広く使用されています: |
| 120 | +- Webサービス開発 |
| 121 | +- クラウドインフラストラクチャ |
| 122 | +- コマンドラインツール |
| 123 | +- マイクロサービス |
| 124 | + |
| 125 | +ゴルーチンとチャネルを使った並行プログラミングが魅力的です。 |
| 126 | +``` |
| 127 | + |
| 128 | +内部では次のように区切られてスペース区切りにされて保存されます。 |
| 129 | + |
| 130 | +```text |
| 131 | +#|Go|プログラミング|言語|Go|Google|開発|し|プログラミング|言語| |
| 132 | +##|特徴|-|シンプル|文法|-|高速|コンパイル|-|優れ|並行|処理|機能| |
| 133 | +-|ガベージコレクション|##|活用|分野|Go|以下|分野|広く|使用|さ|れ|い| |
| 134 | +-|Web|サービス|開発|-|クラウドインフラストラクチャ|-|コマンドラインツール| |
| 135 | +-|マイクロ|サービス|ゴルーチン|チャネル|使っ|並行|プログラミング|魅力|的 |
| 136 | +``` |
| 137 | + |
| 138 | +「Web開発」で検索します。この単語は文中にはありません。「Webサービス開発」ならあります。うまく分かち書きされると単語がヒットしてくれるはず!!! |
| 139 | + |
| 140 | +```shell |
| 141 | +$ ./pgtfs search "Web開発" |
| 142 | +Searching for: "Web開発" |
| 143 | +Found 1 articles: |
| 144 | + |
| 145 | +=== Result 1 (Rank: 0.0989) === |
| 146 | +Title: golang |
| 147 | +File: articles/golang.md |
| 148 | +``` |
| 149 | + |
| 150 | +きた! |
| 151 | + |
| 152 | +# より使いやすい検索にするには |
| 153 | + |
| 154 | +分かち書きとstop wordは最低限です。実際にElastichsearchで使われるKuromojiではそれ以外に漢数字を算用数字にしたりさまざまなフィルタを提供しています。 |
| 155 | + |
| 156 | +* [ZOZO TECH BLOG: Elasticsearchで日本語検索を扱うためのマッピング定義](https://techblog.zozo.com/entry/elasticsearch-mapping-config-for-japanese-search#kuromojiプラグイン機能) |
| 157 | + |
| 158 | +これにより、表記揺れでもヒットしやすいようになります。PostgreSQLの検索機能も類義語辞書が持てるようになっているため、日本語でも頑張って集めることでさらに精度を上げる余地があります。最終的にはベクトル検索を実装して類似文書検索ですかね。いつかはやってみたい。 |
| 159 | + |
| 160 | +# まとめ |
| 161 | + |
| 162 | +かんたんな前処理でPostgreSQLの標準の検索機能が使えました。これでコストを増やさずに全文検索機能が簡単に組み込めるでしょう。bigmなんかは正規の単語の区切りではないところでもひっかかってしまったりというのがあり、やはりきちんと分かち書きをした検索エンジンが使いたいですよね?どのような言語でもたいてい分かち書きのライブラリはあると思うので言語問わず利用できると思います。 |
| 163 | + |
| 164 | +[PostgreSQLにはPub/Sub機能もある](/articles/20240628a/)ので、DBさえあれば他のマネージドサービスは要らない、という時代がそのうち来るんじゃないかと思っているところです。 |
0 commit comments