データベースからcsvファイルに大量のデータをエクスポートしようとしていますが、非常に長い時間がかかり、大きなメモリ問題が発生する可能性があります。
メモリを増やすことなくCSVをエクスポートするためのより良い方法を誰かが知っていますか?もしそうなら、あなたは私にどのように見せることができますか?ありがとう。
ここに私のコントローラー:
def users_export
File.new("users_export.csv", "w") # creates new file to write to
@todays_date = Time.now.strftime("%m-%d-%Y")
@outfile = @todays_date + ".csv"
@users = User.select("id, login, email, last_login, created_at, updated_at")
FasterCSV.open("users_export.csv", "w+") do |csv|
csv << [ @todays_date ]
csv << [ "id","login","email","last_login", "created_at", "updated_at" ]
@users.find_each do |u|
csv << [ u.id, u.login, u.email, u.last_login, u.created_at, u.updated_at ]
end
end
send_file "users_export.csv",
:type => "text/csv; charset=iso-8859-1; header=present",
:disposition => "attachment; filename=#{@outfile}"
end
回答:
回答№1の場合は7あなたは1つの巨大な弦を作っているのでcsvファイル全体をメモリに保存します。あなたは「たくさんのメモリを使うことになる全てのユーザをロードしている。あなたが数百人か数千人のユーザしかいないとしても、何の違いもないが、あなたがたぶん2つのことをする必要があるだろう」
つかいます
User.find_each do |user|
csv << [...]
end
これにより、ユーザー全員ではなくバッチ(デフォルトで1000)にユーザーがロードされます。
全体をメモリにバッファするのではなく、csvをファイルに書き込むことも検討してください。一時ファイルを作成したとします。
FasterCSV.open("/path/to/file","w") do |csv|
...
end
あなたのcsvをファイルに書きます。あなたはその後使用することができます send_file
それを送るために。すでにファイルを開いている場合は、 FasterCSV.new(io)
うまくいくはずです。
最後に、レール3.1以降では、作成時にcsvファイルをストリーミングできるかもしれませんが、それは私が以前に試したことはありません。
回答№2については2
いいね!はい、もっと良い方法があります。 FasterCSV.generateは文字列をメモリに格納し、ブロックの最後に出力します。あなたは「使うほうがよい:
FasterCSV.open(output_file, "w+") do |row|
row << my_row_data
end
これで各行がファイルに書き込まれます。send_dataではなく "send_file"。あるいは、本当に本当にsend_dataを実行しなければならないのであれば、ファイルが構築されている間それをすべて保持するのではなく、ファイルの内容を取得して送信してください(まだお勧めできません)
回答№3の場合は1
csv生成のヒントに加えて、データベースへの呼び出しも最適化するようにしてください。 必要な列だけを選択してください。
@users = User.select("id, login, email, last_login, created_at, updated_at").order("login")
@users.find_each do |user|
...
end
例えば1000人のユーザがいて、それぞれがpassword、password_salt、都市、国、... その後、1000個以下のオブジェクトがデータベースから転送され、ルビーオブジェクトとして作成され、最後にガベージコレクションされます。