まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:elasticsearch

IBM Bluemix 上でスケーラブルな検索エンジンである ElasticSearch サービスが利用可能になりました:
https://console.ng.bluemix.net/catalog/services/compose-for-elasticsearch/

2016092906


実は以前からこの Compose for ElasticSearch サービスは利用可能な状態ではありましたが、まだ Bluemix との統合が完全ではなく、別途 Compose 社との契約が必要になる状況でした。それが先日 2016/Sep/22 のアップデートで Bluemix 画面から直接契約可能(というか、サービスとして追加やバインドが可能)になったのでした。

実際にサービスとして追加し、バインドしたランタイムからはこの ElasticSearch サービスを利用するための接続情報が環境変数から確認できます:
2016092901


この環境変数 VCAP_SERVICES 内の "compose-for-elasticsearch" と書かれたエントリー内 "credentials" の "uri" に記載された値が ElasticSearch サービス(のドキュメントルート)にアクセスするための URL になります(この図の XXXX 部分がユーザー名、YYYY 部分がパスワードになるので、取扱いにはご注意ください):
  :
  :
  "compose-for-elasticsearch": [
    {
      "credentials": {
        "db_type": "elastic_search",
          :
        "uri": "https://XXXX:YYYY@bluemix-ZZZZZZ.dblayer.com:NNNNN/"
          :
    }
  ]
  :
  :

試しに同 URL にブラウザでアクセスすると、ElasticSearch サービスのバージョン情報などの概要を確認することができます。この例だとバージョン 2.4.0 が動いていることがわかります:
2016092902


この ElasticSearch サービスで既に導入されているプラグインはあるのでしょうか?その確認には、以下の URL (上記 uri の値に "_nodes?plugin=true&pretty" を足したもの)にブラウザや curl などで GET アクセスします:
(上記 "uri" の値)_nodes?plugin=true&pretty

するとプラグイン情報を含むサーバー情報が整形された形で確認できるような結果が表示されます:
2016092903


この画面内で "plugins" を検索してスクロールすると、実際に導入済みのプラグインの一覧を確認できます。この例では "cloud-aws" と "head" と "kopf" の3つのプラグインが導入済みであることが分かりました:
2016092904


head プラグインは ElasticSearch では定番プラグインの1つで、ElasticSearch クラスタのウェブインターフェースを提供してくれるプラグインです。なので "uri" の値の最後に "_plugin/head" を付けてアクセスすると、head プラグインの画面が(特にカスタマイズ無しで)表示することができます:
2016092905


Bluemix がまた1つ便利になりました。


(注 今回のエントリは以下の内容が古くなってしまったため、新たに書き直したものです)



全文検索エンジン ElasticSearch に日本語形態素解析ソフトウェア Kuromoji のプラグインを導入して日本語全文検索エンジンを作ります。なおプラットフォームは CentOS 6 を前提とします。


まず ElasticSearch の動作に必要な Java 環境を用意します。Oracle Java を導入しても構いませんが、Open Java であれば以下のコマンドで導入可能です(Java 7 の場合):
# yum install java-1.7.0-openjdk

次に検索エンジンである ElasticSearch 本体を導入します。公式サイトから最新版(2016/07/11時点では 2.3.4)のインストールモジュールをダウンロードします。インストールモジュールにはいくつかの種類はありますが、今回は rpm パッケージ版(elasticsearch-2.3.4.rpm)をダウンロードします:
https://www.elastic.co/downloads/elasticsearch

2016071101


ダウンロードできたら rpm コマンドでインストールして起動、および自動起動設定までを行います。ちなみにこの rpm 版をインストールした場合、ElasticSearch 本体は /usr/share/elasticsearch/ 以下に導入されます :
# rpm -ivh elasticsearch-2.3.4.rpm
# /etc/init.d/elasticsearch start
# chkconfig elasticsearch on

これで ElasticSearch 本体の導入は完了しました。続けて Kuromoji (とウェブ GUI である head)を導入します。いったん ElasticSearch を止めた上で本体のディレクトリに移動し、それぞれのプラグインを導入します:
# /etc/init.d/elasticsearch stop
# cd /usr/share/elasticsearch
# bin/plugin install analysis-kuromoji
# bin/plugin install mobz/elasticsearch-head

設定ファイル(/etc/elasticsearch/elasticsearch.yml)を編集します。まずデフォルトの状態だとウェブ GUI にはローカルホストからでないとアクセスできません。何かと不便なので外部からもアクセスできるように変更します(以下の例では全てのホストからのアクセスを許可しています)。また解析エンジンのデフォルトとして Kuromoji を利用するよう変更します:
(/etc/elasticsearch/elasticsearch.yml)

# allow http connection from every hosts
network.host: 0.0.0.0

# set kuromoji as default Tokenizer
index.analysis.analyzer.default.type: custom
index.analysis.analyzer.default.tokenizer: kuromoji_tokenizer

この状態で ElasticSearch を起動すれば日本語全文検索エンジンとしての ElasticSearch が、ウェブ GUI 付きで稼働します:
# /etc/init.d/elasticsearch start

ウェブ GUI にアクセスするにはウェブブラウザから http://(ElasticSearch のホスト):9200/_plugin/head/ にアクセスします:

2016071102


では実際に日本語データを挿入して検索してみましょう。以下の内容のテキストを manho.json という名前で保存します:
(manho.json)

{"create":{"_id":"4442072443477838363"}}
{"id": 4442072443477838363,"username": "michelle_yama","created": "2016/07/11 12:49:25","updated": "2016/07/11 12:49:25","tag": "","filename": "","type": "image/jpeg","address": "","text": "","lat": 35.6565093994141,"lng": 139.752868652344,"nice": 0,"niceby": "","width": 480,"height": 360,"imgkey": "","misc": "東京都港区芝大門2丁目1"}

{"create":{"_id":"5545583388040541151"}}
{"id": 5545583388040541151,"username": "42ER03","created": "2016/07/11 05:59:20","updated": "2016/07/11 05:59:20","tag": "市章と市の花「ツツジ」","filename": "20051119兵庫県加古川市山手2丁目29-12地先_ .jpg","type": "image/jpeg","address": "","text": "平成17年11月撮影","lat": 34.78293426048821,"lng": 134.88915952863033,"nice": 0,"niceby": "","width": 480,"height": 480,"imgkey": "","misc": "兵庫県加古川市山手2丁目27"}

{"create":{"_id":"1831807387175567807"}}
{"id": 1831807387175567807,"username": "ippei0605","created": "2016/07/10 23:07:24","updated": "2016/07/11 16:01:52","tag": "居合の稽古","filename": "image.jpeg","type": "image/jpeg","address": "","text": "","lat": 35.76108611111111,"lng": 139.5356611111111,"nice": 1,"niceby": "dotnsf","width": 480,"height": 360,"imgkey": "","misc": "東京都東久留米市新川町1丁目10 都道125号線"}

{"create":{"_id":"3546975096233083798"}}
{"id": 3546975096233083798,"username": "dotnsf","created": "2016/07/10 22:28:51","updated": "2016/07/10 22:28:51","tag": "hiroshima shobara","filename": "hilside.jpg","type": "image/jpeg","address": "","text": "国営備北丘陵公園内のヒルサイドパークマンホール","lat": 34.83977596170017,"lng": 132.99681892575074,"nice": 0,"niceby": "","width": 269,"height": 480,"imgkey": "","misc": "広島県庄原市上原町1300"}

{"create":{"_id":"4946513086810703103"}}
{"id": 4946513086810703103,"username": "minamu4545","created": "2016/07/10 21:06:11","updated": "2016/07/10 21:08:31","tag": "","filename": "image.jpeg","type": "image/jpeg","address": "","text": "某水泳アニメの聖地となった岩美町の色蓋(おすい)。<br/>浦富海岸が描かれて美しい蓋。此の蓋もかなり美しいですが、實際の海岸はもっと美しかったです。<br/>色蓋は浦富海岸沿いの縣道155號線にたくさん設置されてますが、路上なので状態は惡いものが多いです。<br/>此の蓋もガムが付いて無ければ完璧だったのですが殘念です。蓋にガム捨てるのほんとやめて欲しい。<br/>","lat": 35.59056896685199,"lng": 134.32106172622503,"nice": 0,"niceby": "","width": 480,"height": 480,"imgkey": "","misc": "鳥取県岩美郡岩美町大字浦富 県道155号線"}

{"create":{"_id":"7542390449304551253"}}
{"id": 7542390449304551253,"username": "42ER03","created": "2016/07/10 07:50:19","updated": "2016/07/10 07:50:19","tag": "ビルバオ市の市章","filename": "20140731③ビルバオ03_.jpg","type": "image/jpeg","address": "","text": "2014年07月撮影/グッゲンハイム美術館付近","lat": 43.26813637672919,"lng": -2.9336467575902736,"nice": 0,"niceby": "","width": 480,"height": 480,"imgkey": "","misc": "スペイン"}

{"create":{"_id":"8484297972018100687"}}
{"id": 8484297972018100687,"username": "42ER03","created": "2016/07/09 08:17:30","updated": "2016/07/09 08:17:30","tag": "高崎まつり","filename": "20160610,1156,03群馬県高崎市高崎駅東口_.jpg","type": "image/jpeg","address": "","text": "平成28年06月撮影","lat": 36.32132769063828,"lng": 139.01428243101884,"nice": 0,"niceby": "","width": 480,"height": 480,"imgkey": "","misc": "群馬県高崎市栄町16"}

{"create":{"_id":"4255462064362665608"}}
{"id": 4255462064362665608,"username": "minamu4545","created": "2016/07/08 19:42:02","updated": "2016/07/08 19:43:49","tag": "","filename": "image.jpeg","type": "image/jpeg","address": "","text": "にかほ市(旧象潟町)小滝地區の農業集落排水の色蓋。<br/>描かれているのは奈曽の白滝。<br/>奈曽の白滝の駐車場前の歩道に設置されていますが劣化が激しいです。<br/>ちなみに、駐車場の横には農業集落排水の処理場があります。","lat": 39.18138258105665,"lng": 139.94690930427362,"nice": 0,"niceby": "","width": 480,"height": 480,"imgkey": "","misc": "秋田県にかほ市象潟町小滝"}

{"create":{"_id":"5394969523646972723"}}
{"id": 5394969523646972723,"username": "ujiteaa","created": "2016/07/08 06:08:46","updated": "2016/07/08 06:08:46","tag": "埼玉県川越市旭町","filename": "_20160708_053352.JPG","type": "image/jpeg","address": "","text": "","lat": 35.690625,"lng": 139.699788,"nice": 0,"niceby": "","width": 473,"height": 480,"imgkey": "","misc": "東京都新宿区西新宿1丁目1"}

{"create":{"_id":"3662100910616235567"}}
{"id": 3662100910616235567,"username": "ujiteaa","created": "2016/07/08 06:07:30","updated": "2016/07/08 06:07:30","tag": "埼玉県川越市","filename": "_20160708_053352.JPG","type": "image/jpeg","address": "","text": "","lat": 35.690625,"lng": 139.699788,"nice": 0,"niceby": "","width": 473,"height": 480,"imgkey": "","misc": "東京都新宿区西新宿1丁目1"}

この内容をバルク API でまとめて挿入します。以下の例ではインデックスに manholes、タイプに geo を指定しています:
# curl -XPOST http://localhost:9200/manholes/geo/_bulk --data-binary @manho.json

ではこの状態で「美しさ」というキーワードで検索してみましょう:
# curl -X GET http://localhost:9200/manholes/geo/_search -d '{"query":{"match":{"text":"美しさ"}}}'

{
  :
 "hits":{
  "total":2,
  "max_score":0.045926645,
  "hits":[
   {
    "_index":"manholes",
    "_type":"geo",
    "_id":"4946513086810703103",
    "_score":0.045926645,
    "_source":{
     "id": 4946513086810703103,
       :
     "text": "某水泳アニメの聖地となった岩美町の色蓋(おすい)。&lt;br/&gt;浦 富海岸が描かれて美しい蓋。此の蓋もかなり美しいですが、實際の海岸はもっと美しかったです。&lt;br/&gt;色蓋は浦富海岸沿いの縣道155號線にたくさん設置されてますが、路上なので状態は惡いものが多いです。&lt;br/&gt;此の蓋もガムが付いて無ければ完璧だ ったのですが殘念です。蓋にガム捨てるのほんとやめて欲しい。&lt;br/&gt;",
       :
   },
   {
    "_index":"manholes",
    "_type":"geo",
    "_id":"4255462064362665608",
    "_score":0.0049227546,
    "_source":{
     "id": 4255462064362665608,
       :
     "text": "にかほ市(旧象潟町)小滝地區の農業集落排水の色蓋。&lt;br/&gt;描かれているのは奈曽の白滝。&lt;br/&gt;奈曽の白滝の駐車場前の歩道に設置されていますが劣化が激しいです。&lt;br/&gt;ちなみに、駐 車場の横には農業集落排水の処理場があります。",
       :
   }
  ]
 }
}

「美しさ」で完全一致するデータは存在しませんが、「美しい」でヒットしたようです。正しく日本語が形態素解析されて判断されていることがわかります。

というわけで、日本語全文検索エンジンの出来上がり!


IBM Bluemix にサードパーティ製サービス Searchly が追加されました:
2015113001


Searchly は検索エンジンでは非常に多くのサイトで利用されている ElasticSearch の SaaS サービスです。この機能が IBM Bluemix のサービスとしても提供されるようになり、外部 API として検索エンジンの機能(登録、更新、検索、削除など)が提供されています。Searchly 自体は有償サービスですが、無料で利用できるプランも提供されています。

IBM Bluemix では、ElasticSearch のサービスとしては Searchly 以前にも IBM が買収した Compose からも ElasticSearch サービスが提供されています。この2つのサービスの違いを確認する意味も含めて、Searchly を使ってみました。

サービスそのものは既に IBM Bluemix から利用できます。「Web とアプリケーション」カテゴリの中に "Searchly" という名前で登録されています:
2015113002


Searchly サービスには、その利用レベルに応じていくつかのエディションが用意されています。ここでは無料で利用できる "Starter" エディションを選択して使ってみます。なお、この Starter エディションでは2つのインデックスが使え、ストレージは 5MB まで利用できるようです:
2015113001


Searchly サービスを追加したら、Bluemix ダッシュボードから資格情報を参照して credentials の uri (または sslUri)の値を確認しておきます:
2015113001


以下では、この uri の値が以下であったとして、説明を続けます。また今回は ssl は使わない例として紹介します。実際の使い方に併せて適宜読み替えてください:
      :
  "credentials": {
    "uri": "http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com",
        :
  }
      :

サービスの追加ができていれば、既に(普通に) ElasticSearch が使える状態になっています。ここからは curl コマンドを使って、インデックスおよびタイプを作成/マッピングし、いくつかのドキュメントを実際に登録して検索クエリーを発行してみます。なお、ここで登録するデータは拙作「マンホールマップ」の登録データを使うことにします:
マンホールマップ


インデックスおよびタイプの定義としては、以下の様な条件でインデックスおよびタイプを作成し、ドキュメントを登録していきます:
属性設定値
インデックスdotnsf-index1
タイプmanhole
タイプのマッピング
craeted:date(yyyy/MM/dd HH:mm:ss)
text:string
lat:double
lng:double
nice:integer
misc:string


また、このインデックスには以下の5ドキュメントを登録することにします:
_id登録ドキュメント
3352100918908634136{"created": "2015/11/30 07:02:25","text": "2014年07月撮影 SANEAMIENTOをGoogle翻訳で翻訳すると、「衛生」と訳される。","lat": 36.54225675978826,"lng": -4.623699016177132,"nice": 0,"misc": "スペイン"}
5503588762756270641{"created": "2015/11/29 17:04:47","text": "","lat": 34.936504361111105,"lng": 139.07788083333332,"nice": 0,"misc": "静岡県伊東市鎌田"}
7291753577358145111 {"created": "2015/11/29 16:39:15","text": "大井競馬場内の東京都章マンホール","lat": 35.5938,"lng": 139.742875,"nice": 1,"misc": "東京都品川区勝島2丁目1"}
6201898997178416785{"created": "2015/11/29 15:13:24","text": "","lat": 34.70050555555556,"lng": 135.19425555555557,"nice": 0,"misc": "兵庫県神戸市中央区加納町2丁目6 県道30号線"}
3419746974473077161{"created": "2015/11/29 14:09:21","text": "","lat": 35.4247131347656,"lng": 139.248657226562,"nice": 0,"misc": "神奈川県伊勢原市大山"}


では実際に Searchly に作成したサービスを使って作業してみましょう。まずはインデックスを登録します。今回は dotnsf-index1 という名前でインデックスを作成します:
$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/

{"ok":true,"acknowledged":true}

※黒文字が入力コマンド、青文字が出力結果です。以下同様


続いて manhole という名前でタイプを作成し、上の表の内容でタイプ定義をマッピングします:
$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/_mapping -d '{
 "manhole": {
  "properties": {
   "created": { "type": "date", "format": "yyyy/MM/dd HH:mm:ss" },
   "text": { "type": "string" },
   "lat": { "type": "double" },
   "lng": { "type": "double" },
   "nice": { "type": "integer" },
   "misc": { "type": "string" }
  }
 }
}'

{"ok":true,"acknowledged":true}

これでタイプが定義できたので、いくつかのドキュメントを _id を指定しながら追加していきます:
$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/3352100918908634136 -d '{"created": "2015/11/30 07:02:25","text": "2014年07月撮影 <br/>SANEAMIENTOをGoogle翻訳で翻訳すると、「衛生」と訳される。","lat": 36.54225675978826,"lng": -4.623699016177132,"nice": 0,"misc": "スペイン"}'

{"ok":true,"acknowledged":true}

$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/5503588762756270641 -d '{"created": "2015/11/29 17:04:47","text": "","lat": 34.936504361111105,"lng": 139.07788083333332,"nice": 0,"misc": "静岡県伊東市鎌田"}'

{"ok":true,"acknowledged":true}

$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/7291753577358145111 -d '{"created": "2015/11/29 16:39:15","text": "大井競馬場内の東京都章マンホール","lat": 35.5938,"lng": 139.742875,"nice": 1,"misc": "東京都品川区勝島2丁目1"}'

{"ok":true,"acknowledged":true}

$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/6201898997178416785 -d '{"created": "2015/11/29 15:13:24","text": "","lat": 34.70050555555556,"lng": 135.19425555555557,"nice": 0,"misc": "兵庫県神戸市中央区加納町2丁目6 県道30号線"}'

{"ok":true,"acknowledged":true}

$ curl -XPUT http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/3419746974473077161 -d '{"created": "2015/11/29 14:09:21","text": "","lat": 35.4247131347656,"lng": 139.248657226562,"nice": 0,"misc": "神奈川県伊勢原市大山"}'

{"ok":true,"acknowledged":true}

これで5ドキュメントが登録できました。では実際にいくつかの条件で ElasticSearch の検索を試してみましょう(赤字はコメント):
$ curl -XGET http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/7291753577358145111

{"_index":"dotnsf-index1","_type":"manhole","_id":"7291753577358145111","_version":1,"found":true,"_source":
{"created": "2015/11/29 16:39:15","text": "大井競馬場内の東京都章マンホール","lat": 35.5938,"lng": 139.742875,"nice": 1,"misc": "東京都品川区勝島2丁目1"}}

↑_id = 7291753577358145111 のドキュメントをピンポイントで検索

$ curl -XGET http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/_search -d '{"query":{"match":{"nice":1}}}'

{"hits":{"total":1,"max_score":1.0,"hits":[{"_index":"dotnsf-index1","_type":"manhole","_id":"7291753577358145111","_score":1.0,"_source":{"created":"2015/11/29 16:39:15","text":"大井競馬場内の東京都章マンホール","lat":35.5938,"lng":139.742875,"nice":1,"misc":"東京都品川区勝島2丁目1"}}]},"_shards":{"total":1,"successful":1,"failed":0},"timed_out":false,"took":1}

↑nice == 1 のドキュメントを検索。ヒット結果は1件。

$ curl -XGET http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/_search -d '{"query":{"range":{"lng":{"gt":139.0,"lt":139.2}}}}'

{"hits":{"total":1,"max_score":1.0,"hits":[{"_index":"dotnsf-index1","_type":"manhole","_id":"5503588762756270641","_score":1.0,"_source":{"created":"2015/11/29 17:04:47","text":"","lat":34.936504361111105,"lng":139.07788083333332,"nice":0,"misc":"静岡県伊東市鎌田"}}]},"_shards":{"total":1,"successful":1,"failed":0},"timed_out":false,"took":1}

↑lng の値が 139.0 より大きく、139.2 より小さいドキュメントを検索。ヒット結果は1件。

おお、期待通りにちゃんと動いてる!感動!!


調子に乗って日本語キーワードで検索をかけてみると・・・
$ curl -XGET http://XXXXXXXX:YYYYYYYYYY@dwalin-us-east-1.searchly.com/dotnsf-index1/manhole/_search -d '{"query":{"match":{"misc":"東京都"}}}'

{"hits":{"total":2,"max_score":0.77568257,"hits":[{"_index":"dotnsf-index1","_type":"manhole","_id":"7291753577358145111","_score":0.77568257,"_source":{"created":"2015/11/29 16:39:15","text":"大井競馬場内の東京都章マンホール","lat":35.5938,"lng":139.742875,"nice":1,"misc":"東京都品川区勝島2丁目1"}},{"_index":"dotnsf-index1","_type":"manhole","_id":"5503588762756270641","_score":0.07663258,"_source":{"created":"2015/11/29 17:04:47","text":"","lat":34.936504361111105,"lng":139.07788083333332,"nice":0,"misc":"静岡県伊東市鎌田"}}]},"_shards":{"total":1,"successful":1,"failed":0},"timed_out":false,"took":2}

↑misc に「東京都」を含むドキュメントを検索。1件がヒットするはずが実際には2件ヒット。

うーん、日本語検索には対応してないのか。。

どうやらこれが Searchly の制約っぽいです。一般的に ElasticSearch で日本語を使う場合はユーザー辞書に kuromoji とかを指定して使うことが多いと思うのですが、Searchly ではユーザー側にその権限はなさそうです:

ちなみに IBM Compose の ElasticSearch サービスを使った場合は kuromoji プラグインの利用もできそう:
https://help.compose.io/docs/elasticsearch-faq


現時点では Bluemix で日本語検索エンジンを使うには Compose ElasticSearch を選ぶ必要がありそうです:
https://console.ng.bluemix.net/catalog/services/elasticsearch-by-compose/



なお、Searchly サービスではインデックスの利用状況が確認できるダッシュボード機能も提供しています。ダッシュボードにアクセスするには Bluemix で追加したサービスのアイコン部分をクリックします:
2015113002


すると「SEARCHLY ダッシュボードを開く」という画面が表示されるので、ここをクリックしてダッシュボードに移動します:
2015113003


Searchly のダッシュボードが表示されました。既に色々使っているので、その利用状況概要が表示されています:
2015113004



インデックスの状態を確認してみましょう。メニューから Dashboard - Indices を選択します:
2015113005


インデックスの状態が表示されます。コマンドで作成した dotnsf-index1 が表示されているはずです。また、この各インデックスの状態を確認するには "Stats" と書かれたリンクをクリックします:
2015113006


指定したインデックスの Stats が確認できます:
2015113007


他にも色々、ダッシュボード的に状態を確認できて便利な画面です。基本的には API で操作することを想定したサービスですが、状況の確認であればこの画面からでもできそうだという印象です。


 

IBM が Compose を買収しました:
IBM、DBaaSプロバイダーのComposeを買収

Compose は MongoDB や Redis といったオープンソースデータベースを DBaaS で提供していた企業です。Compose のサービスは早速 IBM Bluemix に統合され、既に Bluemix サービスとして個々の DBaaS が選択・利用できるようになっています:
2015072601


MongoDB や Redis に関しては(データベースとしては)既に Bluemix で使えるようになっていました。が、今回の統合で新たに Bluemix で使えるようになったサービスもあります。

それが ElasticSearch です。高速な NoSQL データベースでもあり、ユーザーの多いオープンソース全文検索エンジンとして知られていましたが、ついにこの ElasticSearch の DBaaS が Bluemix のサービスの1つとして選択できるようになりました:
2015072602


特に、これまで Bluemix には検索エンジンサービスが搭載されていなかったため、アプリケーション内での検索機能を作ろうとすると、外部のサービスを使ったり、自分で個別に検索機能を実装する必要がありました。これからは検索エンジンも Bluemix 内のものが(しかも ElasticSearch が)使えるようになります!

一人のプログラマーとしての立場でも、非常に嬉しく楽しみな新サービスです。


最近のマイブームの1つになっている、全文検索エンジン ElasticSearch に MySQL のデータを取り込んで、MySQL データベースの全文検索エンジンとして ElasticSearch を使う手順の紹介です。
2014070401


まず検索エンジンである ElasticSearch を導入します。日本語形態素解析エンジンである Kuromoji まで含めてのインストール手順を別エントリで紹介しているので、こちらを参照ください:
ElasticSearch に Kuromoji プラグインを導入する


また取り込み先である MySQL サーバーについても環境は構築済みであると仮定します。こちらの構築手順についても、こちらのエントリを参照ください:
CentOS に MySQL をインストール/セットアップする

なお、自分個人的には MySQL ではなく MariaDB を使って同じことをできているので、以下の内容に関しては MariaDB でも同様に可能だと思っています。


さて、ElasticSearch に MySQL データを取り込むための準備として ElasticSearch 自体のインストール後に以下のステップを行う必要があります:
1. (MySQL クライアントと)JDBC ドライバの導入
2. JDBC River プラグインのインストール


最終的には ElasticSearch の River プラグインと呼ばれる拡張機能を使って MySQL からのデータ取り込みを行います。このプラグインの動作に必要な MySQL JDBC ドライバを先にインストールしておく、というステップになります。


まず、これは必須ではありませんが、あると確認に便利なので MySQL のクライアント環境を ElasticSearch サーバー内に構築しておきます。MySQL サーバーに接続する機能があればいいので、MySQL サーバー機能は不要で、クライアント機能だけが必要、ということになります。もし MySQL サーバーと ElasticSearch サーバーが同じサーバーだとすると、既に MySQL クライアント環境は導入済みだと思うので、インストールは不要です。 MySQL クライアントが導入されていない場合は以下のコマンドで MySQL クライアントをインストールします:
# yum install mysql
# vi /etc/my.cnf
  :
(以下の2行を追加)
[mysql]
default-character-set=utf8

MySQL クライアントが導入できた所で、取り込み元の MySQL サーバーへ接続してみます。仮に今回取り込むデータの内容が以下であると仮定します:
 MySQL サーバー: mysql.mylocal.com
 ユーザー名: username
 パスワード: password
 データベース名: mydb
 取り込む内容: samples テーブル


実際に MySQL クライアントで目的のデータベースにアクセスしてみます。ここまでが出来るようであればファイアウォールなども含めて接続準備ができているといえます:
# mysql -h mysql.mylocal.com -u username -ppassword mydb
> select * from samples;
:
:
(samples テーブルの内容)
:
: > quit


次に MySQL サーバーへ Java 環境から接続するための JDBC ドライバー(MySQL Connector/J)を導入します。ドライバー自体はこちらのサイトからダウンロードできます:
MySQL :: Download Connector/J 


ダウンロードしたファイルを展開して JAR ファイルを取り出し、/usr/share/java にコピーします:
# unzip mysql-connector-java-5.1.30.zip
# cd mysql-connector-java-5.1.30
# cp mysql-connector-java-5.1.30-bin.jar /usr/share/java

環境変数 CLASSPATH に、この JAR ファイルを追加します:
# vi /etc/bashrc
  :
(以下の1行を追加)
export CLASSPATH=$CLASSPATH:/usr/share/java/mysql-connector-java-5.1.30-bin.jar
 

# source /etc/bashrc

JDBC ドライバの準備が出来た所で River プラグインをインストールします。リポジトリを確認したところ、2014/07/01 時点での最新バージョンは 1.2.1.1 だったので、このバージョンを指定して導入します:
# /usr/share/elasticsearch/bin/plugin --install jdbc --url http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.2.1.1/elasticsearch-river-jdbc-1.2.1.1-plugin.zip
# cp /usr/share/java/mysql-connector-java-5.1.30-bin.jar /usr/share/elasticsearch/plugins/jdbc/

これで必要なソフトウェアは揃いました。では実際に MySQL からデータを取り込んでみましょう

まず ElasticSearch 側に kuromoji を使った検索インデックス(kuromoji_sample)を作成します。インデックスの作成については別エントリでも紹介しましたが、まだこの内容を実行していない場合は以下のコマンドを実行します(コマンドは黒字部分で、青字はレスポンスを表しています):
# curl -XPUT http://localhost:9200/kuromoji_sample -d '{ "index": { "analysis": { "tokenizer": { "kuromoji_user_dict" : { "type":"kuromoji_tokenizer" } }, "analyzer": { "analyzer": { "type":"custom", "tokenizer": "kuromoji_user_dict" } } } } }'
{"acknowledged":true}

次に River を使って、作成した kuromoji_sample インデックスに MySQL データベースサーバーからデータを取り込みます:
# curl -XPUT http://localhost:9200/_river/my_jdbc_river/_meta -d '{ "type": "jdbc", "jdbc": { "url": "jdbc:mysql://mysql.mylocal.com:3306/mydb", "user": "username", "password": "password", "sql": "select * from samples", "index": "kuromoji_sample", "type": "samples" } }'
{"_index": "_river", "_type": "my_jdbc_river", "_id": "_meta", "_version": 1, "created": true}

上記入力パラメータ(JSON)の中で選択(select)の SQL を発行しています。この SQL の実行結果が ElasticSearch に取り込まれることになります。

取り込みができたら、最後に検索してみます。この例では取り込んだデータの name フィールドに「ほげほげ」が含まれているデータを検索しています:
# curl -XPOST http://localhost:9200/kuromoji_sample/_search?pretty -d '{ "query": { "query_string": { "query": "name:ほげほげ" } } }'
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 29,
    "max_score" : 3.687642,
    "hits" : [ {
      "_index" : "kuromoji_sample",
      "_id" : "XXXXXXXXXXXXXX",
      "_score" : 3.687642,
      "_source":{"id":"1234","name":"ほげほげ"}
    }, {
      "_index" : "kuromoji_sample",
        :
    } ]
  }
}

こんな感じで実現できました。

MySQL の like 節を使った単純検索をしていた頃と比べると、以前は localhost 内の MySQL に対して検索していたのでネットワークによる遅延はほとんどなかったはずで、今回作ってみた環境はリモートの ElasticSearch 環境にアクセスしているので、ネットワークの遅延影響がでるはずです。

にも関わらず、検索パフォーマンスは 10 倍程度になりました。これはでかい!

ElasticSearch の検索パターンやその API 実行方法についてはいずれまたプログに書く予定です。


 

このページのトップヘ