その他
    ホーム 技術発信 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で削除かなぁ