Setting up caching for custom 1C-Bitrix components

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages

Configuring Caching for Custom 1C-Bitrix Components

A component without caching is a database query on every page view. With 1,000 page views per day that means 1,000 queries; at 10,000 it becomes a real load problem. Caching in Bitrix is not a magic "enable" button — it is a set of concrete decisions: what to cache, for how long, under what key, and how to invalidate when data changes.

Bitrix Caching Mechanism

Bitrix uses file-based caching by default. Cache is stored in /bitrix/cache/ (or /upload/cache/ depending on configuration). Each cache file is a serialized $arResult of a component. On a cache hit, template.php is called with the cached data — no database queries at all.

Two levels of caching:

  1. Result cache (StartResultCache / EndResultCache) — $arResult is cached
  2. HTML cache (composite cache, a separate mechanism) — the final HTML is cached

For custom components, the first level is used.

Basic Usage of StartResultCache

// component.php
$cacheId = serialize([
    $arParams['IBLOCK_ID'],
    $arParams['COUNT'],
    $arParams['SECTION_ID'],
    LANGUAGE_ID,
    SITE_ID,
]);

$cacheDir = '/custom/my.component/' . $arParams['IBLOCK_ID'] . '/';

if ($this->StartResultCache($arParams['CACHE_TIME'], $cacheId, $cacheDir)) {
    // This block executes only when there is no cache
    $this->arResult = $this->getData();
    $this->IncludeComponentTemplate();
    $this->EndResultCache();
}

Parameters of StartResultCache($cacheTime, $cacheId, $cacheDir):

  • $cacheTime — TTL in seconds. false or 0 disables caching. -1 means infinite cache (until manually invalidated).
  • $cacheId — unique identifier for this particular set of parameters. Must differ for different component parameters — otherwise all variants will show the same data.
  • $cacheDir — folder inside /bitrix/cache/. Needed for bulk invalidation of a single component's cache.

Forming cacheId Correctly

A common first-time mistake: $cacheId does not account for all influencing parameters. As a result, the page showing the "Electronics" section displays the cache from the "Clothing" section.

What must always be included in cacheId:

$cacheId = serialize([
    // Component parameters that affect the result
    $arParams['IBLOCK_ID'],
    $arParams['SECTION_ID'],
    $arParams['COUNT'],
    $arParams['ELEMENT_SORT_FIELD'],
    // Context
    LANGUAGE_ID,              // multilingual support
    SITE_ID,                  // multi-site support
    // If content depends on user group
    // serialize(CSaleUser::GetUserGroups()), -- only when needed
]);

Do not include in cacheId anything that does not affect the data — otherwise the cache will never be reused. For example, the block's CSS class ($arParams['CSS_CLASS']) is a display parameter, not a data parameter. It is applied in template.php directly from $arParams, not through $arResult.

Cache with User Group Awareness

If a component shows different content to authenticated users and guests (for example, B2B prices differ from retail prices), the cache must be separate:

// Option 1: disable cache for authenticated users
global $USER;
$cacheTime = $USER->IsAuthorized() ? false : $arParams['CACHE_TIME'];

// Option 2: enable per-group separation (heavier)
$arParams['CACHE_GROUPS'] = 'Y'; // this parameter enables per-group separation in Bitrix

The CACHE_GROUPS => 'Y' parameter in standard Bitrix components automatically adds user groups to cacheId. In a custom component this must be done manually:

if ($arParams['CACHE_GROUPS'] === 'Y') {
    $userGroups = CSaleUser::GetUserGroups();
    sort($userGroups);
    $cacheId = serialize([$baseParams, $userGroups]);
}

Cache Invalidation on Data Update

TTL cache (time-based cache) is the simplest solution but imprecise: an element updated at 10:00 will only become visible after 1 hour (if CACHE_TIME = 3600). For time-sensitive content, event-based invalidation is required.

Infoblock event handler:

// In /local/php_interface/init.php
AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', 'clearMyComponentCache');
AddEventHandler('iblock', 'OnAfterIBlockElementAdd',    'clearMyComponentCache');
AddEventHandler('iblock', 'OnAfterIBlockElementDelete', 'clearMyComponentCache');

function clearMyComponentCache($arFields) {
    // Only clear cache for the relevant infoblock
    if (!in_array($arFields['IBLOCK_ID'], [IBLOCK_CATALOG_ID, IBLOCK_NEWS_ID])) {
        return;
    }
    // Clear the entire cache folder for the component
    BXClearCache(true, '/custom/my.component/' . $arFields['IBLOCK_ID'] . '/');
}

BXClearCache(true, $path) deletes all files in the specified cache folder. Fast and without ORM.

Tagged Cache: Precise Invalidation

If a component displays data from multiple infoblocks, clearing the entire cache whenever any of them changes is wasteful. Tagged cache handles this more precisely:

use Bitrix\Main\Data\TaggedCache;

$taggedCache = new TaggedCache();
$taggedCache->startTagCache('/custom/my.component/');

if ($this->StartResultCache($cacheTime, $cacheId, $cacheDir)) {
    $taggedCache->registerTag('iblock_id_' . $arParams['IBLOCK_ID']);
    $taggedCache->registerTag('iblock_element_' . $elementId); // when caching a specific element

    $this->arResult = $this->getData();
    $this->IncludeComponentTemplate();

    $taggedCache->endTagCache();
    $this->EndResultCache();
} else {
    $taggedCache->abortTagCache();
}

Invalidation by tag when a specific element is updated:

// In the event handler
$taggedCache = new TaggedCache();
$taggedCache->clearByTag('iblock_element_' . $arFields['ID']);
$taggedCache->clearByTag('iblock_id_' . $arFields['IBLOCK_ID']);

Tagged cache is more precise but requires more code. It is justified for high-load projects with frequent data updates.

Debugging the Cache

The cache can be disabled for a specific component during development:

// In component.php while debugging
$cacheTime = defined('DEVELOPER_MODE') && DEVELOPER_MODE ? false : $arParams['CACHE_TIME'];

DEVELOPER_MODE is defined in /bitrix/.settings.php or in project constants. When set to false, cache is bypassed and data is always fetched from the database.

Inspecting the cache: files in /bitrix/cache/ are accessible directly — they are PHP files with serialize() data. The file creation timestamp is the time of the last cache warm-up.

Cache and AJAX Requests

For components with AJAX-based updates (loading products during pagination), cache is applied to each request individually. The cacheId for an AJAX request includes the request parameters:

$page    = max(1, (int)$_REQUEST['page']);
$cacheId = serialize([$baseParams, $page]);

Each pagination page is cached separately — this is the correct approach.

Setup Timelines

Task Timeline
Basic caching for a component 2–4 hours
+ Event-based invalidation 4–8 hours
+ Tagged cache 1–2 days
Audit of existing components + fixes 1–3 days

Caching is one of the few optimizations that delivers an immediate and measurable result. A properly configured cache reduces page generation time from 500 ms to 20–50 ms without changing any application logic.