40
Расширенное кеширование в Doctrine Ильяс Салихов Intaro, CTO retailCRM, CTO

Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Embed Size (px)

Citation preview

Page 1: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Расширенное кеширование в Doctrine

Ильяс Салихов

Intaro, CTO

retailCRM, CTO

Page 2: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

О докладчике

Ильяс Салихов

• Интаро – веб-интегратор http://intaro.ru

• retailCRM – cпециализированная CRM-система

для интернет-торговли http://www.retailcrm.ru

2

Page 3: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Инструменты кеширования в Doctrine

Doctrine предоставляет:

1. Компонент doctrine/cache с большим

набором драйверов

2. 3 уровня кеширования в ORM

3. First-level-cache + Second-level-cache (с

Doctrine >=2.5)

Очумелые ручки:

4. Taggable caching

3

Page 4: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

3 уровня кеширования

1. Metadata Cache

• Описание модели данных

2. Query Cache

• Результирующий SQL

3. Result Cache

• Результаты SQL-запроса

4

Page 5: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

5

Page 6: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

1. Снижают нагрузку на app-, но не db-

сервера

2. Занимают мало места

3. Много запросов в кеш

4. Простота и удобство использования

Можно целиком очищать и перегенерировать

4. Всегда включаем в production

6

Page 7: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

На странице может легко быть 100+

обращений за metadata и query

Держим в APC На порядок быстрее, чем в memcached, особенно,

если memcached на другой машине

doctrine: orm: entity_managers: default: metadata_cache_driver: type: apc query_cache_driver: type: apc 7

Page 8: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

В CLI нет APC, поэтому приходится

держать в memcache/redis/…

Как совместить разные конфигурации

кеша?

8

Page 9: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

Разные конфигурации для cli и web

Вариант 1. Разные env

# file app/config/config_prod.yml doctrine: orm: entity_managers: default: metadata_cache_driver: type: apc query_cache_driver: type: apc # file app/config/config_cli.yml doctrine: orm: entity_managers: default: metadata_cache_driver: type: memcache host: 127.0.0.1 query_cache_driver: type: memcache host: 127.0.0.1 9

Page 10: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Metadata и Query Cache

Разные конфигурации для cli и web

Вариант 2. Разнести выполнение cli и web на разные сервера

# file app/config/parameters.yml parameters: doctrine_cache.meta.type: apc # or memcache doctrine_cache.query.type: apc # or memcache memcache.host: 127.0.0.1 # file app/config/config_prod.yml doctrine: orm: entity_managers: default: metadata_cache_driver: type: %doctrine_cache.meta.type% host: %memcache.host% query_cache_driver: type: %doctrine_cache.query.type% host: %memcache.host%

10

Page 11: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Автосброс Metadata и Query Cache

<?php class AppKernel extends Kernel { public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function ($container) { $environment = $container->getParameter('kernel.root_dir') . $container->getParameter('kernel.environment'); $key = 'apc_actual_check_' . hash('sha256', $environment); $value = $container->getParameter('cache.prefix'); if (apc_fetch($key) !== $value) { apc_clear_cache(); apc_clear_cache('user'); apc_store($key, $value); } }); } }

1. Заводим параметр cache.prefix в parameters.yml

2. Изменяем его в момент деплоя (например, текущая дата-время)

11

Page 12: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

First-level cache & Second-level cache

12

Page 13: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

First-level cache

Кеширует объекты в памяти в рамках

одного Request

<?php // будут выполнены запросы в БД $city = $em->getRepository('AppBundle\Entity\City')->find(5); $country = $em->getRepository('AppBundle\Entity\Country')->find(1); // НЕ будет запроса в БД, first-level-cache $country = $em->getRepository('AppBundle\Entity\Country')->find(1); $city->getCountry();

13

Page 14: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Second-level cache (since Doctrine 2.5)

Request 1

Request 2

<?php // запросы в БД $city = $em->getRepository('AppBundle\Entity\City')->find(5); $country = $em->getRepository('AppBundle\Entity\Country')->find(1); // НЕ будет запроса в БД, first-level-cache $country = $em->getRepository('AppBundle\Entity\Country')->find(1); $city->getCountry();

<?php // НЕ будет запроса в БД, second-level-cache $city = $em->getRepository('AppBundle\Entity\City')->find(5); $country = $em->getRepository('AppBundle\Entity\Country')->find(1); // НЕ будет запроса в БД, first-level-cache $country = $em->getRepository('AppBundle\Entity\Country')->find(1); $city->getCountry();

