Оптимізація ORM-запитів D7: як прискорити сайт на Бітрікс
D7 ORM — об'єктно-реляційний маппер нового ядра Бітрікс. Ми, розробники з 10+ річним досвідом, часто бачимо проекти, де один необережний запит перетворює сторінку на 5-секундне очікування. Запит, написаний за 5 хвилин, може робити SELECT * із трьома зайвими JOIN — і на великому каталозі це 500 мс замість 10 мс. Розповімо, як знаходити та усувати такі проблеми.
На практиці ми стикалися з проектами, де після оптимізації ORM-запитів час генерації сторінки скорочувався з 3 секунд до 300 мс. Це реальні цифри — достатньо прибрати N+1 та зайві поля. Не знаєте, які запити гальмують сайт? Замовте аудит ORM-шару — отримаєте звіт із конкретними рекомендаціями.
Як D7 ORM будує SQL і які проблеми це створює?
ORM читає опис таблиці з методу getMap() класу-сутності. Відношення (references) описуються там же. Наприклад, для інфоблоків використовується клас \Bitrix\Iblock\ElementTable, в якому getMap() повертає поля та зв'язки. Якщо ви запитуєте IBLOCK.NAME, ORM додає LEFT JOIN до b_iblock. Це зручно, але надлишково, якщо IBLOCK_ID заздалегідь відомий. Ми рекомендуємо завжди перевіряти згенерований SQL через $query->getQuery().
Що таке N+1 і як його уникнути?
N+1 — класична проблема ORM. При використанні fetchObject() кожен доступ до пов'язаної сутності породжує окремий SQL-запит:
// Погано: N+1 запитів
foreach ($elements as $element) {
echo $element->getSection()->getName(); // додатковий запит на кожен елемент!
}
// Правильно: підвантажуємо секції одразу
$result = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME', 'IBLOCK_SECTION_ID', 'SECTION_' => 'IBLOCK_SECTION.NAME'],
'filter' => ['=IBLOCK_ID' => 5],
]);
Важливість усунення N+1: на 1000 елементах ви отримаєте 1001 запит замість 1. Зі зростанням навантаження це вбиває продуктивність. Наш досвід показує, що усунення N+1 знижує час завантаження сторінок у 2–5 разів.
Як правильно вибирати поля в select?
Якщо не вказати select, ORM вибирає всі поля з getMap(). Для ElementTable це 20+ полів, включаючи DETAIL_TEXT (тип Text — потенційно мегабайти). Правило: завжди явно задавайте лише необхідні поля.
$result = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME', 'PREVIEW_PICTURE_ID', 'DETAIL_PAGE_URL'],
'filter' => ['=IBLOCK_ID' => 5, '=ACTIVE' => 'Y'],
'order' => ['SORT' => 'ASC'],
'limit' => 20,
]);
Коли використовувати runtime-поля?
Runtime-поля дозволяють додавати обчислювані значення прямо в SQL, без обробки в PHP. Наприклад, отримати ціну зі знижкою:
use Bitrix\Main\Entity;
$result = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME', 'PRICE_VALUE'],
'runtime' => [
new Entity\ReferenceField(
'PRICE',
\Bitrix\Catalog\PriceTable::class,
['=this.ID' => 'ref.PRODUCT_ID', '=ref.CATALOG_GROUP_ID' => new Entity\ExpressionField('PTYPE', '1')],
['join_type' => 'LEFT']
),
new Entity\ExpressionField('PRICE_VALUE', '%s', ['PRICE.PRICE']),
],
'filter' => ['=IBLOCK_ID' => 5],
]);
Через ExpressionField можна виконувати будь-які обчислення на стороні MySQL — це швидше, ніж постобробка в PHP.
Як працювати з великими вибірками без перевантаження пам'яті?
При імпорті або масовому оновленні не завантажуйте всі рядки в пам'ять:
$offset = 0;
$limit = 500;
do {
$result = SomeTable::getList([
'select' => ['ID', 'NAME'],
'limit' => $limit,
'offset' => $offset,
'order' => ['ID' => 'ASC'],
]);
$rows = $result->fetchAll();
foreach ($rows as $row) {
// обробка
}
$offset += $limit;
} while (count($rows) === $limit);
Для великих таблиць ефективніша курсорна пагінація за ID: filter => ['>ID' => $lastId]. Це прискорює запити при зміщенні вглиб.
Кешування ORM-запитів
D7 підтримує вбудований кеш:
$result = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME'],
'filter' => ['=IBLOCK_ID' => 5, '=ACTIVE' => 'Y'],
'cache' => [
'ttl' => 3600,
'cache_joins' => true,
],
]);
Кеш автоматично інвалідується при зміні даних через ORM. Якщо ви використовуєте прямий SQL — кеш скиньте вручну.
Порівняння підходів до завантаження пов'язаних даних
| Метод |
Продуктивність |
Складність |
Коли застосовувати |
| Join через select |
Висока, один запит |
Низька |
Коли потрібно багато пов'язаних полів |
| FetchObject + окремі запити |
Низька, N+1 |
Низька |
Тільки для поодиноких записів |
| Runtime-поля |
Висока, обчислення в SQL |
Середня |
Складні обчислення, агрегації |
| Окремий запит з кешем |
Середня, два запити |
Середня |
Рідко використовувані зв'язки |
Типові проблеми ORM-запитів та їх вирішення
| Проблема |
Прояв |
Рішення |
| SELECT * без необхідності |
Високе навантаження на мережу та БД |
Явно вказувати select |
| N+1 при fetchObject() |
Безліч дрібних запитів |
Завантажувати пов'язані дані через JOIN |
| Використання OFFSET на великих таблицях |
Гальмує при глибокій пагінації |
Використовувати курсорну пагінацію |
| Відсутність кеша на часто виконувані запити |
Повторні запити до БД |
Увімкнути кеш ORM |
| Обчислення в PHP замість SQL |
Зайве навантаження на PHP |
Використовувати runtime-поля |
Що входить у роботу з оптимізації ORM-шару
Ми пропонуємо комплексний аудит та оптимізацію ORM-запитів під ключ:
- Аналіз усіх ORM-запитів проекту через SqlTracker та профайлер
- Виявлення зайвих select, JOIN, N+1
- Встановлення та налаштування кеша для повільних запитів
- Рефакторинг складних запитів з runtime-полями
- Оптимізація пагінації та обробки великих вибірок
- Документування змін та навчання вашої команди
- Гарантія на результат — прискорення сторінок не менше 30%
Терміни: від 1 до 2 тижнів залежно від обсягу коду. Вартість розраховується індивідуально — пишіть, ми оцінимо ваш проект.
Процес роботи
- Аналітика — збір метрик, профілювання поточних запитів
- Проектування — план оптимізації з пріоритетами
- Реалізація — внесення змін у код
- Тестування — перевірка коректності та продуктивності
- Деплой — викладка та моніторинг
Чому варто довірити оптимізацію нам?
Ми — команда сертифікованих спеціалістів 1С-Бітрікс з 10+ річним досвідом. Виконали понад 100 проектів з оптимізації. Знаємо всі тонкощі D7 ORM і вміємо знаходити вузькі місця, які не бачить статичний аналізатор.
Не відкладайте оптимізацію — замовте аудит ORM-шару вашого проекту. Зв'яжіться з нами — ми підготуємо пропозицію протягом 2 робочих днів.
Wikipedia: об'єктно-реляційне відображення
80% сайтів на Бітрікс гальмують через одну таблицю
b_iblock_element_property — EAV-структура. Кожен рядок зберігає одне значення однієї властивості одного елемента. Каталог у 50 000 товарів з 30 властивостями дає 1,5 млн рядків. Коли розумний фільтр робить JOIN цієї таблиці з b_iblock_element за п'ятьма властивостями, MySQL іде в full table scan на 3–5 секунд.
Наш досвід показує: без втручання в цю таблицю прискорення неможливе. Ми беремося за проекти, де швидкість завантаження впала до 8–10 секунд, і повертаємо TTFB < 200 мс за 1–2 тижні. Оптимізація швидкості сайту починається з аудиту slow-запитів і закінчується комплексною перебудовою інфраструктури під ключ. У нашому портфоліо — понад 200 успішних проєктів. Ми — команда сертифікованих фахівців з 10‑річним досвідом роботи в Бітрікс.
Серверна оптимізація
Nginx. Не просто «увімкнули gzip». Конкретно:
-
gzip_comp_level 4-5 — вище безглуздо, CPU з'їдає більше, ніж економить трафік;
-
brotli on з brotli_static on — для попередньо стиснутих файлів (Brotli стискає на 20–30% краще за gzip);
- HTTP/2 з
http2_max_concurrent_streams 128;
-
fastcgi_cache для PHP-відповідей — кешування на рівні Nginx, минаючи PHP-FPM;
-
worker_processes auto, worker_connections під кількість одночасних з'єднань.
PHP-FPM. Вибір між pm = dynamic і pm = static — не академічний:
- Static: фіксована кількість воркерів, без overhead на форк — для виділених серверів з передбачуваним навантаженням.
- Dynamic: економить RAM при низькому трафіку.
pm.max_children рахуємо як (доступна RAM − RAM для MySQL/Redis) / середня витрата на процес.
- OPcache:
opcache.memory_consumption=256, opcache.max_accelerated_files=20000, opcache.validate_timestamps=0 на продакшені (перезавантаження PHP-FPM при деплої).
MySQL/MariaDB. Головне вузьке місце майже завжди:
-
slow_query_log з порогом 0.5 сек — кожен запит розбираємо через EXPLAIN.
-
innodb_buffer_pool_size = 70–80% доступної RAM на виділеному сервері.
- Складені індекси для фасетного пошуку:
(IBLOCK_ID, IBLOCK_PROPERTY_ID, VALUE) на b_iblock_element_property.
-
OPTIMIZE TABLE b_iblock_element_property після масових операцій.
Як налаштувати кешування на три рівні?
Керований кеш компонентів. TTL налаштовуємо для кожного компонента окремо. Каталог — 3600 сек, новинна стрічка — 300 сек, банери — 86400. Однаковий TTL всюди — гарантія або застарілих даних, або марного кешу.
Композитний кеш. Технологія bitrix:composite — Nginx віддає готовий HTML із файлу, PHP не запускається. Динамічні зони (кошик, авторизація) підвантажуються AJAX-запитом через CBitrixComponent::setFrameMode(true). TTFB < 50 мс. Але не всі компоненти сумісні: $APPLICATION->ShowPanel() і прямий вивід через echo ламають композит. Перевіряємо кожну сторінку через панель «Продуктивність → Композитний сайт».
Порівняння: композитний кеш швидший за керований у 10–20 разів за часом першого байта. Офіційна документація Бітрікс з композитного кешу — Bitrix Composite.
Memcached / Redis. Переносимо кеш із файлової системи:
- Сесії → Redis (
session.save_handler = redis) — швидше за файли в 10–50×, плюс робота в кластері.
- Кеш компонентів → Memcached через
.settings.php: 'cache' => ['type' => 'memcache'].
- Кеш ORM-запитів — однакові
GetList() не навантажують MySQL на кожному хіті.
Чому стандартних налаштувань MySQL недостатньо?
Індекси. Складені для фасетного пошуку. Покриваючі — для частих вибірок MySQL відповідає з індексу, не звертаючись до даних. Часткові індекси (MariaDB) для фільтрації за ACTIVE = 'Y'. Аудит невикористовуваних індексів — кожен сповільнює INSERT/UPDATE.
Партиціонування. Для таблиць з мільйонами рядків: b_stat_session, b_search_content_stem, Highload-блоки з історією. Партиція за датою — запит «замовлення за місяць» не сканує дані за три роки.
Реальний кейс: каталог 200 000 товарів, 50 властивостей. Фільтр за 10 властивостями займав 12 секунд. Після створення складених індексів за (IBLOCK_ID, IBLOCK_PROPERTY_ID, VALUE) та партиціонування b_iblock_element_property за IBLOCK_ID час виконання впав до 0,3 секунди. Навантаження на MySQL знизилося в 40 разів. Це дозволило скоротити витрати на сервер до 40% без втрати продуктивності.
Очищення. У будь-якій базі за рік-два накопичується: застарілий пошуковий індекс, прострочені записи в b_cache_tag, історія b_iblock_element_prop_s*, логи в b_event_log на гігабайти. Налаштовуємо регулярне очищення через агенти.
Партиціонування також вирішує проблему з паралельними запитами при обміні з 1С через CommerceML. Докладніше: MySQL Partitioning Documentation.
Після аудиту вашого проекту ми визначимо вузькі місця за 2 години та запропонуємо конкретний план прискорення. Замовте безкоштовний аудит швидкості — заповніть форму на сайті.
Фронтенд
Зображення — 60–80% ваги сторінки:
- WebP через
CFile::ResizeImageGet() з BX_RESIZE_IMAGE_PROPORTIONAL + конвертація;
-
srcset + sizes — не завантажуємо 3000px картинку в блок 400px;
-
loading="lazy" для всього нижче першого екрану;
- AVIF — ще 20–30% економії порівняно з WebP.
CSS/JS:
- Вбудований модуль Бітрікс: об'єднання та мініфікація через «Налаштування → Оптимізація CSS/JS»;
- PurgeCSS / UnCSS — на типовому Бітрікс-проекті 60–70% CSS не використовуються;
-
defer / async для некритичного JS;
- Critical CSS інлайном у
<head> для миттєвого FCP.
Шрифти:
-
<link rel="preload" as="font" crossorigin> для основного шрифту;
-
font-display: swap — текст видно одразу;
- Subsetting через
pyftsubset — вирізаємо кирилицю + латиницю, файл зменшується в 3–5 разів.
CDN
Cloudflare, BunnyCDN, AWS CloudFront або локальні (Selectel CDN, VK Cloud CDN).
- Статика (CSS, JS, зображення, шрифти) — через CDN.
- Правила кешування:
Cache-Control: public, max-age=31536000, immutable для файлів з хешем.
- Оптимізація зображень на льоту (imgproxy, Cloudflare Polish) без навантаження на origin.
Навіщо потрібне навантажувальне тестування?
Не синтетичні бенчмарки, а реальні сценарії:
- k6 / wrk — імітація маршрутів: каталог → фільтрація → картка → кошик → оформлення.
- Метрики: RPS, час відповіді (p50, p95, p99), відсоток помилок.
- Xdebug (callgrind) або Blackfire — профілювання PHP, пошук вузьких місць.
Результат тестування — об'єктивна картина, де реально гальмує. Після оптимізації проганяємо повторно — фіксуємо покращення.
Результати
| Метрика |
До |
Після |
| TTFB |
800–2000 мс |
50–200 мс |
| Повне завантаження |
4–8 сек |
1.5–2.5 сек |
| PageSpeed (мобільний) |
30–50 |
80–95 |
| Одночасні користувачі |
50–100 |
500–2000+ |
Що входить у роботу?
-
Аудит поточної продуктивності — аналіз slow-запитів, профілювання PHP, перевірка кешування, CDN, серверних налаштувань.
-
Налаштування серверної частини — Nginx, PHP-FPM, MySQL, Redis/Memcached, OPcache.
-
Оптимізація кешування — керований кеш, композитний сайт, налаштування TTL, теговане кешування.
-
Робота з БД — створення індексів, партиціонування, очищення, реорганізація EAV-таблиць.
-
Фронтенд — зображення (WebP/AVIF), CSS/JS (мініфікація, deferred), шрифти (preload, subsetting).
-
CDN — підключення, налаштування правил кешування.
-
Навантажувальне тестування — сценарії реальних користувачів, звіт за метриками.
-
Документація — опис усіх змін, рекомендації щодо подальшого обслуговування.
-
Гарантія — підтримка протягом 1 місяця після здачі.
Моніторинг
Без моніторингу через півроку все деградує. Новий модуль, неочищені логи, зміна в шаблоні — і швидкість повернулася до початкової.
-
web-vitals API — Real User Monitoring від реальних відвідувачів.
-
Synthetic monitoring — Pingdom, UptimeRobot, регулярні перевірки з різних локацій.
-
Алерти — TTFB > 500 мс або LCP > 3 сек → сповіщення.
Терміни та вартість
| Тип робіт |
Терміни |
| Базова оптимізація (кеш, зображення, мініфікація) |
2–3 дні |
| Оптимізація БД (індекси, slow queries, налаштування) |
3–5 днів |
| Серверна інфраструктура (Nginx, PHP-FPM, Redis) |
2–3 дні |
| Комплексна (сервер + БД + фронтенд + CDN) |
1–3 тижні |
| Навантажувальне тестування та профілювання |
2–3 дні |
| Кластерна архітектура (балансування, реплікація) |
1–2 тижні |
Вартість розраховується індивідуально після аудиту. Отримайте консультацію щодо вашого проекту — оцінимо поточний стан і запропонуємо план прискорення з конкретними термінами та бюджетом. Ми — команда з 10+ роками досвіду в Бітрікс, виконали понад 200 проектів з оптимізації швидкості сайту. Замовте безкоштовний аудит швидкості — зв'яжіться з нами просто зараз.