Pull to refresh

ORM – зло или Как я пытался кэшировать Propel в Symfony

Symfony *
Работая над одним проектом (соц. сетью) передо мной встала задача «подружить» модель данных с memcache. Как Вы уже поняли из заголовка, проект написан на symfony framework, а в качестве ORM используется Propel.

Зачем кэшировать модель, спросите вы, если можно просто использовать кэш View?
Как показала практика (сначала был использован именно кэш view) это оказалось не целесообразно. При каждом изменении данных пользователем необходимо было сбрасывать кэш сразу нескольких закэшированных блоков. Количество этих блоков с развитием проекта растет, логика усложняется. Чтобы разгрузить мозг и избежать ошибок, и было принято решение кэшировать модель.
 
Как оказалось Propel вообще не предназначен для кэширования, и при отключенном кэша-view генерирует ОЧЕНЬ большое количество запросов.
Мой рекорд 250!!! Почему спросите вы? Потому что Propel интересным образом реализует связи между объектами. Вместо того чтобы брать объект из единой точки ( например методом retriveByPk ), он делает запрос в базу данных!!! А это значит, что если у нас есть объекты юзера, его фоток, его записей из блога, то с каждого объекта мы получаем запрос в базу данных. Есть у нас, например, в выборке 20 фоток – получи 20 запросов в базу, чтобы узнать логин автора фотки. А если у фотки есть еще Альбом? То 40. А если это главная страница, на которой есть фотки, записей из блога и еще много чего?! Все 250 :)
 
Что интересно в Propel’e об этом предупреждают :)
Вот пример:
public function getsfGuardUser(PropelPDO $con = null)
  {
    if ($this->asfGuardUser === null && ($this->user_id !== null)) {
      $c = new Criteria(sfGuardUserPeer::DATABASE_NAME);
      $c->add(sfGuardUserPeer::ID, $this->user_id);
      $this->asfGuardUser = sfGuardUserPeer::doSelectOne($c, $con);
      /* The following can be used additionally to
        guarantee the related object contains a reference
        to this object. This level of coupling may, however, be
        undesirable since it could result in an only partially populated collection
        in the referenced object.
        $this->asfGuardUser->addPhotos($this);
       */
    }
    return $this->asfGuardUser;
  }

* This source code was highlighted with Source Code Highlighter.

А можно всего-лишь:
 public function getsfGuardUser(PropelPDO $con = null)
 {
  return sfGuardUserPeer::retrieveByPK($this->getUserId(), $con);
 }

* This source code was highlighted with Source Code Highlighter.

 
Таким вот простым переопределением можно избавиться от «лишних» запросов.
Правда, если связей много, придется изрядно покопаться в коде.
 
Теперь перейдем непосредственно к кэшированию. Хотелось, чтобы после вызова метода retrieveByPK возвращался объект из кэша, если он там есть, а если нет, то брался из базы и сохранялся в кэш. Что самое интересное, в propel есть пул загруженных объектов. Этот пул всего лишь временное хранилище.
Вот пример генерируемых методов для модели юзера.
public static function addInstanceToPool(sfGuardUser $obj, $key = null);
public static function removeInstanceFromPool($value);
public static function getInstanceFromPool($key);
public static function clearInstancePool();

* This source code was highlighted with Source Code Highlighter.

 
И если переопределить эти методы таким образом, чтобы объекты, попадающие в пул одновременно и кэшировались в memcache, то решится задача кэширования объектов модели. Правда, это не изящное решение, но в Propel объекты модели изолированы друг от друга. И если и можно что-то сделать, то на более низком уровне, уровне frameworka. А значит, что такие изменения необходимо учитывать при обновлении symfony, что очень неудобно.

Propel не стоит использовать для построения крупных и сложных веб-проектов.

UPD
Join'ы не подходят, т.к. много join'в использовать не рекомендуется, это доп нагрузка на базу, т.к.запрос сложнее. При большой нагрузке это существенно. А вот если все делать простыми запросами и связи дергать по праймари ключам, то все встает на свои места. Пример. Выборка 20 фоток, у них есть альбомы, авторы, аватарки авторов, аватарки альбомов. С join'и будет запрос с соеденением 5 таблиц!!! А если таких воборок 5 на странице!? А в моем случае именно так!

UPD2
Внимательно читайте статью ))

UPD3
Продолжение истории
Как я подружил «memcache» и Propel в Symfony
Tags:
Hubs:
Total votes 20: ↑10 and ↓10 0
Views 2.3K
Comments Comments 16