LoginSignup
1
0

More than 3 years have passed since last update.

ruby で書くバッチ処理のパフォーマンスチューニング

Last updated at Posted at 2019-12-13

概要

数万件のデータパッチをrakeタスクで行い、当初12時間かかっていた処理が、チューニング後2時間に短縮し、パフォーマンスの重大さを身をもって感じたので備忘録的にまとめたいと思います🏃‍♀️🏃‍♀️🏃‍♀️

前提

  • バッチサーバースペック メモリ20GB CPU 4コア
  • rails5系

destroy_allは遅い🚶‍♀️

12時間かかってしまった原因が主にここでした。
当初データ一件ずつに対して複数の関連データをdestroy_allで行なっていたものを
delete_allにしたところ7時間もの短縮となりました🏃‍♀️
関連付けのコールバックも呼ばれるdestroy_allは大量データを処理する時は要注意です

activerecord-importを利用したBULK INSERT

大量データをinsertする時はBULK INSERTするかと思います。
カラム名を配列で指定し、同じ順序でvalueを格納した配列を含む配列をimportするのが最速らしい🏃‍♀️
https://github.com/zdennis/activerecord-import#columns-and-arrays
before

users = []
target_users.each do |u|
    users <<  Result.new(user_id: u.id,  email: u.email)
end
ForX::UserAppMigrationResult.import users

after

target_users =  [[1, 'test@test.com'], [2, 'test@test.com']]
columns = [:user_id,  :email]
Result.import columns, target_users

脱N + 1

大量のSQLを発行してしまうやってしまいがちな問題。
今回改めて違いを理解したのでざっくりな理解をメモしておく。
eager_load  left_outer_joinしてassociationをキャッシュする
joins  inner_joinsのみ。associationをキャッシュしない
preload  指定したassociationを複数のクエリに分けて引いてキャッシュする。
include eager_loadとpreloadをいい感じに使い分けている

※(余談)eager_loadで異なるDBでJOINして詰まった時のメモ。
異なるDBのモデル間でeager_loadを利用したらそんなテーブルないよと怒られました。

User.eager_load(:memos)
#=> Mysql2::Error: Table 'memos' doesn't exist

mysqlではDB名.テーブル名とDB名を明示してあげれば異なるDB感でもjoinできるので、
eager_loadjoinsを利用するときもモデルにDB名を明示すればOKでした。

class Memo
  self.table_name = "xxx.memos"  # xxxはdb名
  belongs_to :user
end

マルチプロセス化

rubyで簡単に並列処理を実装できるparallelを利用しました。

おわりに

大量データを処理するバッチを書くのは初めてだったので、パフォーマンスを意識する良いきっかけとなりました!

参照

https://qiita.com/begaborn/items/95e8d6839dad25584bc8
https://qiita.com/nikadon/items/9e6431f5a7b2b8798113#destroy_all-vs-delete_all

1
0
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
1
0