オープンソースこねこね

Webプログラミングなどについてあれこれ。

ElasticsearchでCentOS上にNgram全文検索サーバを構築する - (その2)Ngramアナライザを設定する

前回に引き続きElasticsearchの設定を行います。

elasticsearch-headプラグインをインストールする

いろいろ設定を試していたりすると、設定内容やデータの確認のためにコンソールからcurlを実行してREST APIを実行するのが面倒になります。そこでElasticsearchにはWebUIからデータや設定内容を参照するための機能がプラグインで用意されているので、これを導入します。

インストールは以下のコマンドを実行するだけでOKです。

$ sudo /usr/share/elasticsearch/bin/plugin --install mobz/elasticsearch-head

あとはブラウザから

http://localhost:9200/_plugin/head/

にアクセスすればWebUIからデータの操作ができるようになります。

アナライザ

アナライザは、文字列タイプのフィールドをElasticsearchにインデックスする(データを保存する)ときや検索クエリを投げるときに行われる処理で、データを品詞分解したり、大文字小文字の入力を変換したりするテキストの解析処理のことです。インデックスするときと検索時に別々のアナライザを使うこともできます。

デフォルトでいくつかアナライザが用意されていますが、自分で定義することもできます。 今回は日本語をNgram検索をしたいのでカスタムアナライザを定義しました。なおNgramについてはググる

http://gihyo.jp/dev/serial/01/make-findspot/0005

などの参照してください。

/etc/elasticsearch/elasticsearch.ymlに以下の定義を追加します

# default analyzer (1-gram and 2-gram)
index.analysis.analyzer.default.tokenizer: custom_ngram_tokenizer
index.analysis.analyzer.default.filter.0: lowercase

index.analysis.tokenizer.custom_ngram_tokenizer.type: nGram
index.analysis.tokenizer.custom_ngram_tokenizer.min_gram: 1
index.analysis.tokenizer.custom_ngram_tokenizer.max_gram: 2
index.analysis.tokenizer.custom_ngram_tokenizer.token_chars.0: letter
index.analysis.tokenizer.custom_ngram_tokenizer.token_chars.1: digit

# default_search analayzer(2-gram)
index.analysis.analyzer.default_search.tokenizer: custom_bigram_tokenizer
index.analysis.analyzer.default_search.filter.0: lowercase

index.analysis.tokenizer.custom_bigram_tokenizer.type: nGram
index.analysis.tokenizer.custom_bigram_tokenizer.min_gram: 2
index.analysis.tokenizer.custom_bigram_tokenizer.max_gram: 2
index.analysis.tokenizer.custom_bigram_tokenizer.token_chars.0: letter
index.analysis.tokenizer.custom_bigram_tokenizer.token_chars.1: digit

設定を反映させるため、Elasticsearchを再起動し、データを再投入します。

さて、上記のアナライザの定義ですが、2つのカスタムアナライザを定義しています。まず最初の

# default analyzer (1-gram and 2-gram)
index.analysis.analyzer.default.tokenizer: custom_ngram_tokenizer
index.analysis.analyzer.default.filter.0: lowercase

部分ですが、index.analysis.analyzer.defaultというキーでデフォルトのアナライザを定義しています。index.analysis.analyzer.default.tokenizer: custom_ngram_tokenizerはトークナイザ(品詞分解する処理)にcustom_ngram_tokenizerを使うことを設定しています。で、このcustom_ngram_tokenizerはその下に設定内容が書いてあります。

index.analysis.tokenizer.custom_ngram_tokenizer.type: nGram
index.analysis.tokenizer.custom_ngram_tokenizer.min_gram: 1
index.analysis.tokenizer.custom_ngram_tokenizer.max_gram: 2
index.analysis.tokenizer.custom_ngram_tokenizer.token_chars.0: letter
index.analysis.tokenizer.custom_ngram_tokenizer.token_chars.1: digit

文字を1-gramおよび2-gramで分解する設定となっています。つまり、

こんにちは

という文字は

こん, んに, にち, ちは
こ,ん,に,ち,は

というように分解されインデックスされます。さて、もう一つのアナライザですが、

# default_search analayzer(2-gram)
index.analysis.analyzer.default_search.tokenizer: custom_bigram_tokenizer
index.analysis.analyzer.default_search.filter.0: lowercase

という定義になっています。このindex.analysis.analyzer.default_searchという設定は、検索時のみに使われるアナライザのデフォルトになります。こちらのトークナイザの設定はcustom_bigram_tokenizerで、これは

