Y-Ken Studio

新しもの好きのデータエンジニアが四方山話をお届けします。

MySQLからelasticsearchへ、レコードをネスト構造化しつつ同期出来る fluent-plugin-mysql-replicator v0.4.0 を公開しました

elasticsearchは全文検索サーバとしても知名度を獲得しており、次のような記事も人気を集めています。

MySQLでは実現の難しかったLuceneならではの次のような特徴を兼ね備えたelasticsearchはとても魅力ですよね。

しかしながらelasticsearchを中核となるデータベースとして扱うにはまだ日が浅いことは事実です。
そこで、私と同様にMySQLを補う形でelasticsearchを使いたいという方にとって有用な情報を本日お届けしたいと思います。

fluent-plugin-mysql-replicator v0.4.0

MySQLテーブルへの更新/削除イベントを逐次取得するFluentdプラグインです。

y-ken/fluent-plugin-mysql-replicator

インストール/アップデート方法

Fluentdのプラグインとしての利用方法の他、独立したRPMパッケージをYamabikoとして公開しております。

### システム側のRubyにインストールする場合
gem install fluent-plugin-mysql-replicator

### td-agentにインストールする場合
/usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-mysql-replicator

### RPMパッケージ、Yamabikoでアップデートする場合
# インストールについては右記記事をご参照ください https://github.com/y-ken/yamabiko/releases
/usr/lib64/yamabiko/ruby/bin/fluent-gem install fluent-plugin-mysql-replicator

利用方法についてはこちらの記事も参考になります。

変更内容概要

v0.4.0は、新機能の実装のみです。

  • [新機能]ネスト構造化したドキュメントの生成に対応

新機能紹介

今回、「ネスト構造化したドキュメントの生成」に対応しました。

MySQLなどのRDBMSでは、1つの情報に複数のレコードが紐付くスキーマ設計を利用し、JOINを用いて取り出す手法をよく使いますよね。
しかしelasticsearchにはJOINに相当する機能は無く、近しい物として次の手段があります。

nested

メリット - クエリのパフォーマンスが高い
デメリット - 更新が多いときのオーバーヘッドが大きい
用途 - 名前の通り入れ子なデータを扱いたいとき、(rails でいう has_many では使わないと思う)

parent/child

メリット - nested と違って更新時に問題を抱えてない
デメリット - クエリのパフォーマンスが落ちる、メモリを多く要する
用途 - 名前の通り親子関係のもの、RDB 的な relation はこちらに近い

引用元: http://engineer.wantedly.com/2014/02/25/elasticsearch-at-wantedly-1.html

parent/childはドキュメント登録時に1つまでの親子関係を予めセットする必要があるため、柔軟性がない事も使いづらいと感じました。
そこでNested Typeと呼ばれる入れ子構造(ネスト構造)の出番です。例えば次のような検索を行う場合にもこのネスト構造を用いると便利です。

  • ある会員に複数紐付くデータがある
  • そのいずれか1つがある値である

そう、今回の新機能の目玉はこのネスト構造化対応です。

使い方

次の結果となる設定の作り方を、サンプルデータと共に解説します。 これはある会員に紐付く、プログラミング言語のスキル情報です。

MySQL側のテーブル設計

f:id:yoshi-ken:20140414092459p:plain

f:id:yoshi-ken:20140414092810p:plain

f:id:yoshi-ken:20140408162655p:plain

本機能を用いてelasticsearchへ登録したドキュメント

$ curl -XGET http://localhost:9200/sample/member_skill/1?pretty
{
  "_index" : "sample",
  "_type" : "member_skill",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "member_id" : 1,
    "member_name" : "ユーザA",
    "skills" : [
      {
        "skill_name" : "PHP",
        "skill_url" : "http://php.net/"
      },
      {
        "skill_name" : "Ruby",
        "skill_url" : "https://www.ruby-lang.org/"
      }
    ]
  }
}
ネスト構造を生成する仕組み

MySQLでは1:N対応となるネスト構造は表現できません。
そこで、クエリの中にプレースホルダを埋め込みそれを都度展開させる処理を行います。
query設定の実行時の結果のfetch時にプレースホルダがあれば、別途プログラムの中でSELECTクエリを発行して結果をマージする処理を行うというものです。

-- prepared_query設定に記述する一時テーブル作成クエリ
CREATE TEMPORARY TABLE tmp_member_skill
  SELECT
    members.id AS member_id,
    skills.name AS skill_name,
    skills.url AS skill_url
  FROM
    members
    LEFT JOIN member_skill_relation ON members.id = member_id
    LEFT JOIN skills ON skills.id = skill_id;

-- query設定に記述する、elasticsearchへ登録するドキュメントを生成するクエリ
-- クエリの中に、ネスト構造として納めたいクエリを記述すると処理時に展開します
SELECT
  members.id AS member_id,
  members.name AS member_name,
  "SELECT skill_name, skill_url FROM tmp_member_skill WHERE member_id = ${member_id}" AS skills
FROM
  members
;

仕組みとしては、まずquery設定のクエリを実行し、次の結果を得ます。

{
  "member_id" : 1,
  "member_name" : "ユーザA",
  "skills" : "SELECT skill_name, skill_url FROM tmp_member_skill WHERE member_id = ${member_id}",
}

このskillsキーに、プレースホルダ付きのクエリがありますので、続けて${カラム名}の展開をします。
クエリ実行結果にあるmember_idの値である1プレースホルダに代入し、SQLクエリを実行します。
SELECT skill_name, skill_url FROM tmp_member_skill WHERE member_id = 1

ここではtmp_member_skillという事前にCREATE TEMPORARY TABLE構文で作成した一時テーブルを利用しています。使わずに都度JOINして結果を得るとパフォーマンスが劣化するため、事前に計算結果をメモリに蓄える手法を利用しています。

そして、実行結果をskillsの値として代入することで、次のネスト構造を得られました。
最後にこのドキュメントをelasticsearchへ登録すれば完了です。

{
  "member_id" : 1,
  "member_name" : "ユーザA",
  "skills" : [
    {
      "skill_name" : "PHP",
      "skill_url" : "http://php.net/"
    },
    {
      "skill_name" : "Ruby",
      "skill_url" : "https://www.ruby-lang.org/"
    }
  ]
}

elasticsearch-JDBC-river との違い

実はJDBCドライバを用いて、elasticsearchへ各種制約はありながらもデータ同期が出来るRiverプラグインは存在します。
しかし、このようなネスト構造のサポートは行われておりません。

まとめ

このプラグインを用いることで、既存のMySQL環境はそのままに、手軽にelasticsearchを用いた高機能な全文検索を試せる環境が作れます。
実際に弊社内で運用しており、特にトラブルなく稼働しております。今後はドキュメントの拡充を進めていこうと考えております。

この記事をご覧頂いた皆様も是非、まずは開発環境でのトライアルから初めて頂けると幸いです。
不明点等ございましたら、@yoshi_kenまでご連絡ください。