FORCIA CUBEフォルシアの情報を多面的に発信するブログ

Elasticsearch vs. PostgreSQL

2020.12.12

アドベントカレンダー2020 PostgreSQL テクノロジー

FORCIAアドベントカレンダー2020 12日目の記事です。

弊社はこれまで PostgreSQL を利用した高速なスペック検索をコアコンピタンスとしてきましたが、今後はドキュメント検索にも注力していく予定です。OSS のドキュメント検索エンジンといえばまず思いつくのが Elasticsearch  です。PostgreSQL と比較されることの多い Elasticsearch ですが、今回は特に日本語処理の周りを技術的にやや深めに比較してみたいと思います。

本記事はPostgreSQL についてある程度知っているがElasticsearch はあまり知らない、という方を対象としています。

Elasticsearch とは

簡単にElasticsearch の特徴を挙げると下記のとおりです。

  • 全文検索に特化した検索エンジン。コア部分は java 製の全文検索エンジン Apache lucene を利用
  • REST APIですべてのオペレーションを実行可能。POSTするデータはJSONで記述する
  • スキーマレス。DB設計をせず文書を登録・検索できる。スキーマを設定することも可能
  • スケーラブル

Elasticsearch v.s. PostgreSQL

一般的な比較記事は多いので、よくまとまっていると思われる記事を紹介します。

Elasticsearch vs. MongoDB vs. PostgreSQL Comparison

上記はデータベースとしての比較記事です。ドキュメントストアとしてよく使われるMongoDB との比較も含まれていて参考になります。
Elasticsearch は MongoDB 同様 "Schema-free"、すなわちDBの設計をせずにとりあえず文書を放り込んで検索することができます。この手軽さが人気のある理由と思われます。

上記は検索言語(query)の比較を行っています。Elasticsearch の検索はSQLではなく Elasticsearch 独自のシンタックス(Domain Specific Language) で行います。
SQL的な厳密で複雑な検索は苦手ですが、テキスト検索については様々な指定ができて便利です。検索処理をコントロールするスクリプトや並び順を調整するオプションを query に含めることもできます。
また aggregation といって検索結果をランキングして出力するだけでなく、ファセット毎の検索数をあわせて出力するよう指定もできます(SQL でいうgroup byに相当) 。

GUI tool  の比較

PostgreSQL には pgAdmin4,   Elasticsearch には head と呼ばれるGUIツールがあります。
pgAdmin はスタンドアロンのアプリケーションですが head は node.js の server版と chrome extension 版があります。[1]

pgAdmin

pgAdmin - PostgreSQL Tools

xx

Elasticsearch  head

GitHub - mobz/elasticsearch-head: A web front end for an elastic search cluster

xxx

Google Trend の比較

Google Trendでの比較

Google Trend 的にはPostgreSQL が Elasticsearch を圧倒しています。
例外的に中国では Elasticsearch が強いようです。

全文検索速度の比較

PostgreSQL と Elasticsearch は設計思想が違うので一概に比較できないのですが、

Full-Text Search Battle: PostgreSQL vs Elasticsearch | sudo README によれば、かなり乱暴にまとめると下記のようになっています。

  • 普通はElasticsearchが 5倍位速い
  • PostgreSQL でチューニングした場合、速度は大体同等

上記は様々な条件付きの結果です。詳細はオリジナルの記事をご覧ください。

機能拡張の比較(extension v.s. plugin )

PostgreSQL にはextension と呼ばれる機能拡張用のモジュール( pg_bulkload のように pg** という名前が多い)が多数あります。
Elasticsearch にも同様の機能拡張用のモジュールがあり pluginと呼ばれています。どちらもユーザが作ることができますが、PostgreSQL のextension はC言語で記述、plugin は Java で書くことになります。そのせいかplugin の方が個数は多いように見えます。

Elasticsearch の面白いプラグインとして ingest-attachment plugin があります。これは pdf や docx, xlsx, pptx といったオフィス文書ファイルをpostすると、テキストや author 属性、CreateDate 情報を抽出して自動的にDBに登録してくれるものです。このようにElasticsearch のプラグインはより応用的・ユーザ志向である一方、PosgreSQL の extension はシステム開発者向けという位置づけになるかと思います。
Elasticsearch では言語(英語、ドイツ語、日本語など)に固有な処理もプラグインの形で提供されています。日本語形態素解析もプラグインとして Elasticsearch に組み込みます。

Elasticsearch の日本語形態素解析

以下では Elasticsearch の日本語形態素解析周辺について述べていきます。
Elasticsearch の言語解析はAnalyzer と呼ばれる言語毎のプラグインの形になっていますが、基本的な機能や仕様は言語共通になっています。すなわち、

  • 文字単位の前処理を行う Char_filter
  • 単語分割を行う Tokenizer
  • 不要単語の削除など後処理を行う Token_filter

の3つから構成されています。

xyz

詳細は下記をご覧ください。

