2
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-04

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

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

embulk活用前

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


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

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

こんな時こそのembulk

embulkです。
スクリーンショット 2020-03-04 23.28.16.png

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

スクリーンショット 2020-03-04 23.32.22.png

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

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

データある分だけループしつつ、 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の活用方法など、もっといい方法があれば是非ともご連絡お願いします。それでは、また次記事にて。。。,

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2