Как ускорить выборку в 1с Битрикс в 20 раз

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Задача - сформировать фид для выгрузки.

Дано:

  • кол-во товаров более 50 000

  • Товары выгружаются с пользовательскими свойствами

Результат эксперимента

ускорение в 40 раз
ускорение в 40 раз

Довольно частая задача - это формирование фидов, что подразумевает под собой выгрузку большого кол-ва элементов.

Битрикс часто ругают за то - что при простых операциях - происходит огромное ко-во запросов к БД, я видел на проектах , как для отображения простой страницы каталога, битрикс делал более 5000 запросов к БД. Именно из- за этого проекты на Битрикс иногда очень требовательны к железу на хостинге.

Современное ядро D7 позволяет значительно снизить нагрузку на БД и сильно ускоряет все процессы связанные с обращением к БД.

Долгое время на одном проекте работал скрипт по формированию фида для загрузки на сторонние сервисы. Скрипт работал, никаких сбоев не выдавал, и все всех устраивало, но вот пришло время вносить изменения и в него.

Первое что бросилось в глаза это то. как в этом скрипте происходила выборка товаров из каталога и добавление к ним пользовательских свойств

\Bitrix\Main\Diag\Debug::startTimeLabel('f_time1');
$obj_items=\CIBlockElement::GetList([] ,$filter, false, false, $select);
while ($objItem = $obj_items->GetNextElement()) {
    $arItem=$objItem->GetFields();
    $result[$arItem['ID']] = $arItem;
    $result[$arItem['ID']]['PROPERTIES'] = $objItem->GetProperties();   
}
\Bitrix\Main\Diag\Debug::endTimeLabel('f_time1');
$f_time=\Bitrix\Main\Diag\Debug::getTimeLabels();
$str_res= "Кол-во эл-в выборки Старое ядро: ".$obj_items->SelectedRowsCount().PHP_EOL;
$str_res.= "Время выполонения выборки Старое ядро: ".round($f_time['f_time1']['time'], 8, PHP_ROUND_HALF_UP).PHP_EOL;
echo $str_res;
Это конечно никуда не годится
Это конечно никуда не годится

Первое что пришло на ум это изменить добавление свойств к элементу


$obj_items=\CIBlockElement::GetList([] ,$filter, false, false, $select);
while ($arItem = $obj_items->fetch()) {
    $result[$arItem['ID']] = $arItem;
    $result[$arItem['ID']]['PROPERTIES'] = [];
    $ids[] = $arItem['ID'];
}
$chunks = array_chunk($ids, 1000);
foreach ($chunks as $key => $chunk) {
    \CIBlockElement::GetPropertyValuesArray(
        $result,
        2,
        ['ID' => $chunk],
        ['CODE' => $properties],
        ['GET_RAW_DATA' => 'Y']
    );
}
\Bitrix\Main\Diag\Debug::endTimeLabel('f_time1');
$f_time=\Bitrix\Main\Diag\Debug::getTimeLabels();
$str_res= "Кол-во эл-в выборки Старое ядро: ".$obj_items->SelectedRowsCount().PHP_EOL;
$str_res.= "Время выполонения выборки Старое ядро: ".round($f_time['f_time1']['time'], 8, PHP_ROUND_HALF_UP).PHP_EOL;
echo $str_res;

Что дало уже приемлемый результат:

Уже улучшил время выборки в 30 раз
Уже улучшил время выборки в 30 раз

Но, обращаться к БД в цикле - это точно нехорошо.

Поэтому решил попробовать Переписать этот код с использованием возможностей ядра D7


//список свойств
//CODE свойств изменены
$properties = [
    'PROP_1',
    'PROP_2',
    'PROP_3',
    'PROP_4',
    'PROP_5',
    'PROP_6',
    'PROP_7',
    'PROP_8',
    'PROP_9',
    'PROP_10',
    'PROP_11',
    'PROP_12',
    'PROP_13',
    'PROP_14',
    'PROP_15',
    'PROP_16',
    'PROP_17',
    'PROP_18',
    'PROP_19',
    'PROP_20',
    'PROP_21',
    'PROP_22',
    'PROP_23',
    'PROP_24',
    'PROP_25',
];
//ищем IBLOCK_ID по CODE
$CatalogiblockId = Helper::getIblockIdByCode("catalog");

