Upload
ilyas-salikhov
View
190
Download
6
Embed Size (px)
Citation preview
О докладчике
Ильяс Салихов
• Интаро – веб-интегратор http://intaro.ru
• retailCRM – cпециализированная CRM-система
для интернет-торговли http://www.retailcrm.ru
2
Инструменты кеширования в Doctrine
Doctrine предоставляет:
1. Компонент doctrine/cache с большим
набором драйверов
2. 3 уровня кеширования в ORM
3. First-level-cache + Second-level-cache (с
Doctrine >=2.5)
Очумелые ручки:
4. Taggable caching
3
3 уровня кеширования
1. Metadata Cache
• Описание модели данных
2. Query Cache
• Результирующий SQL
3. Result Cache
• Результаты SQL-запроса
4
Metadata и Query Cache
1. Снижают нагрузку на app-, но не db-
сервера
2. Занимают мало места
3. Много запросов в кеш
4. Простота и удобство использования
Можно целиком очищать и перегенерировать
4. Всегда включаем в production
6
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
Metadata и Query Cache
В CLI нет APC, поэтому приходится
держать в memcache/redis/…
Как совместить разные конфигурации
кеша?
8
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
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
Автосброс 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
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
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
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
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
Классический Result Cache
Для чего кешировать запросы:
1. Неэффективность HTTP-кеша при
большой плотности данных на странице
(бизнес-приложения)
2. Недопустимость отображения данных из
устаревшего кеша
3. Снижение нагрузки БД
18
Result Cache
Cотни и тысячи разных выборок данных из
разных таблиц
Классическая проблема сброса кеша при
изменении данных
19
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
Taggable Cache
Первый подход к снаряду:
memcached-tags
https://code.google.com/p/memcached-tags/
(форк memcached)
22
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
Taggable Cache: memcached-tags
Плюсы:
1. Нативные методы в memcached
Минусы:
1. Зависимость от нестандартной сборки
2. Недостаточно стабильная реализация
3. Проект не поддерживается
24
Taggable Cache: Redis
memcached:
• Простое key-value хранилище, где
value – только строка
Redis:
• Кроме значений типа strings
предоставляет набор разных типов
данных: lists, sets, hashes, sorted sets и др.
26
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
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
Taggable Cache: принцип работы
<?php $tag = 'tag1'; /** * CAS-behavior */ $redis->watch($tag); $keys = $redis->sMembers($tag); $redis->multi() ->delete($tag) // удаляем тега ->delete($keys) // уделяем ключей кеша по тегу ->exec();
Удаление кеша по тегу
29
Taggable Cache: чем пришлось жертвовать
Свой форк Doctrine2…, который отличается
двумя строчками:
30
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
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; } }
Taggable Cache: чем пришлось жертвовать
Свой форк Doctrine2
+ Бандл Intaro\TaggableCacheBundle
1. Свой CacheDriver с поддержкой тегов
2. Наследованные EntityRepository, Query,
NativeQuery, QueryBuilder с поддержкой тегов
3. Listener для удаления по тегу при
добавлении/изменении записей
33
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
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
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
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
Taggable Cache
Плюсы Taggable Cache:
1. Почти нет ручной работы по сбросу кеша
(только при DELETE или UPDATE на SQL)
2. Всегда актуальный кеш данных
3. В боевом режиме проверено
на 1500 req/s к кешу
38
Taggable Cache
Минусы Taggable Cache:
1. Форк Doctrine2 (хотя в силу малых
изменений легко поддерживается)
2. Есть узкое место по памяти при сбросе
кеша по тегу
~40 mb на 200 тысяч ключей
<?php $tag = 'tag1'; $keys = $redis->sMembers($tag); $redis->delete($keys);
39
Спасибо за внимание.
• 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