この記事はアピリッツの技術ブログ「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で削除かなぁ