Rails環境下でembulkを使って快適に数十万件のデータを取り込む


こんばんわ。人に裏切られるのと同じくらいの仕打ちを受けて、最近意気消沈しているプレイライフエンジニアの合原です。

今回は弊社でのembulkの活用事例について、まとめます。

embulk活用前

弊社では、特集記事や遊びのプラン記事では、グルメやホテルの紹介もあり、漏れなく、アフィリエイト広告(以降アフィリエイト)を掲載しています。


https://play-life.jp/articles/1550
https://play-life.jp/articles/1510

こういった感じで。
また、日本全国には、グルメ・ホテルなど、数えきれないほどの店舗数があり、自ずと、各種アフィリエイト用の店舗データも大量なものとなります。
にも関わらず、embulkの活用前までは、rubyで何万ー何十万と言うデータをinsert, updateと。。。やっている始末でした。

こんな時こそのembulk

embulkです。

大量のデータ転送ツールで有名なembulkにはフィルタリング、パーサー等のプラグインも充実していて、データのI/Oが柔軟にできるようになります。

従来のやり方は何が問題だったのか?

  • 時間がかかる
  • 何よりもテストがない

データある分だけループしつつ、 insertupdateとやっているので、これは多くを語らずとも...なかなかに辛い状況が想像しやすいかと思います。
また、データによっては、クライアントのAPIを経由して取得するデータもあるが、その場合のケアが全くないと言う状態もあり。

まずは設計

何はともあれまずは設計です。
どう攻めるか。

まず、使用経験もあったことから、時間のコストについては、真っ先にembulkを採用しようとなりました。
あとは、embulkをどう実行するか。

端的に言えばembulkに任せたいのは、
* データ転送(投入)

のみ
投入するデータの取得自体は、APIを叩けば取得できる。つまり、ここをスクリプト(ruby)で、かつ、テストもする形すれば...
と言うことで、手順を改めて整理すると、

  1. スクリプト(ruby)で投入データの取得
  2. embulk で1をDBへ投入

と、方針を決定。

(少し寄り道)データ転送は本来どこでやるべき?

本来であれば、サービス環境とデータのETL(Extract(抽出) Transform(変換) Load(格納)の環境が別れて存在しているのが理想でした。そうあれば、ETL環境で外部からの

  1. スクリプト(ruby)で投入データの取得
  2. embulk で1を(サービス環境側の)DBへ投入

ができたので。
が、弊社もまだまだスタートアップということで贅沢は言ってられないため、この点は一旦目を瞑りました。

早速embulkをRails環境下で使う

ここからは普段通りのRails環境下での開発です。
手順としては、

  1. データの取得を行う(model)を用意する
  2. 上記のテストを書く
  3. データの投入を行うembulkの設定ファイルを用意する
  4. embulkを実行するrake task を用意する

rakeタスクでデータ取得を行うのでなく、それをするmodelを使うこととしているのは、そのmodelのテストがしやすくなるからです。また、こうすることで、rakeタスクでは決められた手順でmodelを呼び出すだけ済みます。

Rails環境下で使うembulkの構成

RailsApps直下にembulkディレクトリを用意して、下記のような構成で配置します

$ ./rails_apps(master)

embulk
├── config.yml.sample
├── hogehoge_restaurant_plans
└── hogehoge_restaurant_plans_import.yml.liquid

対象となるデータインポート内容に応じて、誰が見てもわかりやすいような名前でディレクトリ(hogehoge_restaurant_plans)と、そのembulkの設定ファイル(hogehoge_restaurant_plans_import.yml.liquid)を用意します。

例) hogehoge_restaurant_plans_import.yml.liquid


{% include 'hogehoge_restaurant_plans/in' %}
{% include 'hogehoge_restaurant_plans/filters' %}                                                                                                                                     
{% include 'hogehoge_restaurant_plans/out',
   db_host: env.DB_HOST,
   db_name: env.DB_NAME,
   db_user: env.DB_USER,
   db_password: env.DB_PASSWORD %}

ご覧のようにin, filter, outそれぞれの設定ファイルは、メンテもしやすくするためにも上記で作ったディレクトリの方に、下記のように用意します。(下記)
ポイントは各種liquidファイルを分け、includeする形にすることです。

hogehoge_restaurant_plans
├── Gemfile
├── Gemfile.lock
├── _filters.yml.liquid
├── _in.yml.liquid
├── _out.yml.liquid
├── embulk
├── src
└── vendor

※詳細については本記事では割愛しますが、別記事にて embulkの詳細な使い方については、まとめます。

最終的な利用方法

あとはrakeタスクを用意して、定期実行(cronなり)設定すれば使えるようになります。

task shops_download: :setup_logger do
    logger.info 'Start download hogehoge_restaurant_shops: :setup_logger'
    Dir.chdir('./embulk/hogehoge_restaurant_shops/src') do
      fetcher = Affiliates::HogeRestaurants::PlanFetcher.new(local: Rails.env.development?)
      fetcher.execute
    end
    logger.info 'Finished download hogehoge_restaurant_shops: :setup_logger'
  end
  desc 'Bundle install hogehoge_restaurant shops'
  task bundle_install: :setup_logger do
    logger.info 'Bundle hogehoge_restaurant_shops: :setup_logger'
    Dir.chdir('./embulk/hogehoge_restaurant_shops') do
      sh %(embulk bundle --path=vendor/bundle)
    end
    logger.info 'Finished bundle hogehoge_restaurant_shops: :setup_logger'
  end
  :
  desc 'Import hogehoge_restaurant shops'
  task import_shops: :setup_logger do
    logger.info 'Import_hogehoge_restaurant_shops: :setup_logger'
    Dir.chdir('./embulk') do
      sh %(export DB_HOST=localhost;
            export DB_NAME="#{db_config['database']}";
            export DB_USER="#{db_config['username']}";
            export DB_PASSWORD=#{db_config['password']} ;
            embulk run hogehoge_restaurant_shops_import.yml.liquid -b hogehoge_restaurant_shops/
      )
      logger.info 'Finished import_hogehoge_restaurant_shops: :setup_logger'
    end
  end
end

:

def db_config
  file_path = Rails.root.join('config', 'database.yml')
  YAML.load_file(file_path)[Rails.env]
end

まとめ

rubyで長時間におよぶループをして、時間をかけていた点が解決しただけでなく、embulkを比較的にスマートにRails環境下で使える形にできたかと思います。
今回紹介した例はあくまで一例ですが、以降は、同様の形で、弊社ではさまざまな用途でembulkを活用していっています。
その辺りの話はまた今度。

embulkの活用方法など、もっといい方法があれば是非ともご連絡お願いします。それでは、また次記事にて。。。,