Mattermost, Inc.

MySQL and Postgres to support Japanese search

日本語での全文検索についてです。

see:

個人的に動作確認できたのは、

  • MySQL 5.7.9+
    • ngram Full-Text Parser を使ったN-Gram方式
    • MeCab Full-Text Parser Plugin を使った形態素解析方式も、たぶんできると思います。
  • Postgres 9.4.x
    • fulltextsearch_ja + Mecab を使った形態素解析方式
    • pg_bigmの方はMattermost本体のSQLをts_queryの方式から普通のLike検索にしないといけない?

というあたりでしょうか。

いろいろ情報交換できると嬉しいです。

こんばんは!よろしくお願いします。

pg_bigmの方はMattermost本体のSQLをts_queryの方式から普通のLike検索にしないといけない?

現状のクエリから注目すべきWHERE句だけ抜き出して簡単化すると、次のようなSELECT文になるかと思います。

SELECT MESSAGE FROM POSTS WHERE MESSAGE @@ to_tsquery('検索語');

これは、下記引用部の説明により、次のSELECT文と等価です。

SELECT MESSAGE FROM POSTS WHERE to_tsvector(MESSAGE) @@ to_tsquery('検索語');

PostgreSQL9.5.1文書 12.1.2. 基本的なテキスト照合:

@@演算子は、textを入力として受付けるので、簡単に使うときには、明示的にテキスト文字列をtsvectorまたはtsqueryに変換することを省略できます。
[中略]
text @@ tsqueryという形式は、to_tsvector(x) @@ yと同じです。

そしてこのSELECT文は、次の説明にある通りindexが使用されません。

PostgreSQL9.5.1文書 12.2.2. インデックスの作成:

[前略]上記のインデックスでは、2引数バージョンのto_tsvectorが使われているので、同じ設定名の2引数バージョンのto_tsvectorを使う問合わせ参照だけがそのインデックスを使います。すなわち、WHERE to_tsvector(‘english’, body) @@ 'a & b’はインデックスが使えますが、WHERE to_tsvector(body) @@ 'a & b’は使えません。これにより、インデックスエントリを作ったときの設定と、同じ設定のときだけインデックスが使われることが保証されます。

今回使用しようとしているindex idx_posts_message_txt'english'指定で作成されているため、to_tsvector第1引数に同じ'english'を明示することで、Index Scanが行えるようになります。

 SELECT MESSAGE FROM POSTS WHERE to_tsvector('english', MESSAGE) @@ to_tsquery('検索語');

英文検索のみを考えた場合、これでindexが使用され想定通りの動作になると思われます。
ただ、私がやりたいことは日本語検索なので、index作成のところのように'english'決め打ちされると困るなあ、むしろto_tsvector'english'決め打ちされてしまうと、現状textsearch_jadefault_text_search_config設定で実現できている Seq Scan方式すらできなくなってしまうなあ…というのが Issue #2622 の最後で言いたかったことになります。

'english'決め打ちではなく、予めどこかで設定しておいた値を参照するような修正であれば、英語以外の言語でも利用可能になるのではと考えます。

(すみません、文中もうちょっとちゃんと引用先リンクを貼りたかったのですが、新人は2リンクしか駄目なようで…)

@yukihane

二通りの方法がある、という認識であっていますでしょうか?

A案

  1. to_tsvector(‘english’, MESSAGE) を to_tsvector(MESSAGE) に変更
  2. to_tsvectorの’english’部分はpostgres.confのdefault_text_search_config で指定する

B案

  1. to_tsvector(‘english’, MESSAGE) のハードコーディングされている’english’ を、configファイルか何かに外出しする
  2. 1.の’english’ の部分を ‘japanese’ (?)に書き換える

上記のIssueの内容としては、
「A案だと他のpostgresを利用しているソフトウェア(e.g. GitLab)にも影響する可能性があるので、B案が良い」
ということですかね(本当英語ダメなんで間違ってたらごめんなさい)

個人的にはいいと思うんですが、N-gramって日本語以外のマルチバイト文字列でも使えるはずなので、その辺も考慮できればよりいいんじゃないでしょうか。

