Утечки памяти Symfony2 Doctrine2 / превышают лимит памяти

42

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

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

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

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

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

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

Есть ли простой способ отклонить некоторые объекты из Entitymanagerstorage? Есть ли способ установить использование querycache в entitymanager? Могу ли я настроить такое поведение кэширования в конфигурации доктрины symonfony?

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

суа

  • 2
    Слой D2 ORM не предназначен для массовой пакетной обработки. Возможно, вам лучше использовать слой DBAL и просто работать с массивами.
  • 1
    Запуск с --no-debug очень помогает (в режиме отладки профилировщик сохраняет информацию о каждом запросе в памяти)
Теги:
orm
memory-leaks
doctrine2

8 ответов

87

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

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

$em->getConnection()->getConfiguration()->setSQLLogger(null);
  • 4
    Браво! Мы месяцами били себя по этому поводу!
  • 3
    Это вещь с SF2. Вам действительно нужно прочитать документы и код, чтобы понять, как это работает. На днях мы обнаружили, что мы не кешируем DQL и метаданные между запросами. Мы сделали это и получили запросы в два раза быстрее, чем до изменения
Показать ещё 14 комментариев
17

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

  • 0
    Спасибо это в сочетании с отключением sqllogging действительно помогло
  • 0
    это решило проблему с движком шаблонов и веткой. Выполнение цикла над простым шаблоном, казалось, было утечкой памяти в процессе разработки.
Показать ещё 1 комментарий
9
  • Установите логгер 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 вариант, если вы запустите команду с консоли.
8

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

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

или в коде

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

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

Сначала включить управление ручной сборкой мусора. Запустите 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

3

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

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

Интересным побочным эффектом является то, что регистраторы влияют как на ORM ORD, так и на 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 a handler
     * available to pop off except for the \LogicException that 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

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

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

  • 0
    мы, вероятно, тоже должны идти по этому пути ... это отстой, что, хотя они об этом знают, это не решается
0

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

0

Попробуйте отключить любые существующие кэширование 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();
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню