ActiveRecord::Baseを継承したクラスのインスタンスがGCされないことがあった
irb(main):004:0> RUBY_VERSION
=> "2.4.4"
irb(main):006:0> Rails.version
=> "4.2.11.1"
特定のユーザ達になんらかの処理を行うという、よくあるバッチ処理でメモリリークが起きたということを書く。結論を書くとインスタンス変数をやめたらGCしてくれるようになった。
事件が起きたのは、OAuth2クライアントがAPIコールするだけのバッチ処理。当時発生していたクラスの構成は下記の通り。
class User < ActiveRecord::Base
has_one :the_service_account
def run
the_service_account.run
end
end
def TheServiceAccount < ActiveRecord::Base
belongs_to :user
def run
api_client.run
end
private def api_client
@api_client ||= ApiClient.new(user)
end
end
class ApiClient
def initialize(user)
self.user = user
@oauth2_token = OAuth2::AccessToken.new(
build_client,
user.the_service_account.access_token,
user.the_service_account.refresh_token,
user.the_service_account.expires_at,
)
end
def run
call_api
end
end
というように、外部サービスへのアクセスに使うトークンを管理する TheServiceAccount
、外部サービスのAPIを呼ぶための ApiClient
がある。
冒頭に書いたように、このUserモデルに対してfind_eachすると、メモリ使用量が線形に増えていった。
User.find_each do |user|
user.run
end
find_eachしているのにメモリが増え続けているということは、GCされるべきオブジェクトが残っているのだろうと考え、下記のようにインスタンス変数からローカル変数に変更したらGCされるようになった。
class ApiClient
def initialize(user)
self.user = user
end
def run
oauth2_token = OAuth2::AccessToken.new(
build_client,
user.the_service_account.access_token,
user.the_service_account.refresh_token,
user.the_service_account.expires_at,
)
call_api(oauth2_token)
end
end
find_each
を使うとメモリリークを起こす!手動でGC.start
をするべしって記事をたまに見るんだけど、モデルの実装に原因があることもあるのだ。
-
category:
- ruby