日本語解析で使える Analyzer プラグインは下記の5つです。

analysis-icu プラグインは日本語だけでなく中国語、韓国語などアジア言語に対応しています。
ユニコード処理に強いので全角半角統一などユニコード関連の文字の正規化には便利ですが、単語分割は弱そうです。また、ユーザが辞書を追加することができません。[2]

Kuromoji と Sudachi はJavaで書かれた日本語形態素解析モジュールです。
上記はその Elasticsearch プラグイン版です。analysis-kuromoji は辞書のソースコードは MeCab と共通なので解析結果は MeCab と似ていますが、デフォルトで入っている辞書がIPADICと最低限のものになっています。
そこで NEologd 辞書を追加したものが analysis-kuromoji-ipadic-neologd です。

analysis-mecab は Java からMeCab を呼び出す形で形態素解析するものです。[3]
Java製でないためElasticsearch ではマイナーなプラグインのようです。

弊社では日本語形態素解析としてMeCab を使うことが多く、辞書が MeCab 互換だと嬉しいです。そこで以下では analysis-kuromoji,  analysis-kuromoji-ipadic-neologd, analysis-mecab の3つについて調査します。

日本語処理のカスタマイズ

ICU プラグインを利用すると下記処理ができます。

image (1).png

ICU Normalization Character Filter は NKFC正規化を行うもので、英数字やカタカナの全角半角統一等ができます。

Kuromoji プラグインは下記の処理ができます。

image (2).png 踊り字(々、ゞ)の正規化とは、「常々」⇒「常常」といった処理を行います。
これら以外にも言語非依存のフィルターとしてHTMLのタグを除去する HTML Strip Character Filter や電話番号等を正規表現で正規化する Pattern Replace Character Filter があります。

また、これらのフィルターをアップロードする文書のどの部分(フィールド)に適用するか、柔軟に指定することができます。これにより文書の日付欄には日付の正規化を行う、電話番号欄には電話番号の正規化を適用する、名前欄は形態素解析しない等の設定をすることができます。

Kuromojiによる形態素解析

analysis-kuromoji には単語分割に3つのモード(normal, search, extended) があります[4]。

  • normal  通常の単語分割(最長一致)
  • search  通常の形態素解析に短い単語分割を追加したもの
  • extended 未知語(辞書に登録されていない単語)を1文字単位にばらしたものを更に追加

search モードは検索のrecall を上げるために使います。extended は n-gram で部分一致する単語を検索することを想定していると思われます。解析例は下記のようになります。

UntokenizedNormal modeSearch modeExtended mode
関西国際空港関西国際空港関西 国際 空港関西 国際 空港
日本経済新聞日本経済新聞日本 経済 新聞日本 経済 新聞
シニアソフトウェアエンジニアシニアソフトウェアエンジニアシニア/ソフトウェア/ エンジニアシニア/ソフトウェア/エンジニア
ディジカメを買ったディジカメ/ を/ 買っ/ たディジカメ/ を/ 買っ/ たデ/ ィ/ ジ/ カ/ メ/ を/ 買っ/ た

下記によれば、Searchモードは漢字のみで構成される4文字以上の単語、もしくは7文字以上の単語に対してコストを重くすることで、分割を促しているようです。

Java製形態素解析器「Kuromoji」を試してみる

以下に   search モードでの解析例を示します。言語解析結果を表示する API である _analyze を上述した head ツールで利用しています。

image (3).png

ユーザ辞書は使えるか

analysis-kuromoji、およびanalysis-kuromoji-ipadic-neologd は CSV 形式のテキストファイルをユーザ辞書として組み込むことができます。プラグインのカスタマイズパラメータに "user_dictionary" というフィールドがあり、そこにテキストファイルを指定します。

kuromoji_tokenizer | Elasticsearch Plugins and Integrations [7.10] | Elastic

しかし、テキストファイルで指定することからわかるようにユーザ辞書としてはせいぜい数百語程度のサイズを想定しているようです。数万~十数万語レベルのユーザ辞書は使えません。換言すると(検索用にバイナリコンパイルされた)ユーザ辞書を実行時に指定して利用する方法はありません。
実行時でなければ analysis-kuromoji-ipadic-neologd プラグインのソースコードをダウンロードして、NEologd 辞書のソースコードにユーザ辞書をCSV で追加してプラグインをビルドすることにより、大量の単語の追加をすることができます。

一方analysis-mecab は "user_dictionary" カスタマイズパラメータにMeCab用にコンパイルしたユーザ辞書を指定することで、その辞書を利用することができます。

長い単語を辞書登録して形態素解析できるか

analysis-kuromoji は内部的に Lucene Kuromoji を使っていますが、Lucene Kuromoji には「見出しは16文字未満」という制約があります[5]。

一方、NEologd には16文字以上の長い単語が多数登録されています。analysis-kuromoji-ipadic-neologdでこの制約が改善されているか確認しました。

