Railsのバッチ処理のコツ
はじめに
Railsでギョーミーな仕事を行う上で欠かせないのがバッチ処理です。 日々上位システムから送られてくる膨大なデータを迅速に取り込み、集計処理を行いDBに格納する。上位システムは何層も構成されており、我々が集計処理に使える時間はエンドユーザーが出社してくるまでの数時間... みたいなシチュエーション無いですか?
今回はバッチ処理を行うコツについて書いてみようと思います。
想定される処理
- CSVファイルの取り込み
- 集計処理
- 集計結果をDBに格納
普通にrake taskを書いて処理できてれば今回の記事は必要ありません。そっとブラウザを閉じて下さい。そうでない場合、多くの人が直面する問題は次のようなものが考えられます。
- IDのオーバーフロー
- メモリが食いつぶされてバッチ処理が停止
それでは順番に説明します。
IDのオーバーフロー
大量のデータを日々のバッチで取り込んでいる場合、IDがオーバーフローしてしまう可能性があります。IDの最大値はINT型で2147483647もあるので通常利用している範囲ではオーバーフローすることは考えなくても(※1)大丈夫です。しかし、データの取り込みは追記だけとは限りません。何かの条件でDELETE & INSERTする場合、ID列の寿命は思っていたより早く尽きてしまうでしょう。
※1 毎日10万件のデータを取り込んだとして58年持つ計算。
そんな場合は思い切ってID列を削除してしまいましょう。
# migrationの定義 create_table :sales, :id => false do |t| t.references :customer t.date :sales_date t.decimal :total_sales end add_index :sales, [:customer_id, :sales_date], unique: true
IDを削除することの弊害
IDを削除することで、IDのオーバーフローに怯える必要は無くなりました。ActiveRecord::Baseのwhereメソッドによる検索も普通に使えます。findメソッドによるID検索が使えないだけだと思っていると、大きな落とし穴が待ち構えています。それはUpdateができないことです。
この問題を解決するためにはcomposite_primary_keys
というgemを利用しましょう。
Modelに次のようにキーを定義することでUpdateが可能になります。
class Sales < ActiveRecord::Base self.primary_keys = :customer_id, :sales_date end
メモリが食いつぶされてバッチ処理が停止
初めは快調に処理が進んでいたのに、だんだん遅くなって停止してしまう。こんな経験はないでしょうか?
▲こうなるともうお手上げ
メモリの消費をチェック
Linuxであればfreeコマンドでメモリの消費をチェックしてみましょう。
$ watch free -m total used free shared buffers cached Mem: 8000 6998 1001 0 340 2346 -/+ buffers/cache: 4311 3688 Swap: 10063 414 9649
バッチ実行中にbuffers/cache
のfreeの値がどんどん減っていくようであれば、あなたのバッチ処理は何かがおかしいです。
大きなselectを行わない
Sale.all.each do |sale| # 略 end
全てのデータを一気に取得して処理するのではなく、可能であれば小さい単位に分割しましょう。
Shop.all.each do |shop| Sale.where(shop_id: shop.id).each do |sale| #略 end end
適切な粒度でバルクインサートを行う
このような処理はメモリを食いつぶす原因となります。
Sale.all.each do |sale| OtherSale.create!(total_sales: sale.total_sales, ...) end
ActiveRecord.importを利用してバルクインサートを行いましょう。その際に、全てのレコードをインサートするのではなく適切な粒度になるよう気をつけましょう。
Sale.find_in_batches do |sales| # 1,000件ずつ取り出す list = [] sales.each do |sale| list << OtherSale.new(total_sales: sale.total_sales, ...) end OtherSale.import list end
最後に
バッチ実行中にだんだん遅くなっていく現象は、テスト段階では気づきにくいものです。本番運用が始まる前にかならずダミーデータを作成して検証を行うようにしましょう。