2015/07/28

Recent entries from same category

  1. Ruby の Array#<< は Array#push よりも速いか
  2. Ruby の a = a + 1 はなぜ undefined method '+' for nil:NilClass なのか
  3. Crystal と CRuby でHTTPサーバのベンチマーク
  4. pure mruby な JSON パーサ書いた。
  5. bundle exec がウザい

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_size5do |video|
  video.delete! unless video.existing?
end
Posted at by