Development of a caching module for 1C-Bitrix

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
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

1C-Bitrix Caching Module Development

Bitrix has its own cache via \Bitrix\Main\Data\Cache — file-based, with tag support through \Bitrix\Main\Data\TaggedCache. On small sites it works fine. Problems begin under high load: thousands of files in /bitrix/cache/, read/write operations competing on the filesystem, tag-based invalidation as a blocking operation. The caching module builds a unified layer on top of the standard mechanism with a Redis backend, statistics, cache warming, and granular invalidation.

Architecture: CacheManager

The central class CacheManager implements the strategy pattern. The backend is selected in the module settings:

$cache = \Vendor\Cache\CacheManager::getInstance();

// Standard get-or-set
$result = $cache->remember('catalog_section_12', 3600, function() {
    return CIBlockSection::GetList(/* ... */)->Fetch();
}, ['iblock_12', 'catalog']);
// → Data from cache, or the callable result on a miss

// Explicit tag-based invalidation
$cache->invalidateTag('iblock_12');
// → Flushes all keys tagged with iblock_12

Redis Backend

Bitrix's file cache does not scale across multiple servers. Redis solves this problem:

class RedisCacheBackend implements CacheBackendInterface
{
    private \Redis $redis;

    public function get(string $key): mixed
    {
        $data = $this->redis->get($key);
        return $data !== false ? unserialize($data) : null;
    }

    public function set(string $key, mixed $value, int $ttl, array $tags = []): void
    {
        $serialized = serialize($value);
        $this->redis->setEx($key, $ttl, $serialized);

        // Tags are stored as Redis Sets
        foreach ($tags as $tag) {
            $this->redis->sAdd("tag:{$tag}", $key);
            $this->redis->expire("tag:{$tag}", $ttl + 3600);
        }
    }

    public function invalidateTag(string $tag): void
    {
        $keys = $this->redis->sMembers("tag:{$tag}");
        if ($keys) {
            $this->redis->del(...$keys);
        }
        $this->redis->del("tag:{$tag}");
    }
}

Tags in Redis are implemented via Sets. Tag-based invalidation is an atomic operation with no filesystem locks.

Caching Strategies

The module supports several strategies for different data types:

  • TTL cache — classic cache with a time-to-live. For infrequently changing data: site settings, region list, delivery parameters
  • Event-invalidation — cache is flushed on a Bitrix event. Hooked onto OnAfterIBlockElementAdd, OnAfterIBlockElementUpdate, etc.
  • Stale-while-revalidate — stale cache is served immediately while regeneration is triggered in the background via an agent. Eliminates the "thundering herd" on cache misses
  • Request-scoped — cache for the duration of a single HTTP request (in-memory array). Prevents repeated database queries within the same request

Cache Warming

When the cache is flushed, the first request is always slow — it hits the database. Under high load this causes a spike. The warming agent solves the problem:

// Registered warmup tasks
$cache->registerWarmup('catalog_menu', function() {
    return CIBlockElement::GetList(/* full catalogue */);
}, ['iblock_main_catalog'], 7200);

// Agent runs hourly and refreshes cache before TTL expires
\Vendor\Cache\WarmupAgent::run();

Monitoring and Statistics

The b_vendor_cache_stat table stores aggregated statistics per key:

  • key_prefix, hits, misses, avg_ttl, last_hit_at

In the administrative interface:

  • Hit rate by key category
  • Top "cold" keys (many misses)
  • Cache size per backend
  • Manual invalidation by tag or key
  • Log of recent invalidation operations

Integration with Bitrix Components

Standard components use $APPLICATION->IncludeComponent() with the CACHE_TYPE parameter. The module intercepts this mechanism and redirects to Redis:

// In init.php after loading the module
\Vendor\Cache\BitrixCacheBridge::install();
// → Overrides \Bitrix\Main\Data\Cache::createInstance()
//   returning the Redis backend instead of the file backend

The bridge makes the replacement transparent — components continue to work without any code changes.

Development Timeline

Stage Duration
CacheManager architecture, backend interface 1 day
Redis backend with tag support 2 days
Strategies: stale-while-revalidate, request-scoped 2 days
Cache warming agent 1 day
Bridge for standard Bitrix components 1 day
Statistics, administrative interface 2 days
Load testing 1 day

Total: 10 working days. For projects with a PHP server cluster — additional Redis Cluster or Sentinel configuration: +1–2 days.