Laravel 5

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

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    18

    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.

  • Avatar de nash
    Membre depuis :
    16/04/2019
    Messages :
    14

    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.

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    18

    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.

  • Avatar de athena-dev
    Membre depuis :
    04/05/2019
    Messages :
    1

    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.

  • Avatar de nash
    Membre depuis :
    16/04/2019
    Messages :
    14

    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...

  • Avatar de CinquièmeDimension
    Membre depuis :
    18/04/2019
    Messages :
    18

Vous ne pouvez pas répondre à ce sujet.