元々は、(GitLab CEと一緒にyumパッケージ管理したいので)どうすればMattermostのソースコードを変更せずに日本語検索対応できるのだろう、というのが考え始めたきっかけです。

最初考えたのは、MySQLと同様(Issue #2033)N-gram indexを張れば良いのでは、というものですが、

  • PostgreSQLではそもそもindexが使用されていない(Issue #2622)
  • PostgreSQLでの N-gram 検索は 照合演算子(@@) ではなく LIKE を使用する

ためソース変更しない前提では不可能であることが分かりました。

そこで、次に考えたのが形態素解析による検索で、これはtextsearch_jaとMeCabを使うことで実現できましたのでIssue #2159にFYIとして記載しました。

以上が経緯になります。

個人的にはいいと思うんですが、N-gramって日本語以外のマルチバイト文字列でも使えるはずなので、その辺も考慮できればよりいいんじゃないでしょうか。

最終的に本体に取り込んでもらえるのであれば、私もN-gram検索できるよう変更するのが望ましいと思います。(ググった限り、textsearch_ja & MeCab 方式はちょっとレガシーみたいですし…)
ただ、英語圏の人にとってはN-gramのLIKE検索は使い勝手が悪いと思うので、現在の@@検索とどちらを使うか選択できるようになっていなければならないのでは、とも思います。


(以下、N-gramの話は置いておいて、@@検索について)

私が現在理解している範囲においては、理想的には、暗黙的に使用されるdefault_text_search_config設定に依存すべきでなく、すべてのto_tsvectorに対して明示的にregconfigパラメータ('english''japanese')が指定されるべきであると考えています。

なぜなら、regconfigを明示しない場合にはIndex ScanされずSeq Scanで実行されるため、パフォーマンス上の懸念となるためです。
(注: 実際どの程度パフォーマンスに差異があるのか計測したわけではなく、まあそうなんだろうなあ、という想像で書いています。)

ただし、ハードコーディングすると多言語化の妨げになるため、外部設定できるのが望ましいと考えます。

つまり、私が望ましいと考えるのはB案ということになります。

以下補足ですが、A案に書かれている

to_tsvector(‘english’, MESSAGE)を to_tsvector(MESSAGE) に変更

については、すみません、説明が下手なせいで誤解を招いてしまっているようです。
CREATE INDEX時にはto_tsvectorへのregconfigパラメータ指定は必須です。無ければエラーになります。
(仮に実行時の言語指定によって挙動を変えられるようにするとなると、都度index再作成が必要になりますからね…)
ただしこの部分は、ハードコーディングされていたとしても、MySQLと同様にユーザ自身がindexを張り直すことはできるのであまり深刻では無いと考えます。

「A案だと他のpostgresを利用しているソフトウェア(e.g. GitLab)にも影響する可能性があるので、B案が良い」

regconfigを明示せずdefault_text_search_configを利用する実装になっているとindexが働かないため修正すべきでは、というのが意図になります。
(GitLabにも影響しそうだからdefault_text_search_config変更を避けたい、というのも多少はあるのですが、まあそれはMattarmost側からしたら知ったこっちゃないですよね…)

すみません、これは単なる私の思い込みで、全く根拠は無いです。
(実際どうなのかは、またググったりしてみます…)

整理すると、(:Terms = keyword)

現状

  • to_tsvector(‘english’, :Terms)
    • english-tokenizer

あるべき姿

  • 英語
    • to_tsvector(‘english’, :Terms)
      • english-tokenizer
  • 日本語
    • to_tsvector(‘japanese’, :Terms)
      • japanese-tokenizer = 形態素解析(textsearch_ja & MeCab)
    • to_tsvector(:Terms)
      • N-gram (bigram)
  • その他のpostgresが標準で対応している言語
    • to_tsvector($lang, :Terms)
      • $lang = Danish, Dutch, English, Finnish, French, German, Hungarian, Italian, Norwegian, Portuguese, Romanian, Russian, Spanish, Swedish, Turkish
  • その他のマルチバイト言語(e.g. Korean, Chinese)
    • to_tsvector(:Terms)
      • N-gram (bigram)

つまり

  1. to_tsvector($lang, :Terms) // with tokenizer
  • $langはconfig等で指定できること
  1. to_tsvector(Terms) // with N-Gram

この2つの形式にできればいいんじゃないでしょうか。
認識が間違っていたら、教えて下さい。
(用語として “tokenizer” というのが適切なのかもちょっと自信ないです。)

P.S.

もしかしたら他の言語を使っている人も、同じように全文検索で、困っているかもしれません。
その時に、このフォーラムを(機械翻訳を使いながら)見るかもしれません。

なので、わかりやすい日本語で書いておくと、いいのかなと思いました。
(私もあまり人のことは言えないのですが…)

Groonga(日本語も他の言語もいける全文検索エンジン)とMroonga(GroongaをMySQLから使えるようにするプラグイン)とPGroonga(GroongaをPostgreSQLから使えるようにするプラグイン)を開発していて全文検索に関する知識があり、助けになれるかもしれないと思ったのでコメントします。

やりとりの中であっていることとあっていないことを示します。他にも、これはあっているか?という項目があれば回答するので言ってください。

個人的にはいいと思うんですが、N-gramって日本語以外のマルチバイト文字列でも使えるはずなので、その辺も考慮できればよりいいんじゃないでしょうか。

N-gramを使うと日本語以外の言語も扱えるというのはあっています。

ただし、(後でも触れますが)to_tsvectorを使う仕組みではN-gramを使うことはできません。

PostgreSQLでの N-gram 検索は 照合演算子(@@) ではなく LIKE を使用する

あっています。

(ググった限り、textsearch_ja & MeCab 方式はちょっとレガシーみたいですし…)

textsearch_jaを開発していた人はすでにメンテナンスをしていないのでそういう意味ではレガシーですが、GitHubで(非公式といいつつ)メンテナンスしている人がいるので、その人が頑張っているうちは使っても大丈夫かと思います。

ただ、最新PostgreSQLに対応する点についてメンテナンスしているようなので、なにか問題があったときにどこまで対応できるかは未知数かと思います。

ただ、英語圏の人にとってはN-gramのLIKE検索は使い勝手が悪いと思うので、現在の@@検索とどちらを使うか選択できるようになっていなければならないのでは、とも思います。

これはN-gramは関係なくLIKE検索かどうかという話になります。

LIKE検索は部分一致検索になりますが、そうすると、単語の一部でヒットすることがあります。たとえば、部分一致では「sum」で検索すると「consumer」もヒットします。このとき、「consumer」が望んでいない結果の場合は検索ノイズとなります。検索ノイズが増えると探している情報を見つけにくくなるので使い勝手が悪くなります。

日本語で言えば、「京都」で検索したら「東京都」もヒットするかどうか、というようなケースです。

to_tsvector(‘english’)の場合は部分一致検索ではなく単語で検索できます。そうすると「sum」で検索したときに「consumer」はヒットしません。なお、to_tsvector(‘english’)他にもステミングと呼ばれるの語形の変化を吸収する処理もしています。たとえば、「speak」と「speaks」と「speaking」をすべて同じものとして扱う、ということです。

to_tsvector(‘japanese’)なら、「京都」で検索しても「東京都」はヒットしない、というような感じです。単語の区切りが「東」と「京都」ではないからです。

ただ、日本語の場合は必ずしも単語の区切りで検索できたほうがよいというわけではありません。単語の区切りを見つけることが難しく、単語の区切りにならないことがあるからです。英語など単語を空白で区切る言語にはない難しさです。人名や新語などはうまく区切ることができないことがあります。

実際は部分一致の方が便利なことが多いです。部分一致を高速に実現するために使う技術がN-gramになります。

なぜなら、regconfigを明示しない場合にはIndex ScanされずSeq Scanで実行されるため、パフォーマンス上の懸念となるためです。
(注: 実際どの程度パフォーマンスに差異があるのか計測したわけではなく、まあそうなんだろうなあ、という想像で書いています。)

どの程度パフォーマンスに影響があるかは1レコードあたりのバイト数とレコード数が影響します。環境にもよりますが、たとえば、250万件あっても1レコードのバイト数が20バイト程度なら1秒程度で実行できます。一方、1レコードのバイト数がもっと大きければもっと少ない件数でも1秒以上かかります。

参考: http://www.clear-code.com/blog/2015/5/25.html

to_tsvector(Terms) // with N-Gram

これはできません。

まず、言語を指定しなければインデックスを作れないからです。

CREATE INDEX pgweb_idx ON pgweb USING GIN (to_tsvector(body));
-- ERROR:  functions in index expression must be marked IMMUTABLE

それと、N-gramを使うときはインデックス側だけでなく、検索キーワード側もN-gram用の処理をしなければいけないからです。なので、もし実現しようとすれば、to_tsvectorを変更しただけでは足りなく、to_tsqueryも変更しなければいけません。しかし、to_tsqueryではN-gramで作ったインデックスで検索するための条件を表現することができません。具体的に言うと「フレーズ検索」を表現できません。

(用語として “tokenizer” というのが適切なのかもちょっと自信ないです。)

あまり適切ではありません。tokenizer(英語ならスペースで区切って日本語なら単語で区切ったりするやつ)とその言語特有の処理を含んでいるからです。たとえば、英語なら上述のステミングという処理が入っています。textsearch_jaならいわゆる半角カタカナを全角カタカナに正規化したりします。

@kou
解説ありがとうございます!
全文検索という分野、触れるの今回が初めてなので自分で調べつつももやっとしたところ多くて想像で書いてる部分もあったのですが、色々すっきりしました!

@terukizm
日本語全文検索するに当たって現状何が困っているかというと、単語検索(文字単位の検索)ができない、というところなので、取り敢えずは@@検索以外にLIKE検索でも行えるように本体側に対応してもらいたいな、と考えます。
そうすれば、日本語のように現在@@検索で対応できていない言語でも取り敢えずは入力語で検索できるようになりますね。

n-gramインデクスを作成できるようにしたり、それをいろんな言語で利用できるようにしたり、というのは、パフォーマンス向上の施策なので、また次の段階の話なのかな、と思いました。
(そもそも一発目に#2622みたいなインデックスのIssue上げたのが飛躍しすぎでしたね… :sweat_smile: )

1 Like

@kou

よく調べず適当に書いてしまっていた部分の補足・訂正ありがとうございます。日本語の全文検索関連で修正提案する際には 改めて参考にさせていただきたいと思います。

@yukihane

たまたま見てたらChineseの方の全文検索も動き始めてるみたいでした。もしご興味が有りましたらご参考までに…

ttps://github.com/mattermost/docs/pull/95

おじゃまします、全文検索ついて調べていて辿り着きました。

関連する問題を見て回りましたが、最終的に正しく理解されずにクローズされちゃったように見えますね。
https://mattermost.atlassian.net/browse/PLT-2808

問題はインデックス生成とクエリのミスマッチにあると思うんですが、インデックスの方はロジック的に
「まだ生成されてなかったら作る」という過程で発生するので、再現手順を明示しないと難しいのかもしれません。

で、それを転じて考えると、とりあえずインデックスの方はクエリに合ったものを再生成することで解決
するんじゃないでしょうか(当然セットアップの追加手順になりますが…)

インデックスがあとから生成しても問題なく、そこに各種全文検索用ツールに適したものを採用できると
いうことになれば、むしろそれに適したクエリを発行できるようにする、というのが解決したい問題の
本質のように思います。

この流れでコードを追った結果、store/post_store.go のSEARCH_CLAUSEがユーザー定義可能に
なればいいんじゃないかなと思いました。どうでしょう?

一通り読ませていただいて、インデックスを作成している個所のソースの変更が必要だということがわかりました。
Gitlab CEにバンドルされているMattermostを使っているのですが、/gitlab/embedded/service/mattermostからto_tsvectorでgrepしても出てきませんでした。
どこを変更すればよいのでしょうか?
ソースからビルドする必要があるのでしょうか?

この対応で日本語で検索できることを確認しました。