Ruby 製バッチ処理を省メモリ化した - 彼女からは、おいちゃんと呼ばれています
少し前に Ruby 製の バッチ処理 を省メモリ化したときの話をメモしておきます。 どのような バッチ処理 だったか 動画共有サイト にアップされた動画がオトナの事情によって削除されることがしばしばあ...
http://blog.inouetakuya.info/entry/2015/07/26/204320
ActiveRecord の each は全件、find_each はデフォルトで1000件をインスタンス化するので、HTML をパースしたDOMツリーが1000件もメモリに乗ったらそりゃ OOM Killer で殺されても文句は言えないかもしれない。
ActiveRecord::Batches
The find_each method uses find_in_batches with a batch size of 1000 (or as specified by the :batch_size option).
http://api.rubyonrails.org/classes/ActiveRecord/Batches.html
試しにキャッシュと呼んでおられる @doc の中身を表示すると
  def doc
    p @doc
    # 動画配信元ページの HTML をキャッシュ
    @doc ||= Nokogiri::HTML(open(source_url))
  end
#<Nokogiri::HTML::Document:0x149ddbc name="document" children...
#<Nokogiri::HTML::Document:0x11abd6c name="document" children...
#<Nokogiri::HTML::Document:0xdbcd8c name="document" children=...
毎回異なるメモリのアドレス値が表示されており全くキャッシュに意味が無い事が分かる。上記の通り find_each の仕様ならばきちんと1000個の DOM ツリーがメモリに乗っている事になる。
class Video < ActiveRecord::Base
  after_initialize do
    doc ||= Nokogiri::HTML(open(source_url))
    @deleted = doc.at_css('.message').try(:text).try(:match, 'This video has been deleted')
    @removed = doc.title.match 'Removed'
  end
  def deleted?
    match_removed? || match_deleted?
  end
  def existing?
    !deleted?
  end
  private
  def match_deleted?
    @deleted
  end
  def match_removed?
    @removed
  end
end
こんな感じに使ったらすぐに doc を捨ててしまうか、batch_size の数を減らして一度にインスタンス化される数を制限する事で解決しそうな気がします(こちらは未検証)。
Video.find_each(batch_size: 5) do |video|
  video.delete! unless video.existing?
end
 
