Highload-Block Optimization: Approaches and Tools
Imagine a highload block with an order history of 5 million rows—a filtered query by date takes 40 seconds. After optimization—8 milliseconds. This is achievable with proper indexing, caching, and partitioning. Highload blocks (the highloadblock module) are a Bitrix mechanism for storing arbitrary data in separate tables. Common use cases: event logs, product catalogs with non-standard structure, user profiles, cumulative data (order history, analytics, queues). While rows are under 50–100 thousand, everything works fine. At 1–10 million rows, problems start: the Bitrix ORM generates suboptimal queries, indexes don't cover real selections, JOINs slow down. We have encountered projects where query time was 40 seconds—after optimization it dropped to 8 milliseconds. Savings per query—up to 99.9%.
Why do highload blocks slow down on large data?
Main anti-patterns when working with Highload:
- Missing required indexes. A highload block creates a table with primary key
IDand auto-increment. Custom fields likeUF_*are not automatically indexed. AgetList(['filter' => ['UF_PRODUCT_ID' => 123]])on a million rows is a table scan. - SELECT * like queries. By default, the Bitrix ORM selects all fields. If a record has 30 UF fields, including TEXT and FILE, this is an expensive query even with a small result set.
- Unlimited queries without pagination.
DataManager::getList()withoutlimitreturns all records into PHP memory. - Linked tables via Reference. If a Highload is linked to another Highload or infoblock via Reference fields—the ORM builds a JOIN that kills performance without proper indexes.
- Frequent UPDATE on fields without an index. Typical for status fields, counters.
How to diagnose bottlenecks?
Enable MySQL slow query log:
[mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = 1 How to properly index highload tables?
Add indexes via direct SQL—in an agent during installation or a migration script:
$connection = \Bitrix\Main\Application::getConnection(); $tableName = 'b_hl_product_catalog'; // Example $connection->queryExecute( "CREATE INDEX IF NOT EXISTS idx_product_id ON {$tableName} (UF_PRODUCT_ID)" ); $connection->queryExecute( "CREATE INDEX IF NOT EXISTS idx_status_date ON {$tableName} (UF_STATUS, UF_DATE_CREATE)" ); What if the ORM still slows down even with indexes?
Explicit SELECT of required fields. Never request select: ['*'] or an empty select array:
$result = ProductCatalogTable::getList([ 'select' => ['ID', 'UF_NAME', 'UF_PRICE', 'UF_ACTIVE'], 'filter' => ['UF_CATEGORY_ID' => $categoryId, 'UF_ACTIVE' => 1], 'order' => ['UF_SORT' => 'ASC'], 'limit' => 50, 'offset' => ($page - 1) * 50, ]); Direct SQL for aggregation. For COUNT, SUM, GROUP BY on large tables—direct SQL is 5–7 times faster than ORM. Use Bitrix\Main\Application::getConnection()->query().
Partitioning for chronological data. If a Highload stores logs or events with dates—partitioning by date range drastically speeds up period queries:
ALTER TABLE b_hl_event_log PARTITION BY RANGE (YEAR(UF_DATE_CREATE) * 100 + MONTH(UF_DATE_CREATE)) ( PARTITION p_jan VALUES LESS THAN (202402), PARTITION p_feb VALUES LESS THAN (202403), PARTITION p_future VALUES LESS THAN MAXVALUE ); Caching results. Highload data caches well via Bitrix\Main\Data\Cache with tagging. Example implementation:
class CachedProductCatalog { private const CACHE_TAG = 'hl_product_catalog'; private const CACHE_TTL = 3600; public function getByCategory(int $categoryId): array { $cache = \Bitrix\Main\Data\Cache::createInstance(); $cacheKey = 'hl_catalog_cat_' . $categoryId; if ($cache->initCache(self::CACHE_TTL, $cacheKey, '/hl/catalog/')) { return $cache->getVars(); } $cache->startDataCache(); // Fetch from DB $result = $this->fetchFromDb($categoryId); // Tagged cache $tagCache = new \Bitrix\Main\Data\TaggedCache(); $tagCache->startTagCache('/hl/catalog/'); $tagCache->registerTag(self::CACHE_TAG . '_' . $categoryId); $tagCache->endTagCache(); $cache->endDataCache($result); return $result; } } Benchmarks: what each optimization gives
| Optimization | 1M row table | 10M row table |
|---|---|---|
| Adding index on filtered field | 4000 ms → 5 ms | 40000 ms → 8 ms |
| SELECT only needed fields | 800 ms → 120 ms | — |
| Cache hit | 120 ms → 0.5 ms | — |
| Direct SQL instead of ORM (aggregation) | 350 ms → 45 ms | 3000 ms → 80 ms |
| Partitioning by date | — | 3000 ms → 60 ms |
Step-by-step optimization plan
- Audit Highload blocks. Collect structure, data volumes, typical queries. Enable slow query log.
- Analyze bottlenecks. Identify top-5 slowest queries by time.
- Add indexes. Create single and composite indexes for actual filters.
- Refactor code. Replace
select: ['*']with explicit list, implement limits and pagination. - Implement caching. Use tagged cache for frequently requested data.
- Partition tables. For chronological tables—split by date.
- Load testing. A/B comparison of performance before and after.
What's included in the work
- Audit of Highload blocks: structure, data volume, typical queries and slow query log.
- Bottleneck analysis: identification of top-5 slowest queries.
- Index addition: single and composite indexes for real filter patterns.
- Code refactoring: explicit select, limits, pagination, replacement of ORM with direct SQL where it gives substantial benefit.
- Implementation of tagged cache for heavy selections.
- Partitioning of chronological tables (if needed).
- Load testing with A/B comparison before/after.
- Documentation and training for your developer.
Timeline and pricing
Work timeline: audit + indexes + cache — 2–3 weeks. Full optimization with partitioning and refactoring — 4–8 weeks. Cost is calculated individually based on data volume and complexity. Our team with many years of Bitrix experience and over 50 completed optimization projects guarantees a transparent approach and measurable results. Contact us for a preliminary estimate—we will propose a work plan with checkpoints. Order a performance audit and get an engineer consultation.
Learn more about Highload blocks in the official documentation.