foreach ( $properties as  $property_code) {
    //ID свойства по CODE
    $PROP_ARTICLE_ID = Helper::getIblockPropIDByCode($property_code, $CatalogiblockId);

    $props['PROPERTY_'.$PROP_ARTICLE_ID ]= ['data_type' => 'string'];

}
$props['IBLOCK_ELEMENT_ID']= ['data_type' => 'integer'];


$entityProps = Bitrix\Main\Entity\Base::compileEntity(
    'PROPS',
    $props,
    [
        'table_name' => sprintf('b_iblock_element_prop_s%s', $CatalogiblockId),
    ]
);
$select = [
    'ID',
    'IBLOCK_ID',
    'NAME',
    'SORT',
    'IBLOCK_SECTION_ID',
    'DETAIL_PICTURE',
    'PROPS'
];

$result = \Bitrix\Iblock\ElementTable::getList([
    'select'  => $select,
    'filter'  => [
        'IBLOCK_ID' => $CatalogiblockId,
    ],     
    'runtime' => [
        'PROPS'         => [
            'data_type' => $entityProps->getDataClass(),
            'reference' => [
                '=this.ID' => 'ref.IBLOCK_ELEMENT_ID',
            ],
        ],
    ],
]);
\Bitrix\Main\Diag\Debug::endTimeLabel('f_time1');

$f_time=\Bitrix\Main\Diag\Debug::getTimeLabels();

echo "Кол-во эл-в выборки D7: ".$result->getSelectedRowsCount().PHP_EOL;
echo "Время выполонения выборки D7: ".round($f_time['f_time1']['time'], 4, PHP_ROUND_HALF_UP).PHP_EOL;

И результат который очень порадовал

прирост производительности 80 раз
прирост производительности 80 раз

Т.к. получить свойства элементов напрямую нельзя, то сначала создаем сущность $entityProps которая содержит поля из таблицы b_iblock_element_prop_s.IBLOCK_ID где наименование столбцов свойств являются PROPERTY_.ID (ID свойства, поэтому надо их сначала получить по CODE, или добавить посмотрев в свойствах элементов)

Затем мы делаем выборку присоединив сущность к выборке.

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

Но если взять во внимание существенное снижение количества запросов к БД - это может существенно снизить нагрузку на БД. И проведя простую оптимизацию - вы добьетесь потрясающих результатов.

Итог:

  • Добились ускорения выполнения скрипта с 1333 сек до 0,552, т.е. почти в 700 раз.

  • Снижение количества запросов к БД с более 50 000. до ОДНОГО.

Источник: https://habr.com/ru/post/664950/


Интересные статьи

Интересные статьи

Привет! Меня зовут Антон Поляков, и я разрабатываю аналитическое хранилище данных и ELT-процессы в ManyChat. Несколько лет назад мы выбрали Snowflake как сервис для нашей дата-платформы. С ростом...
Хочу поделиться опытом автоматизации экспорта заказов из Aliexpress в несколько CRM. Приведенные примеры написаны на PHP, но библиотеки для работы с Aliexpress есть и для...
В этой статье мы рассмотрим, как система управления 1С-Битрикс справляется с большими нагрузками. Данный вопрос особенно актуален сегодня, когда электронная торговля начинает конкурировать по обороту ...
Здравствуйте. Я уже давно не пишу на php, но то и дело натыкаюсь на интернет-магазины на системе управления сайтами Битрикс. И я вспоминаю о своих исследованиях. Битрикс не любят примерно так,...
С версии 12.0 в Bitrix Framework доступно создание резервных копий в автоматическом режиме. Задание параметров автоматического резервного копирования производится в Административной части на странице ...