Утечки памяти Symfony2 Doctrine2 / превышают лимит памяти - PullRequest
44 голосов
/ 14 марта 2012

На самом деле у меня много проблем с комбинацией Symfony2 и Doctrine2. Мне приходится иметь дело с огромными наборами данных (около 2-3 миллионов записей и чтения), и мне нужно приложить немало дополнительных усилий, чтобы избежать нехватки памяти.

Я выяснил 2 основных момента, которые «просачиваются» из памяти (на самом деле они не очень протекают, но выделяют много)

  1. Хранилище сущностей Entitymanager (я не знаю настоящего имени этого), похоже, что оно сохраняет все обработанные объекты, и вы должны очистить это хранилище с помощью

    $entityManager->clear()
  2. Doctrine QueryCache - он кэширует все используемые запросы, и единственная найденная мною конфигурация состояла в том, что вы можете решить, какой тип кэша вы хотите использовать. Я не нашел глобальное отключение, ни флаг полезности для каждого запроса, чтобы отключить его. Поэтому обычно отключайте его для каждого объекта запроса с помощью функции

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

так .. вот и все, что я понял прямо сейчас .. мои вопросы:

Есть ли простой способ отказать некоторым объектам из хранилища Entitymanagers? Есть ли способ установить использование кеша запросов в диспетчере сущностей? Могу ли я настроить это поведение кэширования где-нибудь в конфигурации доктрины Symonfony?

Было бы очень здорово, если бы у кого-нибудь были хорошие советы для меня ... в противном случае это могло бы помочь новичку ..

суа

Ответы [ 8 ]

84 голосов
/ 06 июня 2012

Немного поздно, но я думаю, что только что нашел ветку в Группах Google Бенджамина Эберлей, которая отвечает на ваш вопрос: как указано в Справочнике по конфигурации доктрин по умолчанию logging соединения SQL установлено в значение kernel.debug , поэтому, если вы создали экземпляр AppKernel с отладкой, установленной в true , команды SQL сохраняются в памяти за каждую итерацию.

Вы должны либо создать экземпляр AppKernel для false , установить logging в false в вашей конфигурации YML, либо либо вручную установить SQLLogger в null перед использованием EntityManager

$em->getConnection()->getConfiguration()->setSQLLogger(null);
17 голосов
/ 14 сентября 2012

Попробуйте выполнить команду с - без отладки .В режиме отладки профилировщик сохраняет информацию о каждом запросе в памяти.

12 голосов
/ 12 ноября 2015

1.Отключите ведение журнала и профилирование в app/config/config.yml

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

или в коде

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

2.Силовой сборщик мусора .Если вы активно используете процессор, то сборщик мусора ждет, и вы скоро окажетесь без памяти.

Сначала включите ручное управление сборкой мусора.Запустите gc_enable() в любом месте кода.Затем запустите gc_collect_cycles(), чтобы запустить сборщик мусора.

Пример

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

Если ваша таблица очень велика, не используйте findAll.Используйте итератор - http://doctrine -orm.readthedocs.org / projects / doctrine-orm / en / latest / reference / batch-processing.html # iterating-results

9 голосов
/ 18 июня 2015
  1. Установить для регистратора SQL значение null

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

  1. Вручную вызвать функцию gc_collect_cycles() после $em->clear()

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

Не забудьте установить zend.enable_gc на 1 или вручную вызвать gc_enable () перед использованием gc_collect_cycles ()

  1. Добавить --no-debug параметр, если вы запускаете команду из консоли.
4 голосов
/ 23 ноября 2012

получил несколько "забавных" новостей от самих разработчиков доктрин о Symfony Live в Берлине - они говорят, что при больших партиях нам не следует использовать orm ... просто неэффективно создавать подобные вещи в oop

.. да .. возможно они правы xD

3 голосов
/ 08 сентября 2013

В соответствии со стандартной документацией Doctrine2 вам необходимо вручную очистить или отменить удаление объектов.

В дополнение к этому, когда профилирование включено (как в среде разработчика по умолчанию). DoctrineBundle в Symfony2 настраивает несколько регистраторов, использующих довольно много памяти. Вы можете полностью отключить ведение журнала, но это не обязательно.

Интересным побочным эффектом является то, что регистраторы влияют как на Doctrine ORM, так и на DBAL. Один из регистраторов приведет к дополнительному использованию памяти для любой службы, которая использует службу регистрации по умолчанию. Отключение всего этого было бы идеальным для команд - поскольку профилировщик там еще не используется.

Вот что вы можете сделать, чтобы отключить регистраторы с интенсивным использованием памяти, сохраняя профилирование включенным в других частях 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);
}
0 голосов
/ 20 ноября 2016

Я только что опубликовал несколько советов по использованию консольных команд Symfony с Doctrine для пакетной обработки здесь .

0 голосов
/ 22 марта 2013

Попробуйте отключить любые существующие кеши Doctrine. (Если вы не используете APC / other в качестве кэша, тогда используется память).

Удалить кэш запросов

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

Нет способа глобально отключить его

Также это альтернатива очистке, которая может помочь (с здесь )

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