Создание файла CSV / Excel для 100 миллионов записей в Ruby на рельсах? - PullRequest
4 голосов
/ 29 мая 2019

Требование равно

Мы получаем огромный набор данных из базы данных (> 1 млрд. Записей) и должны экспортировать его в файл CSV или Excel.

В настоящее время реализация использует CSV класс CSV.generate

 CSV.generate(headers: true) do |csv|
    csv << header
    @obj.find_each do |c|
     arr = [c.id,c.name,soon]
     csv << array
    end
 end

и отправка вывода на

Zip::File.open(file, Zip::File::CREATE) do |zip|
        zip.get_output_stream("test.#{@format}") { |f| f.puts(convert_to_csv) }
      end

Вся эта операция выполняется другими отложенными заданиями Это хорошо работает, когда запись <20000 Но когда строки начинают расти, возникают проблемы с памятью. </p>

Я думал о том, чтобы разбить запись на части, скажем, 1 миллион строк в 50 файлах (1 миллион / 20000) (csv1.csv, csv2.csv, csv3.csv, csv4.csv, csv5.csv) и затем выполнить конкат их в один файл или сжать все файлы вместе (более быстрый способ)

Может ли кто-нибудь дать мне представление, как я могу начать с него.

Ответы [ 3 ]

1 голос
/ 29 мая 2019

Запись в CSV кусками и find_in_batches и pluck.Что-то вроде:

Model.pluck(:id, :name, ...).find_in_batches(10_000) do |ary|
  CSV.open("tmp.csv", "ab") do |csv|
    csv << ary.map{|a| a.join ','}.join("\n")
  end
end
1 голос
/ 03 июня 2019

Это будет зависеть от того,

     arr = [c.id,c.name,soon]

нужно рассчитать в Ruby, иначе вы сможете переписать его в SQL.

  • Если вам нужно сохранить его в Ruby, вы можете попытаться избежать накладных расходов ActiveRecord и использовать вместо этого raw query . Вам, вероятно, придется самостоятельно выполнять обработку фрагментов
  • В противном случае, вы можете проверить какой-нибудь собственный инструмент базы данных для экспорта в CSV. Например, для MySQL это будет что-то вроде SELECT INTO OUTFILE или mysql
0 голосов
/ 29 мая 2019

Взглянув на источник для CSV.generate, у меня создается впечатление, что данные CSV хранятся в памяти, пока их содержимое накапливается. Это кажется хорошей целью для оптимизации, особенно если вы видите, что память масштабируется линейно с набором данных. Поскольку ваши данные довольно просты, не могли бы вы пропустить CSV и перейти непосредственно к файлу? У вас будет немного больше контроля над тем, когда данные будут записаны на диск.

File.open("my.csv") do |file|
  file.puts '"ID","Name","Soon"'
  @obj.find_each do |c|
    file.puts "\"#{c.id}\",\"#{c.name}\",\"#{c.soon}\""
    # flush if necessary
  end
end

При таком подходе вам нужно будет записать на диск, а затем сжать результаты позже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...