14

Page 15: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Second-level cache (since Doctrine 2.5)

Плюсы:

1. Меньше обращений к БД

2. Кеширование Lazy Fetch методов

НЕ НАДО рассчитывать на second-level cache для

Lazy Fetch. Выбирайте связные данные заранее.

// Request 1 // --------- $city = $em->getRepository('AppBundle\Entity\City')->find(5); // Будет запрос в БД $city->getCountry(); // Request 2 // --------- $city = $em->getRepository('AppBundle\Entity\City')->find(5); // НЕ будет запроса в БД, second-level-cache $city->getCountry();

15

Page 16: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Second-level cache (since Doctrine 2.5)

Минусы:

1. Большое количество запросов в кеш вместо

единоразовой выборки коллекции

<?php // Выполняет запрос (выбирает 100 записей), // сохраняет кеш запроса и entity cache $result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name') ->setCacheable(true) ->getResult(); $em->clear(); // 1 запрос в кеш за result-cache + 100 запросов в second-level cache // за объектами $result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name') ->setCacheable(true) ->getResult();

16

Page 17: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Result Cache

17

Page 18: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Классический Result Cache

Для чего кешировать запросы:

1. Неэффективность HTTP-кеша при

большой плотности данных на странице

(бизнес-приложения)

2. Недопустимость отображения данных из

устаревшего кеша

3. Снижение нагрузки БД

18

Page 19: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Result Cache

Cотни и тысячи разных выборок данных из

разных таблиц

Классическая проблема сброса кеша при

изменении данных

19

Page 20: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache

20

Page 21: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache. Принцип работы

Хранение кеша

21

cache1

tag1, tag2, tag3

cache2

tag2, tag4

cache3

tag3, tag5

cache4

tag1, tag5

Удаление кеша по тегу tag3

cache1

tag1, tag2, tag3

cache2

tag2, tag4

cache3

tag3, tag5

cache4

tag1, tag5

Page 22: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache

Первый подход к снаряду:

memcached-tags

https://code.google.com/p/memcached-tags/

(форк memcached)

22

Page 23: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: memcached-tags

Пример использования: <?php $memcache = new Memcache(); $memcache->connect("127.0.0.1", 11211); $memcache->set("key1", "val1"); $memcache->set("key2", "val2"); $memcache->tag_add("tag_1", ["key1", "key2"]); $memcache->tag_add("tag_2", "key1"); // будет удален кеш key1 $memcache->tag_delete("tag_2");

23

Page 24: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: memcached-tags

Плюсы:

1. Нативные методы в memcached

Минусы:

1. Зависимость от нестандартной сборки

2. Недостаточно стабильная реализация

3. Проект не поддерживается

24

Page 25: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache

Второй подход к снаряду:

Redis + форк Doctrine2

http://redis.io

25

Page 26: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: Redis

memcached:

• Простое key-value хранилище, где

value – только строка

Redis:

• Кроме значений типа strings

предоставляет набор разных типов

данных: lists, sets, hashes, sorted sets и др.

26

Page 27: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: принцип работы

cache1

cache2

cacheN

Query result cache Redis Strings

Сache tags Redis Sets

Tag1 - cache1 - cache2

Tag2 - cache2 - cacheN

В Redis есть нужные для реализации методы:

• sAdd – добавление элемента в коллекцию (пометить кеш

тегом)

• sMembers – получение элементов коллекции (ключи

кешей, помеченных тегом)

• delete – поддержка мультиудаления за один запрос 27

Page 28: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: принцип работы

<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $key = 'hash_123sf1'; $data = [/* коллекция элементов */]; $tags = ['tag1', 'tag2']; // сохраняем данные $redis->set($key, $data); // фиксируем, что эти данные помечены тегами foreach ($tags as $tag) { $redis->sAdd($tag, $key); }

Сохранение кеша

28

Page 29: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: принцип работы

<?php $tag = 'tag1'; /** * CAS-behavior */ $redis->watch($tag); $keys = $redis->sMembers($tag); $redis->multi() ->delete($tag) // удаляем тега ->delete($keys) // уделяем ключей кеша по тегу ->exec();

Удаление кеша по тегу

29

Page 30: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: чем пришлось жертвовать

Свой форк Doctrine2…, который отличается

двумя строчками:

30

Page 31: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: чем пришлось жертвовать

{ "require": { "doctrine/dbal": "2.5.0", "doctrine/common": "2.5.0", "doctrine/orm": "dev-final-keyword-fix-25 as 2.5.0", // ... }, "repositories": [ { "type": "vcs", "url": "https://github.com/retailcrm/doctrine2" } ] }

Свой форк Doctrine2

31

Page 32: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: чем пришлось жертвовать

Для чего форкали

32

<?php class Query extends \Doctrine\ORM\Query { protected function _doExecute() { //... // выполнение запроса и кеширование результатов $result = parent::_doExecute(); // помечаем кеш результатов тегами if ($queryCacheDriver && $queryCacheDriver instanceof TaggableCache) { if ($tags = $this->getCacheTags()) { $queryCacheDriver->tagAdd($tags, $queryId); } } return $result; } }

Page 33: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: чем пришлось жертвовать

Свой форк Doctrine2

+ Бандл Intaro\TaggableCacheBundle

1. Свой CacheDriver с поддержкой тегов

2. Наследованные EntityRepository, Query,

NativeQuery, QueryBuilder с поддержкой тегов

3. Listener для удаления по тегу при

добавлении/изменении записей

33

Page 34: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: выборка через QueryBuilder

<?php namespace Intaro\CRMBundle\Entity\Repository; use Intaro\TaggableCacheBundle\Doctrine\ORM\EntityRepository; class OrderRepository extends EntityRepository { public function getCustomerLastOrder(Customer $customer) { $qb = $this->createQueryBuilder('o') ->select('o, s') ->leftJoin('o.status', 's') ->where('o.customer = :customer') ->setParameter('customer', $customer) ->orderBy('o.id', 'DESC') ->setMaxResults(1) ->addCacheTags([ Order::class, Status::class, ]) ; return $qb->getQuery()->getOneOrNullResult(); } } 34

Page 35: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: выборка через NativeQuery

<?php use Intaro\TaggableCacheBundle\Doctrine\ORM\NativeQuery; use Doctrine\ORM\Query\ResultSetMapping; $rsm = new ResultSetMapping(); $rsm->addScalarResult('cnt', 'cnt'); $q = new NativeQuery($em); $q->setResultSetMapping($rsm) ;$q->setSql(' SELECT COUNT(o.id) AS cnt FROM i_crm_order o WHERE fetchval(o.custom_fields, ?) <= ? '); $q->setParameters(['date_of_sending', '2015-07-01']); $q->addCacheTag(Order::class); $count = $q->getSingleScalarResult();

35

Page 36: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: сброс кеша, изменение записей

<?php $order = new Order(); $em->persist($order); // здесь будет выполнен сброс кеша запросов с тегом Order $em->flush(); $order2 = $em->getRepository(Order::class)->find(5); $order2->setPhone('+7926-123-45-67'); // здесь тоже будет выполнен сброс кеша запросов с тегом Order $em->flush();

Сброс при добавлении или изменении записей

36

Page 37: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache: сброс кеша, массовое обновление

<?php use Intaro\TaggableCacheBundle\Doctrine\ORM\EntityRepository; class OrderRepository extends EntityRepository { public function updateIndexNumber() { $q = ' WITH calc_data (id, n) AS ( -- ... ) UPDATE i_crm_order o SET index_number = cd.n FROM calc_data as cd WHERE o.id = cd.id '; $this->getEntityManager()->getConnection()->executeUpdate($q); // сбрасываем кеш запросов с тегом Order $this->clearEntityCache(); } }

37

Page 38: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache

Плюсы Taggable Cache:

1. Почти нет ручной работы по сбросу кеша

(только при DELETE или UPDATE на SQL)

2. Всегда актуальный кеш данных

3. В боевом режиме проверено

на 1500 req/s к кешу

38

Page 39: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Taggable Cache

Минусы Taggable Cache:

1. Форк Doctrine2 (хотя в силу малых

изменений легко поддерживается)

2. Есть узкое место по памяти при сбросе

кеша по тегу

~40 mb на 200 тысяч ключей

<?php $tag = 'tag1'; $keys = $redis->sMembers($tag); $redis->delete($keys);

39

Page 40: Расширенное кеширование Doctrine2 (Ильяс Салихов, Intaro)

Спасибо за внимание.

• http://docs.doctrine-project.org/projects/doctrine-

orm/en/latest/reference/caching.html

• http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-

level-cache.html

• https://github.com/retailcrm/doctrine2

• https://code.google.com/p/memcached-tags/

• http://redis.io

• http://smira.ru/posts/20081029web-caching-memcached-5.html

Мои контакты:

twitter.com/salikhov

github.com/muxx

habrahabr.ru/users/muxx/

40