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

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


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

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

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

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

# こんな時こそのembulk
embulkです。
[<img width="151" alt="スクリーンショット 2020-03-04 23.28.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/578823/7c701c76-acff-6b9a-730a-a513a0d8109b.png">](https://www.embulk.org/docs/)

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

[<img width="639" alt="スクリーンショット 2020-03-04 23.32.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/578823/82cc2505-883d-2acc-30d8-69287ffbb0ae.png">](https://www.embulk.org/docs/)


# 従来のやり方は何が問題だったのか？
* 時間がかかる
* 何よりもテストがない

データある分だけループしつつ、　`insert``update`とやっているので、これは多くを語らずとも...なかなかに辛い状況が想像しやすいかと思います。
また、データによっては、クライアントの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）を用意する
1. 上記のテストを書く
1. データの投入を行うembulkの設定ファイルを用意する
1. embulkを実行するrake task を用意する

rakeタスクでデータ取得を行うのでなく、それをするmodelを使うこととしているのは、[そのmodelのテストがしやすくなるからです。また、こうすることで、rakeタスクでは決められた手順でmodelを呼び出すだけ済みます。](https://qiita.com/aeroastro/items/c97bd26ce8b8818b6bed#%E5%A4%A7%E5%89%8D%E6%8F%90-rake%E3%82%BF%E3%82%B9%E3%82%AF%E3%81%AF%E8%96%84%E3%81%84%E3%82%B0%E3%83%AB%E3%83%BC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E3%81%99%E3%82%8B)


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

```sh
$ ./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

```yaml

{% 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`](https://qiita.com/hiroysato/items/861e3689eef430f5e723)それぞれの設定ファイルは、メンテもしやすくするためにも上記で作ったディレクトリの方に、下記のように用意します。（下記）
ポイントは各種`liquidファイル`を分け、`include`する形にすることです。

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

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

# 最終的な利用方法
あとはrakeタスクを用意して、定期実行（cronなり）設定すれば使えるようになります。

```rb
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の活用方法など、もっといい方法があれば是非ともご連絡お願いします。それでは、また次記事にて。。。,