LoginSignup
66
53

More than 5 years have passed since last update.

ActiveRecordがメモリを食って解放しない問題

Last updated at Posted at 2016-02-18

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

メモリを解放する処理は少なくても時間がかかるので、この処理を追加したら、パフォーマンスが落ちる可能性があります。メモリ量とパフォーマンスを考慮する必要があると思います。

66
53
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
66
53