SEVENSPIRALS

Node.jsとMongoDBとElasticsearchで日本語全文検索

プログラミング

MongoDBのver2.4から全文検索が導入されたので使ってみようと思ったんですが、やっぱりというか日本語には対応していなかった・・・。

何か悔しいのでElasticsearchでMongoDBのコレクションを日本語全文検索する環境を構築してみました。

Elasticsearchのインストール

何はともあれElasticsearchのインストールですが、Macならbrewで簡単にインストールできます。

ちなみにWindowsでも適当な場所にzipを展開するだけです。

$ brew install elasticsearch

で、起動。今回は試しながらなので-fオプションを付けてフォアグラウンドで起動します。

$ elasticsearch -f

特にエラーが出なければ実際にアクセスしてみます。

elasticsearchはREST APIでやりとりするのでcurlコマンドでリクエストを投げます。

まずはテスト用のIndexを作成します。

$ curl -XPUT 'http://localhost:9200/test'

実行した結果、{"ok":true,"acknowledged":true}のようなレスポンスが返ってくれば成功です。

ついでに分析リクエストも投げてみますがこの時点では日本語は正しく分析できません。

$ curl -XGET 'http://localhost:9200/test/_analyze?pretty' -d '東京都渋谷区'
/*{
  "tokens" : [ {
    "token" : "東",
    "start_offset" : 0,
    "end_offset" : 1,
    "type" : "<IDEOGRAPHIC>",
    "position" : 1
  }, {
    "token" : "京",
    "start_offset" : 1,
    "end_offset" : 2,
    "type" : "<IDEOGRAPHIC>",
    "position" : 2
  }, {
    "token" : "都",
    "start_offset" : 2,
    "end_offset" : 3,
    "type" : "<IDEOGRAPHIC>",
    "position" : 3
  }, {
    "token" : "渋",
    "start_offset" : 3,
    "end_offset" : 4,
    "type" : "<IDEOGRAPHIC>",
    "position" : 4
  }, {
    "token" : "谷",
    "start_offset" : 4,
    "end_offset" : 5,
    "type" : "<IDEOGRAPHIC>",
    "position" : 5
  }, {
    "token" : "区",
    "start_offset" : 5,
    "end_offset" : 6,
    "type" : "<IDEOGRAPHIC>",
    "position" : 6
  } ]
}*/

プラグインの追加

まずは日本語の全文検索ができるようにkuromojiの分析プラグインをインストールします。

インストールはコマンド一発で簡単にできます。

$ plugin -install elasticsearch/elasticsearch-analysis-kuromoji/1.5.0

で、今回はMongoDBのCollectionを全文検索したいのでそれ用のプラグインもインストールします。

elasticsearch-river-mongodbプラグインのインストールは他と同じような書き方にするとURLが見つからずインストールできなかったので直接githubを指定しました。

$ plugin -install elasticsearch/elasticsearch-mapper-attachments/1.8.0
$ plugin -i com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb/1.7.0

特にエラーが出なければインストールは完了です。

kuromojiのテスト

まずアナライザにkuromojiを指定して日本語の構文解析が出来るようになっているか確認します。

$ curl -XGET 'http://localhost:9200/test/_analyze?analyzer=kuromoji&pretty' -d '東京都渋谷区'
/*{
  "tokens" : [ {
    "token" : "東京",
    "start_offset" : 0,
    "end_offset" : 2,
    "type" : "word",
    "position" : 1
  }, {
    "token" : "都",
    "start_offset" : 2,
    "end_offset" : 3,
    "type" : "word",
    "position" : 2
  }, {
    "token" : "渋谷",
    "start_offset" : 3,
    "end_offset" : 5,
    "type" : "word",
    "position" : 3
  }, {
    "token" : "区",
    "start_offset" : 5,
    "end_offset" : 6,
    "type" : "word",
    "position" : 4
  } ]
}*/

最後にデフォルトでkuromojiを使うようにelasticsearch.ymlに以下の行を追加します。

index.analysis.analyzer.default.type: custom
index.analysis.analyzer.default.tokenizer: kuromoji_tokenizer

MongoDBの設定

MongoDBからIndexを作成するelasticsearch-river-mongodbプラグインはReplicaSetを構築した際に作成されるoplog.rsコレクションを利用するのでReplicaSetを構築する必要があります。

構築自体は1台構成でもいいのでmongoのコンソールからrs.initiate()を実行します

しばらく待つと構築が終わるのでrs.config()で結果を確認します。

最後にElasticsearch側でCollectionとIndexの関連付けを行います。(DB名やIndex名は実際に使う名前を指定します)

$ curl -XPUT 'http://localhost:9200/_river/mongodb/_meta' -d '{
  "type": "mongodb",
  "mongodb": {
    "db": "DATABASE_NAME",
    "collection": "COLLECTION",
    "gridfs": true
  },
  "index": {
    "name": "ES_INDEX_NAME",
    "type": "ES_TYPE_NAME"
  }
}'

この時点でElasticsearchを再起動するとNoShardAvailableActionExceptionという例外が出るんですが何故出るのか分からず調査中。検索自体は普通に出来ます。

Node.jsから呼び出す

設定が終わってしまえばあとはMongoose等で普通にデータの追加や削除を行えばほぼタイムラグ無しにElasticsearch側に反映されます。

Elasticsearch側のインタフェースはREST APIなので普通にNode.jsからHTTPリクエストを投げれば良いんですがその辺をラップしてくれるライブラリがあります。

まずはインストール

$ npm install elasticsearch

サンプルとして以下のようなSchemaのコレクションを検索する処理を書きます。

{
  subject: String
  body: String
}

例によってCoffeeScriptですがsubjectとbodyに対してキーワード検索するにはこんな感じで書きます。

log4js = require 'log4js'
logger = log4js.getLogger()
elasticsearch = require 'elasticsearch';
es = elasticsearch {_index: 'index_name', _type: 'type_name'}

search = (keyword, callback)->
  es.search {query:{multi_match:{query: keyword, fields:['subject', 'body']}}}, callback

search('日本語全文検索', (err, data)->
  if err
    logger.error err
  else
    logger.debug data
)

という感じで日本語全文検索出来るようにしただけで力尽きたので今日はここまで。

そのうちこれを使って何か作ってみたいと思います。

今日使った諸々のリンク