ホーム DoRuby Tempfileを使ってリソースを有効活用

Tempfileを使ってリソースを有効活用

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

とあるテーブルのいくつかのカラムをTSVファイルに書き出し、
ダウンロードするという処理が重かったので
Tempfile等を使って少し軽くした。

※Tempfileの説明はリファレンスを参照。
http://www.ruby-lang.org/ja/man/html/tempfile.html

Before


# 細かい処理は省略。
# あくまで例なので、connection.executeでやればいいじゃんというのは無しで。。。
# Item は Model Class。

def download
 colnames = Item.column_names

 buf = String.new
 buf << colnames.join("¥t")
 buf << "¥n"

 Item.all.each do |item|
   buf << colnames.map do |col| item[col] end.join("¥t")
   buf << "¥n"
 end

 send_data(buf, :filename => "items.tsv")
end

これだと、Itemのサイズが数百万レコード等、巨大な場合、
Item.all と buf 辺りでメモリを大量に消費しそう。

Item.allだと一度に全てのItemのインスタンスを保持するが、
別に上記処理の場合、小分けしても問題ないね。
# yieldとか使って1行ずつ読み込んだ方がさらにいいかも?

そもそもファイルを生成し、最終的に出力するのだから、
一時的な情報(buf)は、メモリではなくファイルに書き出し、
そのファイルをそのまま渡した方がリソースを有効活用できる。

という訳で↓

After


DEV_NUM = 1000

def download
 colnames = Item.column_names

 temp = Tempfile.new('items')
 temp.puts colnames.join("¥t")

 for i in 0..Item.count/DEV_NUM
   Item.all(:limit => DEV_NUM, :offset => i*DEV_NUM).each do |item|
     temp.puts colnames.map do |col| item[col] end.join("¥t")
   end
 end

 temp.close
 send_file(temp.path, :filename => "items.tsv")
end

処理速度は余り変わらないものの、
取りあえず、メモリ馬鹿食いでプロセスが落ちる事は無くなった。

よかったよかった :)

P.S.
残されたファイルの消去は。。。cronで削除かなぁ
記事を共有

最近人気な記事