UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

ElasticsearchをMySQLと同期しつつ手軽に無停止アップデートする

nazoです。

Elasticsearchを運用する際に、マスタデータはMySQLで持ちたいという場合にどうやって同期をするかというのが問題になります。また、Elasticsearchはバージョンの互換性が厳しく、別バージョンをクラスタに混ぜることは基本的にできず、さらに辞書の更新などを行う場合はインデックスを全て更新しなくてはいけないなどの運用上の課題があります。

今回は社内向けに使っているElasticsearchを、これらの問題を解決しつつどのように運用するかを考えてみましたので、紹介したいと思います。

簡単に

MySQLとElasitcsearchの同期は go-mysql-elasticsearch を使います。 無停止のためのデータコピーは elasticsearch-dump を使います。

MySQLとElasitcsearchの同期

go-mysql-elasticsearchは、binlogを使ってMySQLとElasticsearchを同期するツールです。binlogを使うので、MySQL側で binlog_format=row にしておく必要があります。

go-mysql-elasticsearchは、起動しているだけで勝手にbinlogから設定ファイルのルールに基いてデータをElasticsearchに流してくれます。初回はmysqldumpで全てのデータを移行させて、その後はbinlogからの取得になります。また、 data_dir で設定した場所にどこまで同期したかという情報を書き込んでくれるので、中断させても安全に再開することができます。

私はdockerで運用しているので、data_dirをホストボリュームにマウントさせておいて、簡単に停止・再開ができるようにしています。

Webアプリケーションフレームワークで、O/Rマッパーと連動してElasticsearchに書き込んで同期してくれるようなライブラリ(elasticsearch-railsなど)もありますが、この方法だと同期部にアプリケーション側の都合を考えなくてよいのがメリットです。elasticsearch-railsのようなものがない環境でも簡単に同期することができます。

無停止でのElasticsearchアップデート

Elasticsearchはバージョン違いでクラスタを組むことができず、またインデックスの更新も基本的に作り直しを要求されます。インデックスの更新はエイリアスを使うような方法もありますが、どうせやるならインスタンス丸ごと新しくしてしまって、そちらにコピーしてしまえば面倒なことが少ないです。これらを無停止でできれば完璧です。

前提として、Elasticsearchへのデータの追加は、go-mysql-elasticsearch以外では行わないというルールにします。というよりは「Elasticsearchへのデータの送り込みを一時停止できて、再開すると一時停止したところから同じように送りこむことができる状態」である必要があります。停止中のデータがロストしてしまうような環境ではこの方法を使うことはできません。

Elasticsearchへのデータ追加がgo-mysql-elasticsearchからしか行われないとすると、go-mysql-elasticsearchさえ止めてしまえばElasticsearch内のデータは常に一定になるので、安心しでデータコピーができます。ここでelasticsearch-dumpが登場します。

elasticsearch-dumpはDocker imageでも配布されているので、適当なDockerが動いているインスタンス上で以下のコマンドを実行するだけです。このケースではマッピングは事前に作成しておく必要がありますが、typeを変えればマッピングもコピーすることができます。逆に言えば、マッピングがコピーされないということは、ついでにマッピングの変更をしたい場合にも簡単に対応することができます。

docker run --rm -ti taskrabbit/elasticsearch-dump \
  --input=http://production.es.com:9200/my_index \
  --output=http://staging.es.com:9200/my_index \
  --type=data

あとはElasticsearchを使用しているアプリケーションの接続先を書き換えて(私は内部DNS書き換えで行っています)新クラスタに向くようにして、go-mysql-elasticsearchでの同期を再開し、以前のクラスタを削除してしまえば、無停止メンテナンスの終了です。

terraformと組み合わせて、簡単にインスタンスの上げ下げができるようにしておくと、コピー以外は数分の作業で済むようになります。ちなみにコピーはデータ量によっては一日作業になります。想定されるデータ量と、「何時間同期が止まってても許されるか」は事前に検討しておきましょう。

難点

両方ともオフィシャルのプロダクトではないため、いつElasticsearchのバージョン追従が止まるかはわかりません。

elasticsearch-dumpはなんとなく自作できそうな雰囲気がありますが、go-mysql-elasticsearch相当のものを自作するのは相当難易度が高いと思われます。とりあえず1000万レコード以上ある環境で一ヶ月以上運用していますが、今のところ特に問題はなさそうです。

注意点

O/Rマッパーと連動するようなElasticsearchライブラリ(elasticsearch-railsなど)を使っている場合、そちらから書き込みアクセスが入ってしまうと同期が多少おかしくなります。ただし基本的に同じ内容が書き込まれるはずなので、書き込みタイミングが重複したりしなければそこまで変なことにはならないと思います。信頼性を気にするようであればこのあたりも考慮するといいと思います。

まとめ

elasticsearch-analysis-kuromoji-neologd プラグイン使うのはいいけど辞書どうやって更新するといいのかな、というところから始まった試行錯誤ですが、なかなか低コストでいい感じに着地したのではないかと思っています。

ユーザー向けにヘビーな使い方をするのであれば別のアプローチを取ったほうがいいかもしれませんが、このような方法もあるということで参考にしていただければと思います。