Настройка стратегии инвалидации кэша (TTL, Event-Based, Cache-Aside)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка стратегии инвалидации кэша (TTL, Event-Based, Cache-Aside)
Сложная
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка стратегии инвалидации кэша (TTL, Event-Based, Cache-Aside)

Кэширование без продуманной инвалидации — источник трудноотлаживаемых багов с устаревшими данными. Выбор стратегии зависит от требований к freshness данных и архитектуры системы.

Основные стратегии

TTL (Time-To-Live) — данные автоматически устаревают через заданный промежуток. Просто реализовать, но данные могут быть устаревшими до истечения TTL.

Cache-Aside (Lazy Loading) — приложение сначала проверяет кэш, при miss — загружает из БД и записывает в кэш. Самая распространённая стратегия.

Write-Through — запись идёт одновременно в кэш и БД. Данные всегда свежие, но каждая запись проходит через кэш.

Event-Based Invalidation — при изменении данных генерируется событие, которое инвалидирует соответствующие ключи в кэше.

Cache-Aside с TTL

import redis
import json
from functools import wraps

redis_client = redis.Redis(host='redis', decode_responses=True)

def cached(key_template, ttl=300):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            cache_key = key_template.format(*args, **kwargs)
            cached_val = redis_client.get(cache_key)

            if cached_val:
                return json.loads(cached_val)

            result = func(*args, **kwargs)
            redis_client.setex(cache_key, ttl, json.dumps(result))
            return result
        return wrapper
    return decorator

@cached("user:{0}", ttl=600)
def get_user(user_id):
    return db.query("SELECT * FROM users WHERE id = %s", user_id)

Инвалидация при обновлении:

def update_user(user_id, data):
    db.execute("UPDATE users SET ... WHERE id = %s", user_id)
    redis_client.delete(f"user:{user_id}")
    # Инвалидировать связанные ключи
    redis_client.delete(f"user_posts:{user_id}")
    redis_client.delete(f"user_profile_full:{user_id}")

Event-Based инвалидация через очередь

# publisher (при изменении данных)
import pika

def publish_invalidation(entity_type, entity_id, changed_fields=None):
    connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq'))
    channel = connection.channel()
    channel.exchange_declare(exchange='cache_invalidation', exchange_type='topic')

    message = json.dumps({
        'entity': entity_type,
        'id': entity_id,
        'fields': changed_fields
    })
    channel.basic_publish(
        exchange='cache_invalidation',
        routing_key=f'invalidate.{entity_type}',
        body=message
    )

# subscriber (кэш-сервис)
def on_user_changed(channel, method, properties, body):
    event = json.loads(body)
    patterns_to_invalidate = [
        f"user:{event['id']}",
        f"user_full:{event['id']}",
    ]
    if 'role' in (event.get('fields') or []):
        patterns_to_invalidate.append(f"user_permissions:{event['id']}")

    for key in patterns_to_invalidate:
        redis_client.delete(key)

Cache Tags (зависимости кэша)

Тэгирование позволяет инвалидировать группы связанных ключей по одному тэгу:

// PHP/Laravel: Spatie Response Cache или кастомная реализация
class TaggedCache
{
    public function put(string $key, $value, int $ttl, array $tags = []): void
    {
        Redis::setex($key, $ttl, serialize($value));
        foreach ($tags as $tag) {
            Redis::sadd("cache_tag:{$tag}", $key);
            Redis::expire("cache_tag:{$tag}", $ttl + 60);
        }
    }

    public function invalidateByTag(string $tag): void
    {
        $keys = Redis::smembers("cache_tag:{$tag}");
        if (!empty($keys)) {
            Redis::del($keys);
        }
        Redis::del("cache_tag:{$tag}");
    }
}

// Использование
$cache->put("product:42", $product, 3600, ['product:42', 'category:5', 'brand:3']);

// При изменении категории 5 — инвалидировать всё связанное
$cache->invalidateByTag('category:5');

Stale-While-Revalidate

Паттерн: возвращать устаревшие данные, пока фоново обновляется кэш. Устраняет cache stampede (thundering herd):

import threading

def get_with_stale_revalidate(key, fetch_fn, ttl=300, stale_ttl=60):
    data = redis_client.get(key)
    if data:
        result = json.loads(data)
        remaining_ttl = redis_client.ttl(key)

        # Если TTL мало — начать фоновое обновление
        if remaining_ttl < stale_ttl:
            lock_key = f"revalidate_lock:{key}"
            if redis_client.set(lock_key, 1, nx=True, ex=30):
                threading.Thread(
                    target=lambda: _background_refresh(key, fetch_fn, ttl)
                ).start()
        return result

    # Cache miss — синхронное получение
    result = fetch_fn()
    redis_client.setex(key, ttl, json.dumps(result))
    return result


def _background_refresh(key, fetch_fn, ttl):
    try:
        result = fetch_fn()
        redis_client.setex(key, ttl, json.dumps(result))
    finally:
        redis_client.delete(f"revalidate_lock:{key}")

Cache Stampede защита через Locks

def get_with_lock(key, fetch_fn, ttl=300):
    result = redis_client.get(key)
    if result:
        return json.loads(result)

    lock = redis_client.lock(f"lock:{key}", timeout=10)
    if lock.acquire(blocking=True, blocking_timeout=5):
        try:
            # Повторная проверка после получения блокировки
            result = redis_client.get(key)
            if result:
                return json.loads(result)

            data = fetch_fn()
            redis_client.setex(key, ttl, json.dumps(data))
            return data
        finally:
            lock.release()

TTL стратегии по типу данных

Тип данных TTL Инвалидация
Профиль пользователя 10 мин При update
Список товаров 5 мин При изменении товара
Конфиг приложения 1 час При deploy
Курсы валют 30 сек По событию
Права пользователя 5 мин При смене роли
HTML-страницы 1 час При публикации

Мониторинг эффективности кэша

# Redis INFO stats
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"
# keyspace_hits:12847293
# keyspace_misses:234821

# Hit rate = hits / (hits + misses)
# Нормально: > 80%

Метрика в Prometheus через redis_exporter:

redis_keyspace_hits_total / (redis_keyspace_hits_total + redis_keyspace_misses_total)

Срок выполнения

Разработка стратегии инвалидации с Cache Tags и Event-Based подходом — 3–5 рабочих дней.