48

I have a lot of trouble with the combination of symfony2 and doctrine2. I have to deal with huge datasets (around 2-3 million write and read) and have to do a lot of additional effort to avoid running out of memory.

I figgured out 2 main points, that "leak"ing memory (they are actually not really leaking, but allocating a lot).

  1. The Entitymanager entity storage (I don't know the real name of this one) it seems like it keeps all processed entities and you have to clear this storage regularly with

    $entityManager->clear()
  2. The Doctrine QueryCache - it caches all used Queries and the only configuration I found was, that you are able to decide what kind of Cache you wanna use. I didn't find a global disable neither a useful flag for each query to disable it. So usually I disable it for every query object with the function

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

So.. that's all I figured out right now.. My questions are:

Is there a easy way to deny some objects from the Entitymanagerstorage? Is there a way to set the querycache use in the entitymanager? Can I configure this caching behaviors somewhere in the Symfony/doctrine configuration?

Would be very cool if someone has some nice tips for me.. otherwise this may help some rookie..

cya

callmebob
  • 6,128
  • 5
  • 29
  • 46
MonocroM
  • 1,039
  • 1
  • 11
  • 10
  • 4
    The D2 ORM layer is not really designed for massive batch processing. You might be better off using the DBAL layer and just working with arrays. – Cerad Mar 14 '12 at 20:22
  • 1
    Running with **--no-debug** helps a lot (in debug mode the profiler retains informations about every single query in memory) – Arnaud Le Blanc Sep 14 '12 at 11:29

8 Answers8

90

As stated by the Doctrine Configuration Reference by default logging of the SQL connection is set to the value of kernel.debug, so if you have instantiated AppKernel with debug set to true the SQL commands get stored in memory for each iteration.

You should either instantiate AppKernel to false, set logging to false in you config YML, or either set the SQLLogger manually to null before using the EntityManager

$em->getConnection()->getConfiguration()->setSQLLogger(null);
callmebob
  • 6,128
  • 5
  • 29
  • 46
Sergi
  • 2,872
  • 2
  • 25
  • 24
  • 5
    Bravo! We've been hitting our heads against this problem for months! – jsalvata Jan 30 '13 at 15:48
  • 3
    It's the thing with SF2. You **really** need to read the docs and the code to understand how it works. The other day we discovered that we were not caching DQLs and metadatas between requests. We did so and [ended up](https://coderwall.com/p/ry1y0a) with requests twice as fast as before the change – Sergi Feb 02 '13 at 16:11
  • 4
    This was very helpful. I had a console command I wrote (was like a "daemon" type command) that kept on running out of memory and using the `clear()` method on the object manager wasn't enough. Disabling this SQL logger did the trick. However, since I was in a console command, I had to use `$this->getContainer()->get('doctrine')->getEntityManager()` to actually get to the entity manager to do this. – jzimmerman2011 Mar 17 '13 at 16:45
  • 1
    Well, I only used $em for brevity. – Sergi Mar 18 '13 at 16:58
  • I know this is true, but I'm upset that this is always the answer from the Doctrine camp. I love using it, why can't we get rid of the memory leaks? Also they give this answer to anyone who is referencing doctrine in a daemon type application. Daemon != batch and performance (time) != stability (memory use). – james_t Mar 22 '13 at 19:59
  • Well, Doctrine logging the SQL is more a feature than a bug, isn't it? Apart from that, if you keep doing clear() to your EntityManager you should have almost mo memory leaks. We have some dameons that are up for days and they don't leak memory, but even if they did we could use something like supervisord to restart them from time to time. – Sergi Mar 24 '13 at 13:04
  • Hi guys, i was looking for some solution on memory leaks with symfony2 and i found this threat. I obtain entity manager always like $em=$this->getDoctrine()->getEntityManager() or in repository via $this->_em-> so ... i dont know what should i do to remove some memory problems? BTW: sergi was mentioning the config file you mean like this please? doctrine: dbal: password: "%database_password%" charset: UTF8 logging: false Thx. – Lukas Lukac Aug 06 '13 at 00:05
  • Just run CLI scripts with --no-debug flag – Sergi Aug 07 '13 at 08:12
  • @Sergi hm.. i am not sure what script you mean. – Lukas Lukac Aug 09 '13 at 15:24
  • I though you were having problems with a SF2 command line command. If it's just a page, in production you should not have this kind of problems, as the entity manager has logging disabled, and in development just change the entity manager as already explained with $em->getConnection()->getConfiguration()->setSQLLogger(null); – Sergi Aug 09 '13 at 16:46
  • no i was talking about some suggestions how to decrease the memory usage in PROD env using ORM. – Lukas Lukac Aug 12 '13 at 07:23
  • In your app.php, when you instantiate the kernel, the second parameter $debug should be set to false in your production environment, i.e. "$kernel = new AppKernel($env, false);" Of course this parameter should not be hard coded, but should depend on your environment (true on dev and test, false on production). – Sergi Aug 12 '13 at 11:47
  • Yes it is, but is there any other "hidden" trick what do big difference for ram usage? (still talking about ORM) @Sergi – Lukas Lukac Aug 15 '13 at 12:04
  • Clear the entity manager, detach entities... Take a look to batch processing with doctrine 2 documentation http://docs.doctrine-project.org/en/latest/reference/batch-processing.html – Sergi Aug 16 '13 at 13:34
  • 1
    I was searching exactly for this. Thanks. – Wiliam Nov 16 '13 at 10:58
  • Does adding --no-debug option relapce the $em->getConnection()->getConfiguration()->setSQLLogger(null); – famas23 Sep 21 '20 at 09:10
18

Try running your command with --no-debug. In debug mode the profiler retains informations about every single query in memory.

Arnaud Le Blanc
  • 98,321
  • 23
  • 206
  • 194
  • Thanks this in combination with turning off the sqllogging really helped – Chase Feb 20 '14 at 01:51
  • this resolved an issue with the templating engine and twig. Running a loop over a simple template appeared to be a memory_leak in development. – Will B. Jul 09 '14 at 18:00
  • I'd been tinkering for hours when I found this... it solved the problem. thanks :) – indriq Nov 20 '14 at 20:51
15

1. Turn off logging and profiling in app/config/config.yml

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

or in code

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

2. Force garbage collector. If you actively use CPU then garbage collector waits and you can find yourself with no memory soon.

At first enable manual garbage collection managing. Run gc_enable() anywhere in the code. Then run gc_collect_cycles() to force garbage collector.

Example

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

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

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

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

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

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

If your table is very large, don't use findAll. Use iterator - http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/batch-processing.html#iterating-results

luchaninov
  • 6,792
  • 6
  • 60
  • 75
10
  1. Set SQL logger to null

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

  1. Manually call function gc_collect_cycles() after $em->clear()

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

Don't forget to set zend.enable_gc to 1, or manually call gc_enable() before use gc_collect_cycles()

  1. Add --no-debug option if you run command from console.
Serhii Smirnov
  • 1,338
  • 1
  • 16
  • 24
4

got some "funny" news from doctrine developers itself on the symfony live in berlin - they say, that on large batches, us should not use an orm .. it is just no efficient to build stuff like that in oop

.. yeah.. maybe they are right xD

MonocroM
  • 1,039
  • 1
  • 11
  • 10
  • we probably have to go down that road too.... it sucks that even though they know about it, it's not being addressed – Reza S Mar 04 '13 at 02:11
  • The Mapper pattern they are implementing is not really suited for performances but for improved abstraction & decoupling. I guess memory issues can be addressed using the `Doctrine\ORM\Tools\Pagination\Paginator` and remembering to `$em->clear()` once in a while, to unload already walked entities from memory. – Kamafeather Nov 11 '20 at 12:59
3

As per the standard Doctrine2 documentation, you'll need to manually clear or detatch entities.

In addition to that, when profiling is enabled (as in the default dev environment). The DoctrineBundle in Symfony2 configures a several loggers use quite a bit of memory. You can disable logging completely, but it is not required.

An interesting side effect, is the loggers affect both Doctrine ORM and DBAL. One of loggers will result in additional memory usage for any service that uses the default logger service. Disabling all of these would be ideal in commands-- since the profiler isn't used there yet.

Here is what you can do to disable the memory-intense loggers while keeping profiling enabled in other parts of 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);
}
Reece45
  • 2,771
  • 2
  • 17
  • 19
0

Try disabling any Doctrine caches that exist. (If you're not using APC / other as a cache then memory is used).

Remove Query Cache

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

There's no way to globally disable it

Also this is an alternative to clear that might help (from here)

$connection = $em->getCurrentConnection();
$tables = $connection->getTables();
foreach ( $tables as $table ) {
    $table->clear();
}
Community
  • 1
  • 1
james_t
  • 2,723
  • 1
  • 15
  • 20
0

I just posted a bunch of tips for using Symfony console commands with Doctrine for batch processing here.

Community
  • 1
  • 1
Collin Krawll
  • 2,210
  • 17
  • 15