web-dev-qa-db-de.com

Speicherlecks Symfony2 Doctrine2/überschreitet den Speichergrenzwert

eigentlich habe ich viel problem mit der kombination von symfony2 und doctrine2. Ich muss mich mit riesigen Datensätzen beschäftigen (ca. 2-3 Millionen schreiben und lesen) und viel zusätzlichen Aufwand unternehmen, um nicht genügend Speicherplatz zu haben.

Ich habe 2 Hauptpunkte herausgearbeitet, die den Speicher "durchlöchern" (sie sind eigentlich nicht wirklich undicht, aber sie ordnen viel zu).

  1. Der Entitymanager Entity-Speicher (ich kenne den richtigen Namen dieses nicht) Es scheint, dass er alle verarbeiteten Entitäten enthält und Sie diesen Speicher regelmäßig löschen müssen

    $ entityManager-> clear ()
  2. Doctrine QueryCache - es speichert alle verwendeten Abfragen im Cache und die einzige Konfiguration, die ich gefunden habe, war, dass Sie entscheiden können, welche Art von Cache Sie verwenden möchten. Ich habe weder eine globale Deaktivierung gefunden, noch ein nützliches Flag für jede Abfrage, um sie zu deaktivieren .. __ Deaktivieren Sie sie normalerweise für jedes Abfrageobjekt mit der Funktion

     $ qb = $ repository-> createQueryBuilder ($ a); 
     $ query = $ qb-> getQuery (); 
     $ query-> useQueryCache (false); 
     $ query-> Ausführen (); 
    

so .. das ist alles, was ich gerade herausgefunden habe. Meine Fragen sind:

Gibt es eine einfache Möglichkeit, einige Objekte aus der Entitymanagerstorage zu verweigern? Gibt es eine Möglichkeit, die Verwendung des Querycaches im Entitätsmanager festzulegen? Kann ich dieses Caching-Verhalten in der Konfiguration der Symonfony-Doktrin konfigurieren?

Wäre sehr cool, wenn jemand ein paar nette Tipps für mich hat .. sonst könnte dies einigen Anfängern geholfen haben ..

cya

43
MonocroM

Ein bisschen spät, aber ich glaube, ich habe gerade einen Thread bei Google Groups von Benjamin Eberlei gefunden, der Ihre Frage beantwortet: wie in der Doctrine Configuration Reference standardmäßig angegeben - logging der SQL-Verbindung wird auf den Wert kernel.debug gesetzt. Wenn Sie also AppKernel mit dem Debug-Wert true instanziiert haben, werden die SQL-Befehle gespeichert im Speicher für jede Iteration.

Sie sollten entweder AppKernel auf false instanziieren, logging auf false in Ihrer YML-Konfiguration setzen oder SQLLogger manuell auf null setzen, bevor Sie den EntityManager verwenden

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

Versuchen Sie, Ihren Befehl mit --no-debug auszuführen. Im Debug-Modus speichert der Profiler Informationen zu jeder einzelnen Abfrage im Speicher.

17
Arnaud Le Blanc

1. Deaktivieren Sie die Protokollierung und das Profiling in app/config/config.yml.

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

oder im Code

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

2. Garbage Collector zwingen. Wenn Sie die CPU aktiv verwenden, wartet der Garbage Collector und Sie können sich bald ohne Speicher befinden.

Aktivieren Sie zunächst die manuelle Speicherbereinigung. Führen Sie gc_enable() an beliebiger Stelle im Code aus. Führen Sie dann gc_collect_cycles() aus, um den Garbage Collector zu erzwingen.

Beispiel

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();
}

Wenn Ihre Tabelle sehr groß ist, verwenden Sie nicht findAll. Verwenden Sie den Iterator - http://doctrine-orm.readthedocs.org/projects/doctrine-orm/de/latest/reference/batch-processing.html#iterating-results

11
luchaninov
  1. Setzen Sie den SQL-Logger auf null

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

  1. Manuelles Aufrufen der Funktion gc_collect_cycles() nach $em->clear()

$em->clear(); gc_collect_cycles();

Vergessen Sie nicht, zend.enable_gc auf 1 zu setzen oder gc_enable () manuell aufzurufen, bevor Sie gc_collect_cycles () verwenden

  1. Hinzufügen --no-debug Option, wenn Sie den Befehl von der Konsole aus ausführen.
9
Sergii Smirnov

habe einige "lustige" Neuigkeiten von Doktrinentwicklern auf der symfony live in berlin bekommen - sie sagen, dass wir bei großen Batches keinen orm verwenden sollten. Es ist einfach nicht effizient, so etwas in oop zu bauen

.. ja .. vielleicht haben sie recht xD

4
MonocroM

Gemäß der Doctrine2-Standarddokumentation müssen Sie Entitäten manuell löschen oder freigeben.

Darüber hinaus, wenn die Profilerstellung aktiviert ist (wie in der Standard-Entwicklungsumgebung). Das DoctrineBundle in Symfony2 konfiguriert mehrere Logger, die ziemlich viel Speicher benötigen. Sie können die Protokollierung vollständig deaktivieren, dies ist jedoch nicht erforderlich.

Ein interessanter Nebeneffekt ist, dass die Logger sowohl Doctrine ORM als auch DBAL beeinflussen. Einer der Logger führt zu einer zusätzlichen Speichernutzung für alle Dienste, die den Standard-Logger-Dienst verwenden. Das Deaktivieren all dieser Befehle ist ideal, da der Profiler dort noch nicht verwendet wird.

Mit den folgenden Schritten können Sie die speicherintensiven Logger deaktivieren, während die Profilerstellung in anderen Teilen von Symfony2 aktiviert bleibt:

$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

Ich habe gerade ein paar Tipps zur Verwendung von Symfony-Konsolenbefehlen mit Doctrine für die Stapelverarbeitung hier veröffentlicht.

0
Collin Krawll

Deaktivieren Sie eventuell vorhandene Doctrine-Caches. (Wenn Sie APC/other nicht als Cache verwenden, wird der Speicher verwendet).

Abfrage-Cache entfernen

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

Es gibt keine Möglichkeit, es global zu deaktivieren

Auch das ist eine Alternative zu klar, die helfen könnte (von hier )

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