Перейти к содержанию

Примеры. Кешируем правильно

Рассмотрим примеры, как можно улучшить производительность проекта.

Кешируем правильно

Кеш очень важная составляющая производительности проектов. Чем эффективнее кешируем, чем кеш легче и быстрее, тем меньше ресурсов надо на поддержание проекта.

Но работа кеша может быть организована не совсем корректно, что приводит к проблемам производительности.

Комплексный пример: класс кеширования пользователя

Как неправильно:

// Класс кеширования пользователя с некорретным кешированием
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. Общая папка для кеша пользователей : Такой пример кеша практически не работает на проекте. На портале, где много пользователей, мы постоянно будем терять кеш

    Большое количество хитов попадает в том момент, когда кеш уже скинут. Происходит это из-за того, что кеш пользователей уникальный на каждого человека и его хотелось бы скидывать отдельно. В примере указана общая папка для кеша, а не отдельная для пользователя. Следовательно, когда мы хотим скинуть кеш для пользователя, мы скидываем его его для всех. , что негативно отразиться на его работе.

  2. Время кеширования : Кеширование сущности в примере на 1 час - неэффективно. Сохранение на малое время может привести к массовой генерации кеша

    Простой пример: сотрудники ушли на обед и вернулись через час, а кеш уже у всех сброшен. . Такая проблема актуальна для больших компаний и проектов.

  3. Заполнение пользователя методом GetByID : В кеш попадает большой объем данных о пользователе, а именно много ненужной информации (пароль, подтверждение пароля, настройки синхронизации и т.д.).

  4. Использование 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);
}

Отключаем сброс ключей

Процесс импорта обычно приводит к сбросу кеша. Если мы выгружаем большой объем данных - это занимает продолжительное время и дает большую нагрузку на боевом проекте. В ряде случаев этого можно избежать. В частности при работе с инфоблоками:

  1. Отключите кеширование элементов (сбрасывание тегированного кеша) перед импортом;
  2. Включите его по окончании процесса импорта элементов;
  3. Сбросьте те теги инфоблока, которые сбрасывались в данном случае.

В таком вариант кеш сбросится один раз после полной загрузки, а не после загрузки каждого элемента.

Индексация фасетного индекса также может быть отложена (т.е. отключена перед импортом и включена по окончании).

<?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 очистки не произойдет. Делаем вручную после изменения:

if(defined('BX_COMP_MANAGED_CACHE'))
   $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);