Актуально для сайтов с большим каталогом товаров и множеством свойств.
Для ускорения работы умного фильтра в 1С Битрикс в редакциях Малый бизнес и Бизнес есть функционал фасетного индекса. Фасета создается для каждого инфоблока и представляет из себя таблицу в которой хранятся комбинации свойств фильтра. Для каталогов товаров, где эта таблица небольшая(до 1млн. записей) фильтр работает быстро. Проблема возникает при каталогах свыше 100тыс. товаров, и когда большое количество свойств участвует в умном фильтре.
Один из моих клиентов, интернет-магазин по продаже электротоваров, обратился ко мне с проблемой медленной работы сайта в каталоге с фильтром.
Проблема - медленная работа фильтра:
- торговый каталог в 140 тысяч наименований
- более 150 свойств участвует в умном фильтре
- скорость загрузки страницы в режиме отладка: 19 секунд
Было понятно что ускорить сайт можно либо увеличив мощности сервера(это мы также сделали), либо уменьшив таблицу фасеты.
Для уменьшения фасеты было решено две задачи:
Удалить лишние запросы
Умный фильтр битрикса берет все свойства, у которых стоит галочка "Показывать в умном фильтре" и в цикле проверяет есть ли для свойства фасета.
Делает он это так:
В файле component.php компонента bitrix:catalog.smart.filter запрашиваются все данные из фасеты
$res = $this->facet->query($arResult["FACET_FILTER"]);
А потом в цикле выбираются нужные свойства, при этом делается запрос для каждого свойства.
while ($rowData = $res->fetch())
{
...
}
Получается что лишние запросы создаются из-за того что $this->facet->query возвращает свойства, которых нет в текущем разделе.
Чтобы исключить лишние запросы, мы модифицировали фасету.
В class Facet(/bitrix/modules/iblock/lib/propertyindex/facet.php) мы добавили метод GetPropsArray, который вместо битриксовского \CIBlockSectionPropertyLink::getArray возвращал нам массив используемых на странице свойств. Свойство может участвовать в умном фильтре, но для текущего раздела не нужно. Поэтому мы решили в разделах инфоблока с большим количеством элементов указывать какие именно свойства нужны. Для этого завели свойство раздела инфоблока каталога и из него передаем значения в глобальную переменную $UF_F_PROPS.
public static function GetPropsArray($IBLOCK_ID, $SECTION_ID = 0)
{
global $UF_F_PROPS;
global $DB;
foreach($UF_F_PROPS as $key => $val){
$rs = $DB->Query("
SELECT
B.SECTION_PROPERTY,
BP.ID PROPERTY_ID,
BSP.SECTION_ID LINK_ID,
BSP.SMART_FILTER,
BSP.DISPLAY_TYPE,
BSP.DISPLAY_EXPANDED,
BSP.FILTER_HINT,
BP.SORT,
BP.NAME,
BP.CODE,
0 LEFT_MARGIN,
B.NAME LINK_TITLE,
BP.PROPERTY_TYPE,
BP.USER_TYPE,
BP.ACTIVE
FROM
b_iblock B
INNER JOIN b_iblock_property BP ON BP.IBLOCK_ID = B.ID
LEFT JOIN b_iblock_section_property BSP ON BSP.SECTION_ID = 0 AND BSP.PROPERTY_ID = BP.ID
WHERE
B.ID = ".$IBLOCK_ID." AND BP.CODE = \"".$val."\"
ORDER BY
BP.SORT ASC, BP.ID ASC
");
if ($ar = $rs->Fetch())
{
$result[$ar["PROPERTY_ID"]] = array(
"PROPERTY_ID" => $ar["PROPERTY_ID"],
'SMART_FILTER' => 'Y',
//"DISPLAY_TYPE" => 'F',
"SORT" => $ar["SORT"],
"ACTIVE" => $ar["ACTIVE"],
"ID" => $ar["PROPERTY_ID"],
"PROPERTY_ID" => $ar["PROPERTY_ID"],
"IBLOCK_ID" => $IBLOCK_ID,
"CODE" => $ar["CODE"],
"~NAME" => $ar["NAME"],
"NAME" => htmlspecialcharsEx($ar["NAME"]),
"PROPERTY_TYPE" => $ar["PROPERTY_TYPE"],
"USER_TYPE" => $ar["USER_TYPE"],
"USER_TYPE_SETTINGS" => $ar["USER_TYPE_SETTINGS"],
"DISPLAY_TYPE" => $ar["DISPLAY_TYPE"],
"DISPLAY_EXPANDED" => $ar["DISPLAY_EXPANDED"],
"FILTER_HINT" => $ar["FILTER_HINT"],
"VALUES" => array(),
);
}
}
return $result;
}
В методе getSectionFilterProperty($sectionId) класса Facet проверяем установлены ли для раздела свойства.
if(is_array($UF_F_PROPS) && count($UF_F_PROPS)>0){
$r = $this->GetPropsArray($this->iblockId, $sectionId);
}
else{
$r = \CIBlockSectionPropertyLink::getArray($this->iblockId, $sectionId);
}
В результате мы оставляем только данные нужных нам свойств. Это действие дало ускорение фильтрации примерно в 1,5 раза.
Уменьшить размер таблицы фасеты
Время запроса к большой таблице все-равно было большое(около 4 секунд), поэтому мы создали фасеты для отдельных разделов.
Создаем файл facet.php со следующим содержимым:
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
define ('CATALOG_IBLOCK_ID', 13);
define ('DB_NAME', 'sql_ledlus_ru');
// 1. пределим исходные
// CATALOG_IBLOCK_ID - id ИБ
// SECTION_ID - id раздела, для которого создаем таблицу
function createSectionFacet($data, $data1, $db_new, $db_clon_table){
$connection = \Bitrix\Main\Application::getConnection();
$res = $connection->query('SHOW TABLES FROM '.DB_NAME.' LIKE \''.$db_new.'\'');
if ($row = $res->Fetch())
{
$resDel = $connection->query('DROP TABLE '.$db_new.'');
}
$resCreate = $connection->query('CREATE TABLE '.$db_new.' LIKE '.$db_clon_table.'');
$resInsert = $connection->query('INSERT INTO '.$db_new.' SELECT * FROM '.$db_clon_table.' WHERE SECTION_ID=\''.$data.'\' OR SECTION_ID=\''.$data1.'\'');
}
//Получаем массив id разделов, у которых установлено св-во создания фасеты
$arFilter = Array('IBLOCK_ID'=>13, '>DEPTH_LEVEL'=>1);
if(strlen($_REQUEST['fid'])>0){
$arFilter[">ID"] = intval($_REQUEST['fid']);
}
$db_list = CIBlockSection::GetList(array('id'=>'asc'), $arFilter, TRUE, array("IBLOCK_ID", "ID", "UF_CREATE_FACET", 'ELEMENT_CNT',"NAME"));
$i = 0;
if($arSection = $db_list->GetNext())
{
if($arSection['ELEMENT_CNT'] > 2500)
{
$i++;
$data = $arSection["ID"];
$data1 = $arSection["ID"];
$db_clon_table = "b_iblock_13_index";
$db_new = "b_iblock_13_index_".$arSection["ID"];
createSectionFacet($data, $data1, $db_new, $db_clon_table);
}
}
if(intval($arSection['ID'])>0) {
setTimeout(() => {
window.location.href = 'https://ДОМЕН.ru/facet.php?fid==$arSection['ID'];?>';
}, "1000");
}
else{
$db_clon_table = "b_iblock_13_index";
$arFilter = Array('IBLOCK_ID'=>13, 'DEPTH_LEVEL'=>1);
$db_list = CIBlockSection::GetList(array('id'=>'asc'), $arFilter, TRUE, array("IBLOCK_ID", "ID", "UF_CREATE_FACET", 'ELEMENT_CNT',"NAME"));
while($arSection = $db_list->GetNext())
{
$connection = \Bitrix\Main\Application::getConnection();
$resInsert3 = $connection->query('DELETE FROM '.$db_clon_table.' WHERE SECTION_ID=\''.$arSection["ID"].'\' OR SECTION_ID=\''.$arSection["ID"].'\'');
if ($resInsert3)
{
echo "
DELETE SECTION_ID = ".$data;
}
}
}?>
При запуске файла из браузера будет создаваться по одной фасете для каждого раздела, содержащего более 2500 товаров.
Мы модифицировали метод facet->query , в нем переопределили переменную $tableName, возвращающую наименование таблицы.
$tableName = $this->storage->getTableName();
define ('DB_NAME', 'sql_ledlus_ru'); // наименование БД
global $UF_CREATE_FACET; // указывает, нужно ли подменять таблицу(переменная берет значение из свойства раздела)
if($UF_CREATE_FACET == 1)
{
$res = $connection->query('SHOW TABLES FROM '.DB_NAME.' LIKE \''.$this->storage->getTableName()."_".$this->sectionId.'\'');
if ($row = $res->Fetch())
{
$tableName = $this->storage->getTableName()."_".$this->sectionId;
}
}
В результате, для разделов с большим количеством элементов мы создали отдельные фасеты, запросы к которым стали в разы быстрее. При желании можно удалить записи этих разделов из общей фасеты, это ускорит страницы, где общая фасета будет использоваться.
03.10.2023
Семен Голиков.