2017-09-25 3 views
1

J'ai une commande Symfony qui utilise le Doctrine Paginator sur PHP 7.0.22. La commande doit traiter les données d'une grande table, donc je le fais en morceaux de 100 éléments. Le problème est qu'après quelques centaines de boucles, il arrive à remplir 256M de RAM. Comme mesures contre OOM (hors mémoire) J'utilise:Doctrine Paginator remplit la mémoire

  • $em->getConnection()->getConfiguration()->setSQLLogger(null); - désactive l'enregistreur sql, qui remplit la mémoire avec des requêtes journalisés pour l'exécution de scripts de nombreuses commandes SQL
  • $em->clear(); - détache tous les objets de la doctrine à la fin de chaque boucle

J'ai mis des décharges avec memory_get_usage() pour vérifier ce qui se passe et il semble que le collecteur ne nettoie pas autant que la commande ajoute à chaque appel $paginator->getIterator()->getArrayCopy();.

J'ai même essayé de collecter manuellement les déchets toutes les boucles avec gc_collect_cycles(), mais toujours pas de différence, la commande commence à utiliser 18M et augmente avec ~ 2M tous les quelques centaines d'éléments. Également essayé de mettre à jour manuellement les résultats et le constructeur de requête ... rien. J'ai enlevé tout le traitement des données et gardé seulement la requête choisie et le paginateur et ai obtenu le même comportement.

Quelqu'un a-t-il une idée de ce que je devrais regarder après?

Note: 256M devrait être plus que suffisant pour ce genre d'opérations, donc s'il vous plaît ne recommande pas de solutions qui suggèrent d'augmenter la mémoire autorisée.

La méthode rayée vers le bas execute() ressemble à quelque chose comme ceci:

protected function execute(InputInterface $input, OutputInterface $output) 
{ 

    // Remove SQL logger to avoid out of memory errors 
    $em = $this->getEntityManager(); // method defined in base class 
    $em->getConnection()->getConfiguration()->setSQLLogger(null); 


    $firstResult = 0; 


    // Get latest ID 
    $maxId = $this->getMaxIdInTable('AppBundle:MyEntity'); // method defined in base class 
    $this->getLogger()->info('Working for max media id: ' . $maxId); 

    do { 

     // Get data 
     $dbItemsQuery = $em->createQueryBuilder() 
      ->select('m') 
      ->from('AppBundle:MyEntity', 'm') 

      ->where('m.id <= :maxId') 
      ->setParameter('maxId', $maxId) 

      ->setFirstResult($firstResult) 
      ->setMaxResults(self::PAGE_SIZE) 
     ; 

     $paginator = new Paginator($dbItemsQuery); 

     $dbItems = $paginator->getIterator()->getArrayCopy(); 

     $totalCount = count($paginator); 
     $currentPageCount = count($dbItems); 

     // Clear Doctrine objects from memory 
     $em->clear(); 


     // Update first result 
     $firstResult += $currentPageCount; 
     $output->writeln($firstResult); 
    } 
    while ($currentPageCount == self::PAGE_SIZE); 


    // Finish message 
    $output->writeln("\n\n<info>Done running <comment>" . $this->getName() . "</comment></info>\n"); 
} 

Répondre

0

La fuite de mémoire a été générée par la doctrine Paginator. Je l'ai remplacé par une requête native en utilisant Doctrine prepared statements et l'ai corrigé.

Autres choses que vous devriez prendre en considération:

  • Si vous remplacez la Doctrine Paginator, vous devez recréer la fonctionnalité de pagination, en ajoutant une limite à votre requête.
  • Exécutez votre commande avec le drapeau --no-debug ou -env=prod ou peut-être les deux. Le problème est que les commandes sont exécutées par défaut dans l'environnement dev. Cela active certains collecteurs de données qui ne sont pas utilisés dans l'environnement prod. En savoir plus sur ce sujet dans le Symfony documentation - How to Use the Console

Edit: Dans mon cas particulier j'utilise également le faisceau eightpoints/guzzle-bundle qui implémente la bibliothèque HTTP Guzzle (avait dans ma commande quelques appels API). Cette liasse fuyait également, apparemment à travers un intergiciel. Pour résoudre ce problème, j'ai dû instancier le client Guzzle indépendamment, sans le bundle EightPoints.