こんばんわ。人に裏切られるのと同じくらいの仕打ちを受けて、最近意気消沈しているプレイライフエンジニアの合原です。
今回は弊社でのembulkの活用事例について、まとめます。
embulk活用前
弊社では、特集記事や遊びのプラン記事では、グルメやホテルの紹介もあり、漏れなく、アフィリエイト広告(以降アフィリエイト)を掲載しています。
例
https://play-life.jp/articles/1550
https://play-life.jp/articles/1510
こういった感じで。
また、日本全国には、グルメ・ホテルなど、数えきれないほどの店舗数があり、自ずと、各種アフィリエイト用の店舗データも大量なものとなります。
にも関わらず、embulkの活用前までは、rubyで何万ー何十万と言うデータをinsert
, update
と。。。やっている始末でした。
こんな時こそのembulk
大量のデータ転送ツールで有名なembulkにはフィルタリング、パーサー等のプラグインも充実していて、データのI/Oが柔軟にできるようになります。
従来のやり方は何が問題だったのか?
- 時間がかかる
- 何よりもテストがない
データある分だけループしつつ、 insert``update
とやっているので、これは多くを語らずとも...なかなかに辛い状況が想像しやすいかと思います。
また、データによっては、クライアントのAPIを経由して取得するデータもあるが、その場合のケアが全くないと言う状態もあり。
まずは設計
何はともあれまずは設計です。
どう攻めるか。
まず、使用経験もあったことから、時間のコストについては、真っ先にembulkを採用しようとなりました。
あとは、embulkをどう実行するか。
端的に言えばembulkに任せたいのは、
- データ転送(投入)
のみ
投入するデータの取得自体は、APIを叩けば取得できる。つまり、ここをスクリプト(ruby)で、かつ、テストもする形すれば...
と言うことで、手順を改めて整理すると、
- スクリプト(ruby)で投入データの取得
- embulk で1をDBへ投入
と、方針を決定。
(少し寄り道)データ転送は本来どこでやるべき?
本来であれば、サービス環境とデータのETL(Extract(抽出) Transform(変換) Load(格納)の環境が別れて存在しているのが理想でした。そうあれば、ETL環境で外部からの
- スクリプト(ruby)で投入データの取得
- embulk で1を(サービス環境側の)DBへ投入
ができたので。
が、弊社もまだまだスタートアップということで贅沢は言ってられないため、この点は一旦目を瞑りました。
早速embulkをRails環境下で使う
ここからは普段通りのRails環境下での開発です。
手順としては、
- データの取得を行う(model)を用意する
- 上記のテストを書く
- データの投入を行うembulkの設定ファイルを用意する
- 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の活用方法など、もっといい方法があれば是非ともご連絡お願いします。それでは、また次記事にて。。。,