Недавно я реализовал функции API в проекте с бэк-эндом Symfony2, использущем Doctrine в качестве ORM.

И, как это иногда бывает, скороость отрабатывания не вполне меня устроила. На несложный запрос ответ генерировался аж 7,2 сек.



Первое предположение, где-то запускаются какие-то тяжелые запросы. Но через профайлер не видно ничего такого, что могло бы
навести на мысль о том, где скрылась проблема. SQL-запросов многовато, но все быстрые, и общее их время выполнения всего 223 милисекунды.





Иду в закладку Timeline — видно, что проблема возникает во время работы контроллера (там буквально 5 строк), но где именно — яснее не стало.



Что оказалось в итоге.

Оказалось, что для генерации данных некоторые модели (в глубине давно написанных сервисов) требовали связанные one-to-many объекты. Объект нужен был один, но для доступа к нему поднимался весь ArrayCollection из сотен объектов.

Т.е. некоторые из тех быстрых запросов возвращали сотни строк, на основе которых Doctrine поднимал сотни объектов, и на это уходила уйма ресурсов.

Проблема эта решилась просто. Немного скорректировав логику моделей, к моменту обращения к связанным объектам я их уже подготавливал. Время работы стало приемлимым. Но проблема, при этом, как мне кажется, может быть достаточно типичной и возникать часто в проектах, связанных с Doctrine. Нужен один привязанный объект, инициализируются (не слишком заметно для разработчика) все.

Хорошо бы все-таки это как-то контролировать.

Поэтому я сделал бандл, который собирает данные о времени гидрации объектов внутри Doctrine.

Думаю, может быть полезным для любого проекта, использующего связку Symfony2+Doctrine2.
debesha/DoctrineProfileExtraBundle (гитхаб)

После установки бандла в панели профайлера появляется дополнительный бадж, в котором видно, сколько было выполнено гидраций и сколько на это было потрачено времени:



Соответственно, профайлер в примере, который я привел в начале, выглядит уже более красноречиво — на гидрацию объектов потрачено 5,5 секунд.



И тоже самое, но после оптимизации (которая, как я уже сказал, была очень простой. Самое сложное было локализовать проблему).
Гидрация заняла 0.4 секунды (на моем медленном рабочем компе).



Надеюсь своим бандлом я сэкономлю кому-то из коллег кучу времени.

Комментарии (11)


  1. FractalizeR
    20.07.2015 18:52

    Оказалось, что для генерации данных некоторые модели (в глубине давно написанных сервисов) требовали связанные one-to-many объекты. Объект нужен был один, но для доступа к нему поднимался весь ArrayCollection из сотен объектов.


    Я так понимаю, что помимо медленной гидрации (проблема со стороны Doctrine) это еще и ошибка в логике работы проекта, верно? Вместо того, чтобы выбирать только необходимые связанные объекты DQL запросом, выбирались все записи. Или по-другому было нельзя?


    1. debesha Автор
      20.07.2015 19:07
      +1

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

      Но не то, что выбирались все объекты, а не выбирались вообще ни одного. И доктрин из-за этого уже сам вытаскивал их всех.

      Т.е. было обращение, допуcтим,
      $record = $entity->getRecordByKey($key);

      внутри которой было

      public funtion getRecordByKey($key) {return $this->records[$key]; }

      Если нужный record к моменту вызова getRecordByKey не был готов, но доктрин лез и выбирал все связанные records из базы.

      И это правильно. К Доктрин претензий нет, и отработал он отлично. Но при этом такую ошибку допутить легко (ну не знаю, обращение к null-объекту у всех же случаяются периодически), а отладить сложно. Стандартный профайлер не помогает никак. А мой бандл «проявляет» ошибку, если можно так сказать.


      1. FractalizeR
        21.07.2015 10:06

        Понятно, спасибо.


      1. skobkin
        27.07.2015 01:42
        +1

        Но ведь у коллекций (а у вас там должен быть ArrayCollection/PersistedCollection) есть и специальные фильтры, и методы для получения элементов по нужному индексу, срезки элементов, их подсчета. И EXTRA_LAZY режим.
        Можно включить его и тогда получение по индексу не будет вызывать гидрации всех связанных объектов. То есть, да, тут неверное использование ORM.
        А профайлер гидрации — это полезно, да.


  1. to0n1
    21.07.2015 11:29
    +2

    Думаю в виде отдельного бандла мало кто будет использовать ваше творение. ПР в доктрину был бы куда полезнее


    1. debesha Автор
      21.07.2015 11:33
      +2

      Да, я хочу попробовать это сделать.

      Но, во-первых, не факт, что отцы-основатели посчитают это достойной мастера фичей.
      Во-вторых, это требует поэтапного внедрения — сначала в doctrine/orm, и, после того, как это уйдет в стабильный мастер, уже в doctrine/doctrine-bundle. И это точно произойдет небыстро.

      А бандл уже готов и уже работает.


  1. gaelpa
    21.07.2015 12:35
    +1

    А в packagist.org не хотите свой бандл добавить?


    1. debesha Автор
      21.07.2015 12:37
      +2

      Он уже там.

      Я не стал это упоминать, но в инструкция по установке в гитхабе использует composer, т.е. это значит что бандл уже в пакаджисте.


      1. gaelpa
        21.07.2015 12:43
        +1

        Оу, действительно. Я слепой, а название бандла не совпадает с названием композер-пакета.


  1. Strate
    27.07.2015 22:07

    Респектище вам за бандл!


    1. debesha Автор
      28.07.2015 09:32

      спасибо