Railsのconsoleにて大量のレコードを少ないメモリで処理する

RailsのconsoleでActiveRecordを使って大量のレコードを処理しようとする場合、とりあえずコントローラ内に書くのと同じように以下のように打ってみると思う。

Item.all.each {|item| item.update_price }

ところが経験ある人も多いと思うけれども、これをやるとレコード数が1000程度でも大量のメモリを食う。
自分の経験を言えば、開発マシンのMacbook Proがスワップを大量発生させてコマ送りのようなレスポンスになった。

それぞれのレコードに対応するオブジェクトがループ終了までガベージコレクションによって破棄されないのでこんな風になる模様。
処理が進むにつれだんだんメモリ消費量が増えていく。
メモリリークのようだけどメモリリークではないんですと。

で、この敵に立ち向かうためにしばらくの間は勤勉さを発揮してある方法を採った。
一度に取得するレコード数にlimitを指定して↑キーとエンターを連打するという頭が悪いけど力技で確実な方法。

Item.all(:limit => 100, :order => 'updated_at').each {|item| item.update_price }

しかしながらさすがにこれを数十回繰り返さないとダメとなった段に勤勉さが底をついた。
というわけでいろいろ試して、これを以下のように書いてみたらメモリ消費は少ないということに気がついた。

(Item.count).times { Item.first(:order => 'updated_at').update_price }

これだとループごとにガベージコレクションがちゃんと働いてくれるみたいだ。

この投稿へのコメント

  1. 通りすがり said on 2009年5月19日 at 1:44 PM

    will_paginateが使える環境なら、↓なんてどうでしょう?
    アプリ全体で使っていなくても、console の際だけwill_paginateを読んでやれば大丈夫だと思います

    Item.paginated_each( :per_page => 20) { |item| item.update_price }

  2. akahige said on 2009年5月20日 at 10:24 AM

    おー、orderとかいらないし、いいですね。
    ナイスな方法ありがとうございます!

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL