BETA

【Ruby】 ObjectSpace.each_objectとは

投稿日:2020-02-09
最終更新:2020-02-09

こんにちは、しきゆらです。
気が付くと、年が明けて2月です。

今回は、先輩に教えてもらった謎のコードを解読していきます。
謎のコードとは下記のもの。

ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)  
GC.start  

いきさつ

AWS上にあるインスタンスでRailsを動かしていました。
その中で、DBのデータにミスがあったので、1.5万件ほどのデータに紐づく諸々の差し替え作業を行っていました。
このときにおこった問題としては「メモリ使い過ぎで怒られる」という状況でした。
コードを書き換えたりループを工夫したりしても解決せず困っていたとこで先輩が下記のようなコードを教えてくれました。

 ModelClass.find_in_batches do |objects|  
   # 差し替える処理  

   ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset) # <= ???  
   GC.start                                                      # <= ???  
 end  

結果としては、処理中に止まることもなく走りきることができた。

その当時は、すげぇなって思いつつこのコードを後で調べておこうと思ってメモしておいたが、放置されていたもの。
改めて時間ができたので、調べてみようと思って書いている。

ObjectSpaceとは

そもそも、見慣れないクラス名です。
調べてみると、Rubyさんの組み込みモジュールでした。
リファレンスでは

全てのオブジェクトを操作するためのモジュール
module ObjectSpace (Ruby 2.7.0 リファレンスマニュアル)

とのこと。

わかるようなわからないような・・・という感じですが、定義されているメソッド類を見てみると
Rubyで定義したオブジェクトたちに対してあれこれしたり、プロファイルを取るときなどに使われるもののようでした。

ObjectSpace#each_object

その中に、ありましたeach_objectメソッド。
ObjectSpaceが何かわかれば、メソッド名で何をするものなのか大方予想が付きますね。

調べてみると、

指定されたクラスとObject#kind_of?の関係にあるすべてのオブジェクトに対して繰り返す
ObjectSpace.#each_object (Ruby 2.7.0 リファレンスマニュアル)

とのこと。
上記のコードではActiveRecord::Relationクラスのオブジェクトすべてに対する処理を行うということですね。

こいつは、ブロックを渡すとそのブロックを実行し、繰り返し回数を返すようです。
そして、ブロックが渡らない場合はEnumeratorオブジェクトを返すとのこと。
上記のコードでは、eachメソッドをつなげているので後者ですね。

なお、引数を与えなければすべてのオブジェクトに対して繰り返すようです。
また、即値となるFixnumなどは対象外のようです。

つまり、上記コードはActiveRecord::Relationクラスのすべてのオブジェクト一つ一つに対してresetメソッドを呼び出していることになりますね。

ActiveRecord::Retation#reset

では、ActiveRecord::Retation#resetは何者でしょうか。
定義を確認してみると、内部で保持しているデータをすべて破棄しているようです。

つまり、上記コードの1行目が実行されたら、ActiveRecord::Retationのオブジェクトはすべて空のデータとなるようです。
1行目の内容は把握できました。
2行目を追っていきます。

GC

見たままGCでしょうね。
一応調べてみると、RubyのGCを制御するためのモジュールでした。
正確な情報は持っていませんが、イメージとしては使用していないデータを解放する仕組みという認識です。
メモリ上にあるいらないものを削除してきれいにしてくれる裏方さん。

GC#start

もう、見たままでしょう。
GC.start (Ruby 2.7.0 リファレンスマニュアル)
GCの処理を始めるためのメソッドです。

別メソッドでGCを禁止するようなこともできるようですが、このメソッドで実行した場合はGCを始めるようです。

すべてを見たうえで

 ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)  
 GC.start  

たった2行のコードですが、知らないことが満載でした。
処理を簡単にまとめると、この処理の前までに作成されたすべてのActiveRecord::Relationオブジェクトを空にしてGCに削除してもらう感じでしょうか。

1.5万件のデータを処理していると、たくさんの不要なオブジェクトがたまっていき途中で利用できるメモリサイズをオーバーしてしまうということで、それを防ぐために一定の処理を終えると不要になったオブジェクトを削除して次に進む、ということを行っているようですね。

find_eachfind_in_batchesなどである程度の粒度で処理することなどはわかっていましたが、それですら怒られるのでどうすればいいのかわからないところで、まさか自分でGCを動かして削除させることができるとは・・・。
まだまだ知らないことがいっぱいあるということですね。
まぁよく使うことではないとは思いますが、知っていると困ったときに役に立ちそうです。

まとめ

今回は、先輩が教えてくれた謎のコードを調べながら内容を理解してみました。
ActiveRecordなど、Githubで公開されているコードについては中身を読むことができるので、きちんと中身を知っておくことは大事だなと思いました。

今回は、ここまで。
おわり。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

頭は記憶リムーバブルデバイス

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう