Railsコンソルで以下のように確認しましょう
[1] pry(main)> ObjectSpace.each_object(ActiveRecord::Base).count
=> 0
[2] pry(main)> User.limit(100)
User Load (1.5ms) SELECT `users`.* FROM `users` LIMIT 10
[4] pry(main)> ObjectSpace.each_object(ActiveRecord::Base).count
=> 100
[5] pry(main)> User.limit(100)
User Load (0.3ms) SELECT `users`.* FROM `users` LIMIT 10
[7] pry(main)> ObjectSpace.each_object(ActiveRecord::Base).count
=> 200
作ったActiveRecordのインスタンスがどんどんメモリ上に溜まると分かりました。
溜まったインスタンスが使わなくても、時間をかけて解放されないです。そのため、プロセスのメモリ使用量がどんどん増加てしまいます。多量なデータを処理する場合、メモリ足りない問題が出るおそれがありますね。
実にみんなさんがご存知ですが、この問題はunicornというサーバーでよくありました。時間をかけてunicornのプロセスのメモリ使用量が増加するという問題です。この問題を解決するために、unicorn killerというgemを使ってunicornプロセスを再起度して、メモリを解放する工夫をよくやっていると思います。
なぜRubyのGCはActiveRecordオブジェクトのメモリを自動的に解放しないか、まだ分からないですが、対策として以下のように幾つか方法があります。
1. 必要ない場合、includesを使用しない
includesを使ったら、パフォーマンスが上がるが、紐付けるテーブルのレコードもロードして、メモリを大量に使ってしまいます。パフォーマンスとメモリ量を検討する必要があります。
2. 使用しないレコードの属性をロードしないようにする
属性が多ければ多くほどActiveRecordオブジェクトが使うメモリ量が大きいです。
処理に必要な属性のみをselectしてください。
User.select(:id, :name).limit(100)
3. 手動でメモリ解放する
ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)
GC.start
メモリを解放する処理は少なくても時間がかかるので、この処理を追加したら、パフォーマンスが落ちる可能性があります。メモリ量とパフォーマンスを考慮する必要があると思います。