index.analysis.tokenizer.custom_bigram_tokenizer.type: nGram
index.analysis.tokenizer.custom_bigram_tokenizer.min_gram: 2
index.analysis.tokenizer.custom_bigram_tokenizer.max_gram: 2
index.analysis.tokenizer.custom_bigram_tokenizer.token_chars.0: letter
index.analysis.tokenizer.custom_bigram_tokenizer.token_chars.1: digit

となっており、2-gramで分解する設定です。こんにちはの例ですと

こん, んに, にち, ちは

という分解を行います。 で、なんでインデックス時と検索時のアナライザを別々に定義しているのかというと、1文字による検索でも何らかの検索結果を返したいと考えて設計したからです。

2-gramのみでインデックスしてしまうと、1文字による検索に一切マッチしません。 一方、検索時は2-gramのみをおこなっていますが、このアナライザに1文字の検索クエリをなげると、

# 以下はアナライザの動作確認をおこなうリクエスト
$ curl -XGET 'http://localhost:9200/blog/_analyze?analyzer=default_search&pretty=true' -d 'a'
{
  "tokens" : [ ]
}

# 検索
$ curl -XGET http://localhost:9200/blog/article/_search?pretty=true -d '{"query": {"match":{"_all":"a"}}}'
{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }

のように、トークンが空になってしまい、そのままですとやはり検索に引っかかりません。 そこで一文字の時は、検索にアナライザを利用しないようにqueryをmatchからtermに変更して、以下のようなリクエストをなげるようにしました。

$ curl -XGET http://localhost:9200/blog/article/_search?pretty=true -d '{"query": {"term":{"_all":"a"}}}'

これで一文字のときも検索に引っかかるようになります。

マッピング

今回はまず導入ということで、フィールドに対してマッピングは行いませんでした。 マッピングは個々のフィールドに詳細な設定ができる、RDBMSでいうところのスキーマ定義に当たります。 マッピングを使えば特定のフィールド(たとえばブログのタイトル部分だけ)に特定のアナライザを適用したりとか、もっと高度で効率的な検索もできると思います。 しかし上記のデフォルトアナライザの設定だけでも、ドキュメントのテキスト部分全体に対してNgram検索がかけられて、いい感じの検索結果を得ることができています。

epelにdockerがキタ━(゚∀゚)━!ので、CentOSにインストールした。

ローカルのVirtualBox上のCentOS6で動かしました。epelリポジトリは事前に設定してあるものとします。

インストール

# yum install --enablerepo=epel docker-io

サービスの起動

# /etc/init.d/docker start

doceker runする!

# docker run -t -i centos /bin/bash

コンテナ内でシェルが起動します。echoとか打ってみる。

bash-4.1# echo hello world!
hello world!
bash-4.1# exit

うごく~( ´∀`)♪

Dockerfileからimageつくってみる

Dockerfile

FROM centos
RUN yum clean all
RUN yum install -y openssh-server
RUN yum install -y passwd
RUN echo d0cker | passwd --stdin root

## https://github.com/dotcloud/docker/issues/1240#issuecomment-21807183
RUN echo "NETWORKING=yes" > /etc/sysconfig/network

## http://gaijin-nippon.blogspot.com/2013/07/audit-on-lxc-host.html
RUN sed -i -e '/pam_loginuid\.so/ d' /etc/pam.d/sshd

EXPOSE 22
CMD /sbin/init

docker buildする!

# docker build -t test .
Uploading context 10240 bytes
Step 1 : FROM centos
 ---> 539c0211cd76
Step 2 : RUN yum clean all
 ---> Using cache
 ---> f738a5199bab
Step 3 : RUN yum install -y openssh-server
 ---> Running in 033f90a21823
Loaded plugins: fastestmirror
Determining fastest mirrors
Error: Cannot find a valid baseurl for repo: base
Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os error was
14: PYCURL ERROR 6 - "Couldn't resolve host 'mirrorlist.centos.org'"
Error build: The command [/bin/sh -c yum install -y openssh-server] returned a non-zero code: 1

おちた~(T_T)!!なんでかコンテナからインターネットに接続できん。のおおお!

2013-12-07追記:とおもったらVirtulaBoxのVMから作りなおしたら出来た。何がわるかったかわからんす。

2013-12-10追記:ネット接続できない問題は/etc/sysctl.confnet.ipv4.ip_forward = 1にしたら治ったのかもしれない。でもCMD /sbin/initは結局うまく動いてくれない。Dockerで/sbin/initを動かすのはGithubのissuseにもあがっていて、みんな試行錯誤してるようだけど、まだ解決していないっぽいですな。