Примеры. Кешируем правильно
Рассмотрим примеры, как можно улучшить производительность проекта.
Кешируем правильно
- Комплексный пример: класс кеширования пользователя;
- Время кеширования (cache key and ttl);
- Как в API правильно подставлять ключи;
- Отключаем сброс ключей;
- Итоги рекомендаций по кешированию.
Кеш очень важная составляющая производительности проектов. Чем эффективнее кешируем, чем кеш легче и быстрее, тем меньше ресурсов надо на поддержание проекта.
Но работа кеша может быть организована не совсем корректно, что приводит к проблемам производительности.
Комплексный пример: класс кеширования пользователя
Как неправильно:
// Класс кеширования пользователя с некорретным кешированием
class User
{
private $userData;
function __construct($userId = false)
{
if (!$userId)
{
global $USER;
$userId = $USER->GetID();
}
$cntStartCacheId = __CLASS__.'::'.__FUNCTION__.'|'.SITE_ID.'|'.$userId;
$cache = new \CXxxCache($cntStartCacheId.'sid0',3600,'user_data');
$this->userData = $cache->Init();
if (null == $this->userData)
{
$this->putUserData(array("ID"=>$userId));
$this->putUserData(\CUser::GetByID($userId)->Fetch());
$this->putUserData(array("DEPARTMENT" => $this->getDepartment()));
$cache->registerTag('USER_NAME_'.$userId);
$cache->set($this->userData);
}
}
}
Что не так в примере выше:
-
Общая папка для кеша пользователей : Такой пример кеша практически не работает на проекте. На портале, где много пользователей, мы постоянно будем терять кеш
Большое количество хитов попадает в том момент, когда кеш уже скинут. Происходит это из-за того, что кеш пользователей уникальный на каждого человека и его хотелось бы скидывать отдельно. В примере указана общая папка для кеша, а не отдельная для пользователя. Следовательно, когда мы хотим скинуть кеш для пользователя, мы скидываем его его для всех. , что негативно отразиться на его работе.
-
Время кеширования : Кеширование сущности в примере на 1 час - неэффективно. Сохранение на малое время может привести к массовой генерации кеша
Простой пример: сотрудники ушли на обед и вернулись через час, а кеш уже у всех сброшен. . Такая проблема актуальна для больших компаний и проектов.
-
Заполнение пользователя методом
GetByID: В кеш попадает большой объем данных о пользователе, а именно много ненужной информации (пароль, подтверждение пароля, настройки синхронизации и т.д.). -
Использование
Fetch: При использовании Fetch данные не экранируются и никак не проверяются, что приводит к ошибкам безопасности.
Как правильно:
GetID();
}
$cntStartCacheId = __CLASS__.'::'.__FUNCTION__.'|'.SITE_ID.'|'.$userId;
$cache = new \CXxxCache(
$cntStartCacheId.'sid0',
// увеличили время кеширования
604800,
// путь для ключей кеша сделали зависимым от $userID
'user_data/'.substr(md5($userId),2,2).'/'.$userId
);
$this->userData = $cache->Init();
if (null == $this->userData)
{
$this->putUserData(array("ID"=>$userId));
// Выбираем только нужные поля
$this->putUserData(\CUser::GetList(...)->GetNext(true, false));
$this->putUserData(array("DEPARTMENT" => $this->getDepartment()));
$cache->registerTag('USER_NAME_'.$userId);
$cache->set($this->userData);
}
}
}
- Увеличили время кеширования до недели;
- Изменили папку хранения кеша (разложили персонализированный кеш по подпапкам);
- Изменили сбор информации по пользователю с
GetByIDнаGetList
Достаточно часто в проектах многие используют вызов CIBlockElement::GetById. Простой, удобный метод, когда надо вытащить какое-то поле для элемента инфоблока. Но этот метод тянет все поля и все свойства элемента. В случае инфоблока с большим количеством свойств и большого числа посетителей на сайте этот простой запрос приводит к снижению производительности.
Подробнее...
;
- Заменили Fetch на GetNext(true, false).
Время кеширования (cache key and ttl)
Пример на компоненте, который отображает дни рождения. Установлено большое время кеширования и добавлен дополнительный параметр, который сам компонент никак не отрабатывает и его не обрабатывает шаблон. Но так как в параметры подставлен date, то в 0 часов получим новый ключ у кеша данного компонента.
IncludeComponent(
"bitrix:intranet.structure.birthday.nearest",
"widget",
Array(
"CACHE_TYPE" => "A",
"CACHE_TIME" => "86450",
"DATE_FORMAT" => "j F",
"DETAIL_URL" => "#SITE_DIR#company/personal/user/#USER_ID#/",
"DEPARTMENT" => "0",
.....
"CACHE_DATE" => date('dmy')
)
);
Пример: как в API правильно подставлять ключи
В таком случае часто проставляют лишние параметры, что приводит к увеличению кеша. (например date без параметров приводит к обновлению кеша каждую секунду).
<?php
// Пример добавление в ключ кеша метки времени для корректного переключения кеша. Метка может быть и не из времени.
$cache = Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(86450, '/some_key/'.date('myd').'/', '/some_dir/'))
{
$var = $cache->getVars();
}
else
{
// Получение данных
$cache->startDataCache();
$cache->endDataCache($var);
}
Отключаем сброс ключей
Процесс импорта обычно приводит к сбросу кеша. Если мы выгружаем большой объем данных - это занимает продолжительное время и дает большую нагрузку на боевом проекте. В ряде случаев этого можно избежать. В частности при работе с инфоблоками:
- Отключите кеширование элементов (сбрасывание тегированного кеша) перед импортом;
- Включите его по окончании процесса импорта элементов;
- Сбросьте те теги инфоблока, которые сбрасывались в данном случае.
В таком вариант кеш сбросится один раз после полной загрузки, а не после загрузки каждого элемента.
Индексация фасетного индекса также может быть отложена (т.е. отключена перед импортом и включена по окончании).
<?php
// Отключение сброса тегированного кеша инфоблоков и пересчета фасетного индекса, во время импорта.
Bitrix\Iblock\PropertyIndex\Manager::enableDeferredIndexing();
Bitrix\Catalog\Product\Sku::enableDeferredCalculation();
\CAllIBlock::disableTagCache($iblockID);
// Импорт элементов
\CAllIBlock::enableTagCache($iblockID);
\CAllIBlock::clearIblockTagCache($iblockID);
Bitrix\Catalog\Product\Sku::disableDeferredCalculation();
Bitrix\Catalog\Product\Sku::calculate();
Bitrix\Iblock\PropertyIndex\Manager::disableDeferredIndexing();
Bitrix\Iblock\PropertyIndex\Manager::runDeferredIndexing($iblockID);
Т.о. улучшение производительности достигается обновлением индексов и кеша только один раз, а не по количеству элементов.
Итоги рекомендаций по кешированию
| Хорошо | Плохо | --- |
Внимание! Не забывайте, что управляемый кеш инфоблоков очищается только при вызове CIBlockElement::Update. При изменении, например, свойств с помощью CIBlockElement::SetPropertyValueCode очистки не произойдет. Делаем вручную после изменения: