6. Metadata и Query Cache
1. Снижают нагрузку на app-, но не db-
сервера
2. Занимают мало места
3. Много запросов в кеш
4. Простота и удобство использования
Можно целиком очищать и перегенерировать
4. Всегда включаем в production
6
7. 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
8. Metadata и Query Cache
В CLI нет APC, поэтому приходится
держать в memcache/redis/…
Как совместить разные конфигурации
кеша?
8
9. 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
10. 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
11. Автосброс 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
13. First-level cache
Кеширует объекты в памяти в рамках
одного Request
<?php
// будут выполнены запросы в БД
$city = $em->getRepository('AppBundleEntityCity')->find(5);
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
// НЕ будет запроса в БД, first-level-cache
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
$city->getCountry();
13
14. Second-level cache (since Doctrine 2.5)
Request 1
Request 2
<?php
// запросы в БД
$city = $em->getRepository('AppBundleEntityCity')->find(5);
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
// НЕ будет запроса в БД, first-level-cache
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
$city->getCountry();
<?php
// НЕ будет запроса в БД, second-level-cache
$city = $em->getRepository('AppBundleEntityCity')->find(5);
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
// НЕ будет запроса в БД, first-level-cache
$country = $em->getRepository('AppBundleEntityCountry')->find(1);
$city->getCountry();
14
15. Second-level cache (since Doctrine 2.5)
Плюсы:
1. Меньше обращений к БД
2. Кеширование Lazy Fetch методов
НЕ НАДО рассчитывать на second-level cache для
Lazy Fetch. Выбирайте связные данные заранее.
// Request 1
// ---------
$city = $em->getRepository('AppBundleEntityCity')->find(5);
// Будет запрос в БД
$city->getCountry();
// Request 2
// ---------
$city = $em->getRepository('AppBundleEntityCity')->find(5);
// НЕ будет запроса в БД, second-level-cache
$city->getCountry();
15
16. 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
18. Классический Result Cache
Для чего кешировать запросы:
1. Неэффективность HTTP-кеша при
большой плотности данных на странице
(бизнес-приложения)
2. Недопустимость отображения данных из
устаревшего кеша
3. Снижение нагрузки БД
18
19. Result Cache
Cотни и тысячи разных выборок данных из
разных таблиц
Классическая проблема сброса кеша при
изменении данных
19
22. Taggable Cache
Первый подход к снаряду:
memcached-tags
https://code.google.com/p/memcached-tags/
(форк memcached)
22
23. 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
24. Taggable Cache: memcached-tags
Плюсы:
1. Нативные методы в memcached
Минусы:
1. Зависимость от нестандартной сборки
2. Недостаточно стабильная реализация
3. Проект не поддерживается
24
26. Taggable Cache: Redis
memcached:
• Простое key-value хранилище, где
value – только строка
Redis:
• Кроме значений типа strings
предоставляет набор разных типов
данных: lists, sets, hashes, sorted sets и др.
26
27. 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
28. 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
29. Taggable Cache: принцип работы
<?php
$tag = 'tag1';
/**
* CAS-behavior
*/
$redis->watch($tag);
$keys = $redis->sMembers($tag);
$redis->multi()
->delete($tag) // удаляем тега
->delete($keys) // уделяем ключей кеша по тегу
->exec();
Удаление кеша по тегу
29
30. Taggable Cache: чем пришлось жертвовать
Свой форк Doctrine2…, который отличается
двумя строчками:
30
31. 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
32. Taggable Cache: чем пришлось жертвовать
Для чего форкали
32
<?php
class Query extends DoctrineORMQuery
{
protected function _doExecute()
{
//...
// выполнение запроса и кеширование результатов
$result = parent::_doExecute();
// помечаем кеш результатов тегами
if ($queryCacheDriver && $queryCacheDriver instanceof TaggableCache) {
if ($tags = $this->getCacheTags()) {
$queryCacheDriver->tagAdd($tags, $queryId);
}
}
return $result;
}
}
33. Taggable Cache: чем пришлось жертвовать
Свой форк Doctrine2
+
Бандл IntaroTaggableCacheBundle
1. Свой CacheDriver с поддержкой тегов
2. Наследованные EntityRepository, Query,
NativeQuery, QueryBuilder с поддержкой тегов
3. Listener для удаления по тегу при
добавлении/изменении записей
33
34. Taggable Cache: выборка через QueryBuilder
<?php
namespace IntaroCRMBundleEntityRepository;
use IntaroTaggableCacheBundleDoctrineORMEntityRepository;
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
35. Taggable Cache: выборка через NativeQuery
<?php
use IntaroTaggableCacheBundleDoctrineORMNativeQuery;
use DoctrineORMQueryResultSetMapping;
$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
36. 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
37. Taggable Cache: сброс кеша, массовое обновление
<?php
use IntaroTaggableCacheBundleDoctrineORMEntityRepository;
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
38. Taggable Cache
Плюсы Taggable Cache:
1. Почти нет ручной работы по сбросу кеша
(только при DELETE или UPDATE на SQL)
2. Всегда актуальный кеш данных
3. В боевом режиме проверено
на 1500 req/s к кешу
38
39. Taggable Cache
Минусы Taggable Cache:
1. Форк Doctrine2 (хотя в силу малых
изменений легко поддерживается)
2. Есть узкое место по памяти при сбросе
кеша по тегу
~40 mb на 200 тысяч ключей
<?php
$tag = 'tag1';
$keys = $redis->sMembers($tag);
$redis->delete($keys);
39