念の為 analysis-mecab でも、辞書登録された長い単語が正しく形態素解析できるか確認しました。[6]

原文
(NEologd 辞書ソースより採取)
原文文字数kuromoji-ipadic-neologdanalysis-mecab
あいしてると言ってよかった13あいしてると言ってよかったあいしてると言ってよかった
あさぎり町立上小学校皆越分校14あさぎり町立上小学校皆越分校あさぎり町立上小学校皆越分校
あけましておめでとうございます15あけましておめでとうございますあけましておめでとうございます
あなたの夢の中そっと忍び込みたい16あなた/の/夢の中/そっと/忍び込みたいあなたの夢の中そっと忍び込みたい
うわうみ漁業協同組合日振島女性部16うわ/うみ/漁業協同組合/日振島/女性/部うわうみ漁業協同組合日振島女性部

分割された箇所に"/" を入れています。残念ながらkuromoji-ipadic-neologd では16文字以上の辞書登録単語は分割されてしまいました。この他にも記号で始まる単語で解析されないものがあり(形態素解析結果が空になる)、kuromoji-ipadic-neologd は若干不安定な印象です。

PostgreSQL の日本語形態素解析

PostgreSQL で単語分割を導入して検索精度を上げるのには textsearchja モジュールを用います。jawakati 関数により MeCab を呼び出して単語分割を行い、tsvector 型のフィールドに分割結果を入れます。検索するときは検索文字列を tsquery 型に変換して比較を行います。詳細は下記を参照ください。

MeCab と textsearch_ja を使って高速な全文検索を実現しよう - PowerGres 体験記 第 4 回

形態素解析の前の正規化は to_tsvector() 関数内部で実行されますが、ビルトインでカスタマイズはできません。 tsvector 型は制約が多く 検索が遅くなりがちなので、以下のような回避策がとられる場合もあります。

  1. 検索対象テキストを単語分割する。単語間にセパレータ文字を挟んでテキストとして登録する(a)。
  2. 検索文字列を単語分割する。単語間にセパレータ文字を挿入する(b)。
  3. (a) に対して(b) をフルテキスト検索する。

まとめ

Elasticsearch の日本語解析の周辺をやや細かく眺めてみました。
文字の正規化やストップワードの除去など、テキスト検索に必要な各種機能をElasticsearch はワンストップで提供しています。文書のフィールド毎に解析方法を替えることができ柔軟性が高いです。したがって通常の用途(それなりに単語切りできれば良い)には十分ですが長い専門用語を正確に検索したいといった用途には向きません。
Elasticsearch の設計思想は、長い単語を正確に形態素解析してprecision を上げるより短く切って recall 重視というように感じます。そのほうが解析速度も確保できます。

また、Kuromoji と その Elasticsearch プラグインである analysis-kuromoji (あるいは Lucene Kuromoji)はソースコードが違うというのも意外でした。様々なバージョンがあり辞書や解析結果が若干異なるというのは面倒です。またユーザ辞書の扱いがMeCab に比べると弱いと感じました。後継(Sudachi) が登場しているのはその辺りが理由かもしれません。

PostgreSQLは MeCab を呼ぶ機能はありますが、それ以外の正規化や後処理は基本的に自作する必要があります。
ただ、どれも実装は容易です。MeCabは登場してから10年以上経ちますが、基本設計と性能・実装が良いため日本語形態素解析のスタンダードとして確立しています。ユーザ辞書に辞書登録すれば原文にその単語が出てきたときに正しく分割されます。その意味で安心して使うことができます。

今回は日本語形態素解析周辺の話で終わってしましました。Elasticsearch と PostgreSQL に於ける同義語・類義語の扱いや、Elasticsearch 7.* で導入されたドキュメントベクトル検索について触れられませんでした。いずれ機会があれば紹介したいと思っています。

注釈

[1]
以前は plugin 版や docker 版もありましたが、最新版 Elasticsearch 7.* にはないようです。 Elasticsearch は上述したように全てのオペレーションをREST API で実行できるので、開発や動作確認は curl や wget で済むといえば済むのですが、head には JSON のvalidate やprettyPrint 機能もあり便利です。クラスタの統計情報を確認することもできます。
[2]
なので char_filter には analysis-icu を使い tokenizer には 後述する analysis-kurmoji を使うのがベストプラクティスとされているようです。
[3]
URL版は Elasticsearch 5.* 用で最新版では動作しませんが、簡単な修正で 6.* ↑ 用にビルドすることができます。
[4]
これは Kuromoji 自体の機能です。
[5]
Kuromoji(Atilika)0.9-SNAPSHOTに、NEologd(ipadic、unidic)を適用してみた話 - CLOVER🍀
[6]
mode = normal で計測しました。analysis-mecab ではシステム辞書としてmecab-ipadic-neologdを指定しています。

この記事を書いた人

小野 顕司

技術本部 オフィサー。2018年中途入社。
検索精度改善や言語資源の導入等、社内の自然言語処理技術の整備を行っている。