Laravel 5

Gérer une grosse quantité de données sans faire fondre la RAM

Avatar de CinquièmeDimension
CinquièmeDimension

Salut les amis,

Voilà, je dois** importer des données depuis 5 fichiers CSV (le plus gros faisant un peu plus de 70K lignes)**, retraiter les données pour les uploader dans une base de donnée dont le schema est different, et ce une fois par mois. Les noms des champs sont differents et tous ne sont pas utilisés.

La répartition des champs est comme suit:

  • Csv1 -> base1
  • Csv2 -> base2
  • Csv3 -> base3 et base5
  • Csv4 -> base4 et base5
  • Csv5 -> base4 et base5

Mais la répartition n'est pas le sujet de ce post. Je regarderai ça dans un deuxième temps.

Pour l'instant, j'utilise FastExcel pour créer une Collection avec mon fichier CSV avant de moulier ces données et de les uploader.

Et c'est là que la dificulté commence à poindre. Entre l'import et la création dans la basse d'accueil. La collection de plus de 70K lignes demande beaucoup de ressources. J'ai fait quelques tests et je suis arrivé au bout de la memory_limit et** max_execution_time **plusieurs fois. Certes, je pourrais augmenter ces deux parametres dans php.ini, et je prévoie bien de les augmenter, mais ce n'est que repousser l'obstacle un peu plus loin. Je ne sais pas si un fichier de 200K ne peux pas arriver (très peu probable mais tout de même possible).

J'aimerai savoir si vous avez une idée sur comment stocker toutes ces données en attente de traitement. Car la Collection contenant des dizaines de milliers de lignes demande trop de ressources au serveur.

J'ai pense à faire un chunk() à partir de la collection pour traiter les données par paquets mais ça implique que le serveur ait supporté l'import, ce qui n'est pas forcement le cas. Peut-être existe-t-il un moyen de stocker ces données dans un fichier temporaire au lieu de le stocker dans la mémoire...

J'ai également posté une demande dans le github de FastExcel pour voir s'il existe un moyen de faire un chunk directement à l'import pour ne pas avoir la collection complète à stocker.

Posté il y a 1 an
Avatar de nash
nash

salut,

Utilise une des classes php iterator, tu pourras liberer un max de memoire avec yield. La solution sera de mettre en place un proccess job et un queue laravel (pense a la declaration de ton driver en database).

Si mes souvenirs son bon tu dois pouvoir paginer ta collection. Ce qui te permet la mise en place avec un traitement par lot.

Posté il y a 1 an
Avatar de CinquièmeDimension
CinquièmeDimension

Merci de ta reponse Nash.

Jusqu'à present j'ai envisagé le code comme suit:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Rap2hpoutre\FastExcel\FastExcel;

class csvController extends Controller
{
    public function index()
    {
        //definition du fichier
        $file = public_path('csv/file.csv');
        
        //transformation en collection
        $collection = (new FastExcel)->configureCsv('|', '#', '\n', 'UTF-8')->import($file);
        
        //on défini un index qui correspondra à $collection[$n]
        $n = 0;
        
        //on efface la première ligne de $collection qui correspond à la description des champs
        $description = $collection->shift();
        
        //on découpe la collection en lots de 1000 lignes
        foreach ($collection->chunk(3) as $chunk){
            
            //on traîte chaque ligne
            foreach($chunk as $c){
                
                //création du nouvel objet à unloader
                $table1 = new Table1([
                     'name'   => $c['name'],
                     'firstname'   => $c['firstname']
                ]);
                $table1->save;
                
                //SUPPRESSION DE LA LIGNE POUR SOULAGER LA MEMOIRE
                $collection[$n] = NULL;

                $n++;
                
            }//fin de foreach($chunk as $c)
            
        }//fin de foreach ($collection->chunk(3) as $chunk)
    }
}
 

D'après ce que j'ai lu, la Collection est déjà une classe iterative.

Effectivement j'ai pas utilisé de yield. Je ne me suis jamais servi de yield à vrai dire. Dans cet example j'ai ma collection entière au début et je le vide au fur et à mesure, mais il existe toujours le risque que la mémoire soit surchargée par cette grosse collection.

Je prévois de faire un process job, vu que cet upload sera sensé se faire automatiquement tous les mois.

Posté il y a 1 an
Avatar de athena-dev
athena-dev

Bonsoir

Ayant eu dans le passé à jouer avec de tres gros imports ou update de table où l'unité de mesure etait le 100K lines et surtout travail sur base en prod sans possibilité de lock sur une table, je verais plus ce job avec des procedures stockées et un cron lançant un script. Autant dans ces cas la utiliser les fonctions natives de la BD.

Posté il y a 1 an
Avatar de nash
nash

salut,

D'après ce que j'ai lu, la Collection est déjà une classe iterative. entierement d'accord, sauf que : Tu as la methode getIterator() de la classe Collection ce qui te permettra de redefinir l'itération de ta collection.

//SUPPRESSION DE LA LIGNE POUR SOULAGER LA MEMOIRE

                $collection[$n] = NULL;

Tu as déja rempli ta pile tu ne videra absolument rien, le probleme de base est la quantité de données.

Le traitement devra ce faire directement à la lecture de ton fichier. Dans ton post prédédent, j'ai mis l'accent sur une lecture séquentielle (comme un fichier texte classique) de ton csv ou autre pour la mise en place un traitement par lot (Faudra mettre les mains dans le cambouis pour les traitements de ce genre).

athena-dev propose aussi une bonne solution.

A suivre...

Posté il y a 1 an

Vous ne pouvez pas répondre à ce sujet.