ActiveRecord::Base#find_in_batchesを使ってみた
Railsでバッチ処理したい時に既存の
ActiveRecord::Base#findを使いたいけど、処理件数が多くなりすぎるとメモリ食い過ぎて大変。
なんてことになりそうな場合は、今まではActiveRecord::Base#connectionで直接SQL文を実行してました。
ActiveRecord::Base#connection
ActiveRecord::Base.connection.execute("select id, name from users where status=1")
ってな感じですね。
executeを使うことでレコード単位でDBに問い合わせしてくれるので、メモリへの負荷が軽減します。
これで幸せって思ってたのですが、executeを使うとMysql::Resultで返却されます。
Arrayの中にArrayになっているので取り出すときに面倒なのです。
しかも、中身はすべてStringです。
[["1","aa"],["2","bb"]]
みたいな感じで返ってきます。
なので、
param = {:id => 0, :name => 1} result.each do |row| p "#{row[param[:id]]} #{row[param[:name]]}" end
という感じで取り出さないといけないのです。。
しかも、その後処理するときに面倒ですね。。
(見た目はそれほど面倒じゃなさそうですが、、)
ActiveRecord::Base#find_in_batches
そこで、find_in_batchesを使うとRailsのコードを利用しつつメモリへの展開するオブジェクトも削減できます。
UPDATE_SIZE=100 User.find_in_batches(:conditions => ["status=1"], :batch_size => UPDATE_SIZE) do |users| users.each do |user| p "#{user.id} #{user.name}" #条件に一致するデータのフラグをなどの処理をいれる。 end end
こうすることで
UPDATE_SIZE分だけのレコードを取得してくれます。
中身は何をやってくれているのかというと、
SELECT * FROM `users` WHERE (users.id > 282199) AND (status = 1) ORDER BY users.id ASC LIMIT 100
id順にならべて取得できたidを覚えていて次の実行時にそれ以上のidを取得するということをやってます。
これならlimitでページ送りのような処理になっていないので、パフォーマンスも速いです。
なので、なかなか賢い感じで、Railsっぽく書けるのでいいですね。