2012-06-27 4 views
3

Я пытаюсь экспортировать большой объем данных из базы данных в файл csv, но это занимает очень много времени и боится, что у меня будут серьезные проблемы с памятью.Проблема с памятью с огромным CSV Export in Rails

Кто-нибудь знает, какой лучший способ экспортировать 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 

ответ

2

Хлоп! Да, у меня есть лучший способ. FasterCSV.generate хранит строку в памяти и выводит ее в конце блока. Вы лучше использовать:

FasterCSV.open(output_file, 'w+') do |row| 
    row << my_row_data 
end 

Это будет записывать каждую строку в файл, то вы можете «send_file», а не send_data. Или, если вам действительно нужно send_data, тогда захватите содержимое файла и отправьте, а не удерживайте его все, пока файл создается (все еще не рекомендуется).

7

Вы создаете одну гигантскую строку, поэтому вам нужно сохранить весь файл 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-файлов по мере его создания, но это не то, что я пробовал раньше.

+0

Я изменил свой код в соответствии с предлагаемыми решениями (дайте мне знать, если это похоже на то, что вы имели в виду, пожалуйста), но это все еще, кажется, занимает очень много времени. Есть еще идеи? Также вы знаете, как я мог бы передать файл csv, когда создаю его? Кажется, не может найти ничего о том, как это сделать .. спасибо! –

+0

Примечание: я использую рельсы версии 3.0.9 –

1

Кроме советов по генерации csv, обязательно оптимизируйте вызов в базе данных. Выберите только нужные столбцы.

@users = User.select('id, login, email, last_login, created_at, updated_at').order('login') 
@users.find_each do |user| 
    ... 
end 

Если у вас есть, например, 1000 пользователей, и у каждого есть пароль, password_salt, город, страна, ... затем несколько 1000 объекты меньше передаются из базы данных, созданные в качестве объектов рубина и, наконец, сбором мусора.

Смежные вопросы