Проблема с памятью с огромным экспортом CSV в Rails

Я пытаюсь экспортировать большой объем данных из базы данных в файл 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

person Community    schedule 27.06.2012    source источник


Ответы (2)


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

Наконец, на rails 3.1 и выше вы можете передавать CSV-файл по мере его создания, но это не то, что я пробовал раньше.

person Frederick Cheung    schedule 27.06.2012
comment
Я изменил свой код в соответствии с предложенными решениями (дайте мне знать, похоже ли это на то, что вы имели в виду, пожалуйста), но это все равно занимает очень много времени. Есть еще идеи? Также знаете ли вы, как я могу транслировать CSV-файл при его создании? Кажется, я не могу найти ничего о том, как это сделать .. спасибо! - person ; 29.06.2012
comment
Примечание. Я использую рельсы версии 3.0.9. - person ; 29.06.2012

В дополнение к советам по созданию 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 объектов меньше, созданы как рубиновые объекты и, наконец, собран мусор.

person Meier    schedule 27.06.2012