web-dev-qa-db-fra.com

Fuites de mémoire Symfony2 Doctrine2/dépasse la limite de mémoire

en fait, j'ai beaucoup de mal à combiner symfony2 et doctrine2. Je dois faire face à d'énormes jeux de données (environ 2 à 3 millions d'écriture et de lecture) et faire beaucoup d'efforts supplémentaires pour éviter de manquer de mémoire.

J'ai découvert 2 points principaux, cette "fuite" de mémoire (ils ne coulent pas vraiment, mais ils allouent beaucoup)

  1. Le stockage d’entités Entitymanager (je ne connais pas le nom réel de celui-ci), il semble conserver toutes les entités traitées et vous devez vider ce stockage régulièrement avec

    $ entityManager-> clear ()
  2. Doctrine QueryCache - il met en cache toutes les requêtes utilisées et la seule configuration que j'ai trouvée est que vous êtes en mesure de décider du type de cache que vous souhaitez utiliser. Je n'ai pas trouvé un global désactiver ni un drapeau utile pour chaque requête pour le désactiver . Donc généralement le désactiver pour chaque objet de requête avec la fonction

     $ qb = $ repository-> createQueryBuilder ($ a); 
     $ query = $ qb-> getQuery (); 
     $ query-> useQueryCache (false); 
     $ query-> execute (); 
    

donc ... c'est tout ce que j'ai compris en ce moment ... mes questions sont:

Existe-t-il un moyen simple de refuser certains objets de Entitymanagerstorage? Existe-t-il un moyen de définir l’utilisation de querycache dans le entitymanager? Puis-je configurer ces comportements de mise en cache quelquefois dans la configuration de la doctrine symonfony?

Serait très cool si quelqu'un a quelques bons conseils pour moi .. sinon cela peut aider une recrue ..

cya

43
MonocroM

Un peu tard, mais je pense que je viens de trouver un fil sur Google Groupes de Benjamin Eberlei qui répond à votre question: comme indiqué par Doctrine Configuration Reference par défaut logging du code SQL connection est défini sur la valeur kernel.debug. Par conséquent, si vous avez instancié AppKernel avec debug défini sur true, les commandes SQL sont stockées en mémoire pour chaque itération. 

Vous devez instancier AppKernel sur false, définir logging sur false dans votre configuration YML ou définir manuellement SQLLogger sur null avant d'utiliser EntityManager.

$em->getConnection()->getConfiguration()->setSQLLogger(null);
84
Sergi

Essayez d’exécuter votre commande avec --no-debug . En mode débogage, le profileur conserve des informations sur chaque requête en mémoire.

17
Arnaud Le Blanc

1. Désactiver la journalisation et le profilage dans app/config/config.yml

doctrine:
    dbal:
        driver: ...
        ...
        logging: false
        profiling: false

ou en code

$this->em->getConnection()->getConfiguration()->setSQLLogger(null);

2. Forcer le ramasse-miettes. Si vous utilisez activement le processeur, le ramasse-miettes attend et vous pourrez bientôt vous retrouver sans mémoire.

Activez d'abord la gestion manuelle de la récupération de place. Exécutez gc_enable() n'importe où dans le code. Puis exécutez gc_collect_cycles() pour forcer le ramasse-miettes.

Exemple

public function execute(InputInterface $input, OutputInterface $output)
{
    gc_enable();

    // I'm initing $this->em in __construct using DependencyInjection
    $customers = $this->em->getRepository('AppBundle:Customer')->findAll();

    $counter = 0;
    foreach ($customers as $customer) {
        // process customer - some logic here, $this->em->persist and so on

        if (++$counter % 100 == 0) {
            $this->em->flush(); // save unsaved changes
            $this->em->clear(); // clear doctrine managed entities
            gc_collect_cycles(); // PHP garbage collect

            // Note that $this->em->clear() detaches all managed entities,
            // may be you need some; reinit them here
        }
    }

    // don't forget to flush in the end
    $this->em->flush();
    $this->em->clear();
    gc_collect_cycles();
}

Si votre table est très grande, n'utilisez pas findAll. Utiliser itérateur - http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/batch-processing.html#iterating-results

11
luchaninov

j'ai eu des nouvelles "marrantes" de la part des développeurs de la doctrine eux-mêmes sur le symfony live à berlin - ils disent que, sur de grands lots, nous ne devrions pas utiliser un orm .. il est tout simplement inutile de construire de telles choses dans oop

.. ouais .. peut-être qu'ils ont raison xD

4
MonocroM

Conformément à la documentation standard de Doctrine2, vous devez effacer ou détacher manuellement des entités.

De plus, lorsque le profilage est activé (comme dans l’environnement de développement par défaut). DoctrineBundle dans Symfony2 configure plusieurs enregistreurs qui utilisent beaucoup de mémoire. Vous pouvez désactiver complètement la journalisation, mais ce n’est pas obligatoire. 

Un effet secondaire intéressant est que les enregistreurs affectent à la fois Doctrine ORM et DBAL. L'un des enregistreurs entraînera une utilisation supplémentaire de la mémoire pour tout service utilisant le service d'enregistreur par défaut. Désactiver tous ces éléments serait idéal dans les commandes, car le profileur n'y est pas encore utilisé.

Voici ce que vous pouvez faire pour désactiver les enregistreurs exigeant beaucoup de mémoire tout en conservant le profil activé dans d'autres parties de Symfony2:

$c = $this->getContainer();
/* 
 * The default dbalLogger is configured to keep "stopwatch" events for every query executed
 * the only way to disable this, as of Symfony 2.3, Doctrine Bundle 1.2, is to reinistiate the class
 */

$dbalLoggerClass = $c->getParameter('doctrine.dbal.logger.class');
$dbalLogger = new $dbalLoggerClass($c->get('logger'));   
$c->set('doctrine.dbal.logger', $dbalLogger);

// sometimes you need to configure doctrine to use the newly logger manually, like this
$doctrineConfiguration = $c->get('doctrine')->getManager()->getConnection()->getConfiguration();
$doctrineConfiguration->setSQLLogger($dbalLogger);

/*
 * If profiling is enabled, this service will store every query in an array
 * fortunately, this is configurable with a property "enabled"
 */
if($c->has('doctrine.dbal.logger.profiling.default'))
{
    $c->get('doctrine.dbal.logger.profiling.default')->enabled = false;
}

/*
 * When profiling is enabled, the Monolog bundle configures a DebugHandler that 
 * will store every log messgae in memory. 
 *
 * As of Monolog 1.6, to remove/disable this logger: we have to pop all the handlers
 * and then Push them back on (in the correct order)
 */
$handlers = array();
try
{   
    while($handler = $logger->popHandler())
    {
        if($handler instanceOf \Symfony\Bridge\Monolog\Handler\DebugHandler)
        {
            continue;
        }
        array_unshift($handlers, $handler);
    }
}
catch(\LogicException $e)
{
    /*
     * As of Monolog 1.6, there is no way to know if there's a handler
     * available to pop off except for the \LogicException that's thrown.
     */
    if($e->getMessage() != 'You tried to pop from an empty handler stack.')
    {
        /*
         * this probably doesn't matter, and will probably break in the future
         * this is here for the sake of people not knowing what they're doing
         * so than an unknown exception is not silently discarded.
         */

        // remove at your own risk
        throw $e;
    }
}

// Push the handlers back on
foreach($handlers as $handler)
{
    $logger->pushHandler($handler);
}
3
Reece45

Je viens de publier de nombreux conseils d'utilisation des commandes de la console Symfony avec Doctrine pour le traitement par lots ici .

0
Collin Krawll

Essayez de désactiver les caches Doctrine existants. (Si vous n'utilisez pas APC/other comme cache, la mémoire est utilisée).

Supprimer le cache de requêtes

$qb = $repository->createQueryBuilder($a);
$query = $qb->getQuery();
$query->useQueryCache(false);
$query->useResultCache(false);
$query->execute();

_ {Il n'y a aucun moyen de le désactiver globalement} _

Aussi, c’est une alternative à effacer qui pourrait aider (de ici )

$connection = $em->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
    $table->clear();
}
0
james_t