2014年4月21日は、第4回Elasticsearch勉強会ですね!
http://elasticsearch.doorkeeper.jp/events/8865
第4回Elasticsearch勉強会は、参加希望者が約200名の大反響なようです。
私は勉強会に参加できないので、C言語で書かれた国産の高速な全文検索エンジンGroongaと、Javaで書かれた世界的に勢いのあるElasticsearchについて性能の比較をしたいと思います。
- 注意事項
今回の検証では1台あたりの馬力を比較するためにサーバ1台での全文検索性能について比較しています。
私は、Groonga(Mroonga)の利用暦が約2年であるのに対し、Elasticsearchの利用暦は2日です。このため、Elasticsearchに対するチューニングの不備や公平な比較になっていない点が含まれている可能性があります。
Elasticsearchでの言葉の扱いに慣れていないため、誤った語句を使っている可能性があります。
Groongaとは
GroongaはC言語で書かれた国産の高速な全文検索エンジンです。Groongaは、C言語から呼び出すことが可能な全文検索ライブラリとしての機能を有し、また、単体で全文検索サーバとしての機能も有します。
Groongaでは、MySQLのストレージエンジンとしてSQLベースで容易に高速な全文検索が可能なMroongaが提供されています。GroongaやMroongaは、それ単体ではスケールしませんがfluentdのプラグインとして動作するDroongaやSpiderストレージエンジンを使うことによりデータベースシャーディングが可能です。
Elasticsearchとは
Elasticsearchは、Javaで書かれた全文検索ライブラリApache Luceneを用いた全文検索、解析サーバです。Elasticsearchは、RESTfullで容易にアクセスすることができ、データを容易にスケールさせることができます。
また、Elasticsearchは、全文検索サーバSolrでも使われている歴史あるApache Luceneを使っていることもあり、非常に高度な全文検索機能、集計機能、豊富なプラグインを有し、多種多様な運用ツールとの連携の実績があります。
最近では、Elasticsearchとfluentdとkibanaを使ったログの可視化等が非常に流行っているようです。
検証データ
- 日本語Wikipedia - Articles, templates, media/file descriptions, and primary meta-pages.
http://dumps.wikimedia.org/jawiki/20131119/ *1
XMLデータサイズ | レコード数 |
---|---|
7.3GiB | 1,766,247 |
Wiki記法等は除去せずそのまま利用します。
7/19追加検証
http://dumps.wikimedia.org/jawiki/20140714/
検証環境
- さくらのクラウド ×1台
CPU | メモリ | ディスク |
---|---|---|
4Core | 16GB | SSD 100GB |
7/19追加検証
- さくらの専用サーバ ×1台
CPU | メモリ | ディスク |
---|---|---|
Intel(R) Xeon(R) CPU E5620 @ 2.40GHz 1CPU 4Core | 32GB | HDD 2TB |
Elasticsearchの検証手順
環境構築
CentOSにElasticsearchをインストールする方法の手順に沿って、Elasticsearch1.1.1
をインストールします。
ヒープサイズを8GiBに設定し、JVM起動時にメモリを確保するように設定しています。
また、デフォルトのシャード数を1に設定しています。
% vi /etc/init.d/elasticsearch ES_HEAP_SIZE=8g MAX_OPEN_FILES=65535 MAX_LOCKED_MEMORY=unlimited % vi config/elasticsearch.yml http.port: 9200 index.number_of_shards: 1 bootstrap.mlockall: true
ElasticSearchの運用とか (2)を参考にさせていただきました。ありがとうございます。
スキーマ
以下のような簡潔なテーブル構造を作成することを想定します。
id(integer) | title(string) | text(string) |
---|---|---|
150813 | スポーツのプロリーグ一覧 | '''スポーツのプロリーグ一覧'''(スポーツの... |
150815 | Category:スポーツ競技大会 | {{Pathnav|主要カテゴリ|文化|娯楽|スポーツ|... |
上記のテーブル構造に相当するスキーマを作成します。
アナライザーは、Groongaに合わせるためにngram_tokenizerでmin_gram
とmax_gram
を2にしています*2。
なお、Elasticsearchでは文字の正規化やタグ除去、ストップワード除去等フィルタがかなり豊富に用意されていますが、今回は単純な全文検索性能を比較するため、それらを使用しません。
% vi mapping.json { "settings": { "analysis": { "analyzer": { "ngram_analyzer": { "tokenizer": "ngram_tokenizer" } }, "tokenizer": { "ngram_tokenizer": { "type": "nGram", "min_gram": "2", "max_gram": "2", "token_chars": [ "letter", "digit", "punctuation", "whitespace", "symbol" ] } } } }, "mappings": { "text": { "properties": { "id": { "type": "integer" }, "title": { "type": "string", "analyzer": "ngram_analyzer" }, "text": { "type": "string", "analyzer": "ngram_analyzer" } } } } }
スキーマファイルをPOST
します。
curl -XPOST localhost:9200/wikipedia -d @mapping.json
これにより、wikipediaという名前のindex
のスキーマが設定されました。なお、Elasticsearchは、スキーマレスでも動作します。
7/19追加検証
最初の検証では、punctuationとwhitespaceが入っておらず、Groongaと転置索引のトークンの量に違いがありました。追加検証では、Groongaと合わせるためにpunctuationとwhitespaceを有効にしています。
更新手順
更新は、たとえば、以下のようなPUT
アクセスで行います。
% curl -XPUT 'http://localhost:9200/wikipedia/text/1' -d ' { "id": "1", "title": "title1", "text": "text1" } '
Gistに上げたPHPスクリプトを用いて、WikipediaのXMLデータを1件ずつPUT
で更新します。
Elasticsearchでは、バルクアクセスのAPIや各スクリプト言語のライブラリを用いることもできるようですが、今回は単純に1件ずつCURL
でPUT
するだけにしました。
検索手順
検索は、たとえば、以下のようなGET
アクセスで行います。title
フィールドとtext
フィールドに対してsearchquery
を全文検索し、一致するid
を0件出力します。ここでは、全文検索性能のみを比較するため、出力数は0にしています。
curl -XGET http://localhost:9200/wikipedia/text/_search -d' { "from" : 0, "size" : 0, "fields": ["id"], "query": { "multi_match" : { "query": "\"searchquery\"", "fields": [ "title", "text" ] } } }'
7/19追加検証
以前の検証結果では、"でくくっていなかったため、OR検索となっていました。追加検証では、"でくくってフレーズ検索としています。Elasticsearchでは、空白が含まれていなくとも、"でくくらないと、アナライザーによって分割されたトークンがOR検索されます。たとえば、「東京都」は、「東京」OR「京都」で検索されます。
Wikipediaのカテゴリのうち5文字以上の日本語のみのカテゴリからランダムに1万件を抽出して全文検索します。
Gistに上げたPHPスクリプトを用いて、1万件のカテゴリを1件ずつGET
で全文検索します。更新と同様に、単純にCURL
でGET
するだけです。
なお、Elasticsearchでの名前体系や扱い方に慣れておらず、不平をつぶやいていたら、@johtaniさんがフィールドの指定方法やカウントの見方を教えてくれました。ありがとうございました。Elasticsearchについて無知なにも関わらず、色々不平をつぶやいて大変失礼しました。
GroongaやDroongaでの名前体系に慣れていると、Elasticsearchの名前体系に慣れるのがなかなか大変でストレスを感じます。たぶん、Elasticsearchに慣れている方がGroongaやDroongaを触ろうとしてもそう感じるんじゃないかなと思います。
Groongaの検証手順
環境構築
Groonga4.0.1
とnginxベースのgroonga-httpd
をインストールします。
% rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm % yum makecache % yum install -y groonga % yum install -y groonga-tokenizer-mecab % yum install -y groonga-normalizer-mysql % yum install groonga-httpd
スキーマ
上記と同様のテーブル構造に相当するテーブル定義を作成します。
トークナイザーはTokenBigram
を設定し、ノーマライザーは設定していません。
こうすると、Groongaでは、一般的なバイグラムのルールに従ってテキストがトークナイズされます。
% vi table.ddl table_create text TABLE_PAT_KEY UInt32 column_create text text COLUMN_SCALAR LongText column_create text title COLUMN_SCALAR LongText table_create text-text TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram column_create text-text index COLUMN_INDEX|WITH_SECTION|WITH_POSITION text title,text
Groongaのテーブルを作成します。
% groonga -n /var/lib/groonga/db/db < table.ddl [[0,1397980056.85291,0.00288963317871094],true] [[0,1397980056.85586,0.00232267379760742],true] [[0,1397980056.8582,0.00235915184020996],true] [[0,1397980056.86058,0.00229644775390625],true] [[0,1397980056.86289,0.00667881965637207],true]
コマンドによってデータベースが新規作成された場合、groonga-httpd
から操作できるようにファイルオーナーをgroonga:groonga
に修正します。groonga-httpd
が起動している場合、すでにgroonga:groonga
でデータベースがつくられていると思います。
% chown groonga:groonga /var/lib/groonga/db/db* % service groonga-httpd restart
これにより、text
という名前のテーブルが作成されました。Groongaでは、MySQLなどと同様に明示的なテーブル定義を必要とします。
更新手順
更新は、たとえば、以下のようなPOST
アクセスで行います。
% curl -X POST 'http://localhost:10041/d/load?table=text' -d ' [{"_key":1,"text":"text1","title":"title1"}] '
Gistに上げたPHPスクリプトを用いて、WikipediaのXMLデータを1件ずつPOST
で更新します。
Groongaではデータのみを追加した後に全文インデックスを追加することにより最適化された静的インデックス構築ができますが、今回は単純にCURL
で1件ずつPOST
するだけです。
なお、普段はGroongaをMySQLから利用できるMroongaを利用しており、GroongaのHTTPサーバに対してPOST
でうまく更新できないなぁとつぶやいていたら、@ktouさんがnginxベースのgroonga-httpd
を使えばいいことを教えてくれました。ありがとうございました。
検索手順
検索は、たとえば、以下のようなGET
アクセスで行います。title
カラムとtext
カラムに対してsearchquery
を全文検索し0件出力します。ここでは、Elasticsearch同様、全文検索性能のみを比較するため、出力数は0にしています。また、Groongaでは、ヒット数が0件の場合、自動的に前方一致検索にエスカレーションするという機能があるためこれを抑制しています。
% curl 'http://localhost:10041/d/select?table=text&match_columns=title||text&query=searchquery&limit=0&match_escaltion_threshold=-1'
Wikipediaのカテゴリのうち5文字以上の日本語のみのカテゴリから上記と同じカテゴリ1万件を全文検索します。
Gistに上げたPHPスクリプトを用いて、1万件のカテゴリを1件ずつGET
で全文検索します。更新と同様に、単純にCURL
で1件ずつGET
するだけです。
性能比較
更新時間
Elasticsearch | Groonga |
---|---|
8853sec(2h27min) | 21914sec(6h05min) |
いつインデックスが更新されているかまでは追っていないので単純な比較はできないかもしれませんが、シーケンシャルなスクリプトの実行時間で比較すると、Groongaの方が2.5倍ほど遅かったです。
追記
Groongaの方はデータと転置索引の更新の両方が含まれた時間であり、Elasticsearchの方は転置索引の更新の時間は含まれていません。Elasticsearchは、リフレッシュ、フラッシュ、マージなどの転置索引の更新処理がデータの更新とは別に裏で走ります。
※トータルの更新時間には、XMLのパースが含まれています。
7/19追加検証
Elasticsearch | Groonga |
---|---|
6689sec(1h51min) | 20958sec(5h49min) |
ディスク使用量
Elasticsearch | Groonga |
---|---|
12.7GiB | 18.9GiB |
ディスク使用量は、Elasticsearchの方がコンパクトでGroongaの方が1.5倍ほど大きいですね。 Elasticsearchの方は設定をミスってしまって空白と所定の区切り文字が転置インデックスに入っていないですが、それを差し引いてもGroongaの方が大きいでしょう。
なお、Groongaの場合、インデックスのみを後で静的インデックス構築をすれば、ディスク使用効率が上がり検索性能がさらに向上します。なお、この方法はリアルタイム更新ではないのでここでは比較しません。
6/30追記
Groongaドキュメント読書会1で学んだ事のメモ - Qiitaを参考にすると、Groongaは、インデックスの即時更新のため、インデックス各データの間に隙間があり、データを間に差し込めるようになっているようです。このため、Elasticsearchに比べ、Groongaの方がディスク使用量が大きくなっているものと思われます。その分、インデックス更新にかかる処理コストが低いというメリットがあるようです。
http://groonga.org/ja/blog/2011/07/28/innodb-fts.html
7/19追加検証
Elasticsearch (optimize前) |
Groonga |
---|---|
19.3GiB | 18.9GiB |
punctuationとwhitespaceを有効にしたらElasticsearchの方が大きくなってしまいました。 おそらく、これはマージが終わっていないのでしょう。
以下のようにoptimizeをするとサイズが減少しました。
% curl -XPOST 'http://localhost:9200/wikipedia/_optimize'
Elasticsearch (optimize後) |
Groonga |
---|---|
16.5GiB | 18.9GiB |
検索時間
種別 | Elasticsearch | Groonga |
---|---|---|
トータル(1万件) | 1050.737sec | 424.797sec |
平均 | 0.105sec | 0.042sec |
最長 | 0.792sec | 0.368sec |
最短 | 0.000992sec | 0.000842sec |
上記を比較すると、全文検索速度はGroongaの方が約2.5倍ほど速いことが判ります。 経験則から言うと、Groongaはこの数倍~10倍ぐらいのデータサイズであっても1台で十分に高速に全文検索できると思います*3。
ただし、Groongaは、単体ではシャーディングすることができません。一方、Elasticsearchは、デフォルトのシャード数が5であることからもわかるように、複数台構成が前提となっており容易にシャーディングすることができます。
また、今回は単一アクセスの検索性能しか試していません。同時実行性能を含めるとどちらが勝っているかは判りません。
7/19追加検証
フレーズ検索でもGroongaの方がElasticsearch(optimize後)よりも約2.8倍ほど速いことが判ります。Elasticsearch(optimize前)であれば約4.3倍ほど速いことが判ります。ちなみにGroongaでは、リアルタイム更新をしても速度劣化はほとんどないため、optimizeという処理はありません。詳しくはこちら。
種別 | Elasticsearch (optimize前) |
Elasticsearch (optimize後) |
Groonga |
---|---|---|---|
トータル (1千件) |
216.161sec | 141.701sec | 50.062sec |
平均 | 0.216sec | 0.141sec | 0.050sec |
最長 | 4.313sec | 1.037sec | 0.339sec |
最短 | 0.000960sec | 0.00317sec | 0.00215sec |
おわりに
以上、サーバ1台、単一アクセスにおけるGroongaとElasticsearchの性能を比較しました。 まとめると以下のような比率になります。数値が高い方が性能が良いことを示しています。
性能種別 | Elasticsearch | Groonga |
---|---|---|
更新性能 | 2.5 | 1 |
ディスク使用効率 | 1.5 | 1 |
検索性能 | 1 | 2.5 |
7/19追加検証
性能種別 | Elasticsearch (optimize前) |
Groonga |
---|---|---|
更新性能 | 3.13 | 1 |
ディスク使用効率 | 1 | 1.02 |
検索性能 | 1 | 4.31 |
性能種別 | Elasticsearch (optimize後) |
Groonga |
---|---|---|
更新性能 | 3.13 | 1 |
ディスク使用効率 | 1.14 | 1 |
検索性能 | 1 | 2.83 |
上記の比較例では、更新性能、ディスク使用効率ではElasticsearchが勝っており、検索性能ではGroongaが勝っていました。なお、あくまで今回のテストケースによる結果であり、他のテストケースではどうかわかりません。
Elasticsearchは、今、世界的に勢いがあり、容易なスケール機能、豊富な機能、豊富なプラグイン、運用ツールとの連携等、様々なメリットがあります。 アナライザーやフィルターのラインナップは、歴史が長いApache Luceneのライブラリを使っていることもあり、現状、GroongaよりもElasticsearchの方が優れているでしょう。 MoreLikeThisや類似画像検索は、現在のGroongaにはない機能です。
現状、大規模なWebサービス等でサーバを何十台、何百台とたくさん並べて運用する場合は、Elasticsearchの方が利用しやすいかもしれません。
しかし、私は、サーバ1台あたりの全文検索自体の馬力ではGroongaの方が勝っていると考えています。また、弱点だったスケール機能は、2014年2月にDroongaがメジャーリリースされ解消されつつあります。 さらに、Groongaは、MySQLのストレージエンジンであるMroongaを利用することによりSQLベースで非常に容易に高速な全文検索をすることもできます。私はMroongaがあったので、データベースも全文検索の知識もまったくなかった状態からある程度Groongaが扱えるようになりました。
kuromojiトークナイザの検索モードやフィルタ機能程度の利点であれば、多少工夫すればGroongaやMroongaでも補えます。今、Mroongaを使っていて、高度な検索式や豊富な全文検索機能を追加したいと考えている方は、まずは、GroongaやDroongaを検討してもいいと思います。Mroongaをストレージモードで使っているならデータベースそのままでGroongaのコマンドが利用できますし、MySQLの制約からくるインデックスが使用できないことによる速度劣化であれば、Groongaのコマンドに置き換えるだけで10倍以上の検索速度になったりすると思います。詳しくはこちらを参照してください。
上記の比較はあくまで一例にすぎませんが、そこそこ大規模なデータベースに対して少ないサーバ台数で高速な全文検索を実現したいならば、私はGroongaがおすすめだと思っています。
私はGroonga(Mroonga)を使うことにより、データサイズが400GiB超のデータベースを専用サーバ1台でそこそこ実用的な全文検索速度を実現させることができました*4。
データベースの規模や特性に応じて、ElasticsearchとGroongaを使い分けるのも良いかもしれません。
最近、Mroongaを使って全文検索Webサービスを作ったときにはまったことについて、いくつかブログ記事を書きました。GroongaやMroongaに興味があれば、こちらも参考になるかもしれません。
Mroongaを使って全文検索Webサービスを作ったときにはまったこと(第1回)
Mroongaのラッパーモードからストレージモードに変えた理由
数百GiBの全文検索用データベースをMroongaのストレージモードにしてはまったこと
Groongaがあまり得意でない類似文書検索に連想検索エンジンGETAssocを使った話
今後は、Groonga(Mroonga)を使って工夫した点等を書いていきたいと思っています。
7/22追記
GroongaとElasticsearchの転置索引の違いと更新反映速度の差、およびGroongaの静的索引構築とリアルタイム更新時の性能差について検証した記事を追加しました。よければ、こちらもご参照ください。
GroongaとElasticsearchにおける転置索引の違いと更新反映速度について
7/28追記
GroongaをKVSとして利用した場合のベンチマークについて記事を書きました。Groongaは単純なKVSとしてもかなり高速な性能を有しています。
GroongaとTokyoCabinetのHash表のベンチマークについて
9/9追記
ちなみに、Groongaの場合、トークナイザーをTrigramにし、さらに効率化した自作トークナイザ―を使うと検索速度が8倍ほど速くなります(平均 0.0063 sec)。
https://github.com/naoa/groonga-tokenizer-yangram
2015/3/9追記
最新のGroongaで以下のパッチを適用すると、さらに検索速度が1.5倍〜2倍ほど速くなります。
*1:データがやや古いのは、以前、Mroongaの性能をいろいろ検証したときと同じものを使ったためです。
*2:厳密にGroongaに合わせるためには、token_charsにwhitespaceとpunctuationも指定すべきでした。
*3:インデックスが適正に使われていることが前提です。Mroongaの場合、MySQLによる制約でインデックスが利かないケースが多々あります。
*4:アクセスが増えると参照分散させるために台数を増やす必要があると思います。実用的な速度かどうかは、アプリの要求次第によって変わると思います。デフォルトのトークナイザTokenBigramではこのサイズはさばけないと思います。自前でトークナイザを改修したり工夫しています。