/ / Problème de mémoire associé à une énorme exportation CSV dans Rails - ruby-on-rails, Fastcsv

Problème de mémoire avec une énorme exportation CSV dans Rails - ruby-on-rails, Fastcsv

J'essaie d'exporter une grande quantité de données d'une base de données vers un fichier csv, mais cela prend très longtemps et je crains d'avoir de gros problèmes de mémoire.

Est-ce que quelqu'un connaît un meilleur moyen d'exporter un fichier CSV sans accumuler de la mémoire? Si oui, pouvez-vous me montrer comment? Merci.

Voici mon contrôleur:

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

Réponses:

7 pour la réponse № 1

Vous construisez une ficelle géante pour que vous ayezgarder le fichier csv entier en mémoire. Vous êtes également en train de charger tous vos utilisateurs qui resteront sur une bande de mémoire. Cela ne fera aucune différence si vous n’avez que quelques centaines ou quelques milliers d’utilisateurs, mais vous devrez probablement faire deux choses.

Utilisation

User.find_each do |user|
csv << [...]
end

Cela charge les utilisateurs par lots (1000 par défaut) plutôt que tous.

Vous devriez également envisager d’écrire le fichier csv dans un fichier plutôt que de mettre en mémoire tampon l’ensemble du contenu en mémoire. En supposant que vous ayez créé un fichier temporaire,

FasterCSV.open("/path/to/file","w") do |csv|
...
end

Va écrire votre csv dans un fichier. Vous pouvez alors utiliser send_file pour l'envoyer. Si vous avez déjà un fichier ouvert, FasterCSV.new(io) devrait fonctionner aussi.

Enfin, sur les rails 3.1 et supérieurs, vous pourrez peut-être diffuser le fichier csv au fur et à mesure que vous le créez, mais ce n’est pas quelque chose que j’ai déjà essayé.


2 pour la réponse № 2

Beurk! Oui, j'ai un meilleur moyen. FasterCSV.generate stocke une chaîne en mémoire et la sort à la fin du bloc. Vous feriez mieux d'utiliser:

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

Cela écrira chaque ligne dans le fichier, alors vous pourrez"send_file" plutôt que send_data. Ou si vous devez vraiment envoyer send_data, récupérez le contenu du fichier et envoyez-le plutôt que de le conserver pendant la création du fichier (ce n'est toujours pas recommandé)


1 pour la réponse № 3

Outre les conseils relatifs à la génération de fichiers csv, veillez également à optimiser l'appel à la base de données. Sélectionnez uniquement les colonnes dont vous avez besoin.

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

Si vous avez par exemple 1000 utilisateurs, et chacun ontpassword, password_salt, ville, pays, ... puis 1 000 objets de moins sont transférés de la base de données, créés en tant qu’objets ruby ​​et finalement, les ordures collectées.