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

2009-05-19
2011-01-08
かなり古い記事です。現在も有効な内容であるかどうか分かりませんのでご注意ください。

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 }

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

Profile

フルスタック気味のフリーランスプログラマー。

どちらかと言うと得意はインフラ構築とサーバーサイドプログラミングですが、フロントエンドもぼちぼちやっています。

最近の興味範囲はWordPress、AWS、サーバーレス、UIデザイン。

愛車はセロー。カメラはペンタックス。旅好きです。横浜在住。