疑問文

PythonとかRubyについて、疑問や学びをまとめる場所

勉強しないと。しないと。

MySQLからElasticsearchにデータをインポートする

背景

アプリケーションを作る中で全文検索やらファセットが必要になったので、DBのデータをElasticsearchにインポートしようと思い立ちました。その際に試行錯誤した内容のまとめです。インストール周りの作業は基本的にansibleを使ってます。

方法の検討

2つの方法を試してみました。
(正確には片方を苦労してやったあとでもう片方を見つけて泣く泣くやり直した)

方法1. JDBC importer

github.com

The Java Database Connection (JDBC) importer allows to fetch data from JDBC sources for indexing into Elasticsearch.

Java DataBase Connectionに準拠しているソースのデータをインポートできる。業務のSolrにもこれ使ってた気がするし、スター1000個もついてるし安心!と思いました。

方法2. Embulk

github.com

Embulk is a parallel bulk data loader that helps data transfer between various storages, databases, NoSQL and cloud services.

Embulk supports plugins to add functions. You can share the plugins to keep your custom scripts readable, maintainable, and reusable.

OSSのデータ転送ソフトウェア。fluentdのバッチ版と言えばわかりやすいらしいけど、実はfluentdをまともに使ったことがなかった。データ転送のインプットとアウトプットがプラグインになっていて、input-mysqlとoutput-elasticsearchを使えば今回の目的は達成できそう。

結論

どちらの方法でも今回の作業は可能。ただ、Embulkの方はインプットとアウトプットを他のデータストアに変えても使えそうだったので、つぶしが効くというメリットでEmbulkを使うことにしました。

ただせっかく両方やったので、作業内容と注意点は両方とも書いてみます。

作業

前提

$ mysql --version
mysql  Ver 14.14 Distrib 5.1.73, for redhat-linux-gnu (x86_64) using readline 5.1

$ curl localhost:9200
{
  "status" : 200,
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.7.1",
    "build_hash" : "b88f43fc40b0bcd7f173a1f9ee2e97816de80b19",
    "build_timestamp" : "2015-07-29T09:54:16Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.4"
  },
  "tagline" : "You Know, for Search"
}

JDBC importerでインポート

参考にさせて頂いたページ

qiita.com

手順

JDBCツールのインストール

ansible: jdbcロールのtasks
- name: download jdbc                                              
  get_url: url="{{jdbc_url}}" dest=/tmp                                                  

- name: unzip jdbc
  unarchive: src=/tmp/{{jdbc_package}}.zip dest=/usr/local/src copy=no
ansible: jdbcロールのvars
jdbc_url: http://xbib.org/repository/org/xbib/elasticsearch/importer/elasticsearch-jdbc/1.7.1.0/elasticsearch-jdbc-1.7.1.0-dist.zip
jdbc_package: elasticsearch-jdbc-1.7.1.0-dist

参考ページにも書かれていますが、jdbcのバージョンは完全にElasticsearchと合わせてます。これを守っていないと後の作業で詰まります。

関連ソース
Cannot connect to remote elasticsearch · Issue #567 · jprante/elasticsearch-jdbc · GitHub

スクリプトの作成

/usr/local/src/elasticsearch-jdbc-1.7.1.0/bin以下にmysql-delete-document.shというスクリプトがあるので、それをコピーして書き換えてください。

$ cd /usr/local/src/elasticsearch-jdbc-1.7.1.0/bin
$ cp mysql-delete-document.sh mysql2es.sh

以下、DB名、ユーザ名、パスワード、テーブル名、カラムは自身のものに置き換えてください。

# mysql2es.sh
  
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
bin=${DIR}/../bin
lib=${DIR}/../lib
echo '{
    "type" : "jdbc",
    "jdbc" : {
        "url" : "jdbc:mysql://localhost:3306/$DATABASE",
        "user" : "$USER",
        "password" : "$PASSWORD",
        "sql" : "SELECT id AS _id, title, url FROM $TABLE"
    }
}
' | java \
    -cp "${lib}/*" \
    -Dlog4j.configurationFile=${bin}/log4j2.xml \
    org.xbib.tools.Runner \
    org.xbib.tools.JDBCImporter

スクリプトの実行

素直に実行すればOKです。事前にElasticsearchを起動させておくのを忘れずに…。

$ cd /usr/local/src/elasticsearch-jdbc-1.7.1.0/bin
$ ./mysql2es.sh

結果の確認は省略。Elasticsearchにクエリを投げれば結果が返ってくるはずです。なお、今回は事前にマッピングを作成していないので、Elasticsearch側で動的に作成されます。文字列にアナライザをかけたりごにょごにょしたい場合は、先にやっておけばいいと思います。

Embulkでインポート

手順

Embulkのインストール

ダウンロード、コマンドへのシンボリックリンク作成、プラグインのインストールを行います。

自分のansible力が低いのと今回あまり時間がないのとで、shellモジュール使ってたり書き方が甘々なのはご容赦ください。varsの使い方とかももっと工夫できるはず…。

ansible: embulkロールのtasks
- name: download embulk
  get_url: url="{{embulk_url}}" dest=/usr/local/src
                                                                                                                                                                         
- name: make symlink for embulk
  file: src=/usr/local/src/{{embulk_package}} dest=/usr/local/bin/embulk state=link                                                                                      
                                                                                                                                                                         
- name: download plugins for embulk
  shell: /usr/local/bin/embulk gem install {{item}}
  with_items:
    - embulk-input-mysql
    - embulk-output-elasticsearch
ansible: embulkロールのvars
embulk_url: http://dl.embulk.org/embulk-latest.jar
embulk_package: embulk-0.7.4.jar
確認
$ /usr/local/bin/embulk --version
embulk 0.7.4

上記のように表示されればインストールは完了です。自分がこれをやったとき、バージョンの表示だけで1分以上かかって絶望したんですが、JDKをopenjdk1.8からoracleのjdk1.8に変更したら10倍くらいは高速になりました。一体なんだったのか…

参考
ansibleを学ぶ:vol02:Oracle JDK1.8のインストール、ユーザパスワード設定等 - 文系プログラマによるTIPSブログ

設定ファイルの作成

以下、変数部分とDBのカラムは自身のものに置き換えてください。

# config.yml

in:
  type: mysql
  host: localhost
  port: 3306
  user: $USER
  password: $PASSWORD
  database: $DATABASE
  query: |
    SELECT
      id, title, url
    FROM
      $TABLE

out:
  type: elasticsearch
  index: $INDEX
  index_type: $INDEX_TYPE
  nodes:
  - host: localhost 

ややこしいんですが、yaml内でキーになっているtypeはEmbulkのinoutのそれぞれで使われるプラグインを示しています。一方、index_typeはElasticsearchでインデックスに紐づく単位である「タイプ」を示しているようです。

変数の利用

Configuration — Embulk 0.7 documentation

Embulkのバージョン0.6.22以上では、RubyのLiquidテンプレートを使って変数が使えるようです。このテンプレートエンジンは初めて知りましたが、Smartyライクらしいので、twigやらjinja2を使ったことがある人はすぐ親しめると思います。

ファイル名のymlyml.liquidに変更して、中に変数を埋め込むだけで良いみたいです。DBのパスワードとかは変数にしておいて、サーバの環境変数から取り出すようにすれば便利そうですね。

Embulkの実行

# 設定ファイルの書き方が正しいかどうかの確認
$ /usr/local/bin/embulk preview /path/to/config.yml

# 実行
$ /usr/local/bin/embulk run /path/to/config.yml

結果の確認は省略。Elasticsearchにクエリを投げれば結果が返ってくるはずです。なお、今回は事前にマッピングを作成していないので、Elasticsearch側で動的に作成されます。文字列にアナライザをかけたりごにょごにょしたい場合は、先にやっておけばいいと思います。

まとめ

自分はEmbulkを選択したんですが、ただデータを入れる分にはどちらを使っても困らないと思います。Elasticsearch自体が流行ってるので、何するにしろググれば関連記事は出てくるはず。

ただ、AWSGCPの利用が一般的になったせいでデータの移動タスクがちょこちょこ発生しそうなので、Embulkがさらにメジャーになればよりエコで楽になる気がしました。