Налаштування стратегії інвалідації кешу (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
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування стратегії інвалідації кэша (TTL, Event-Based, Cache-Aside)

Кэширование без продуманої інвалідації — джерело важко відстежуваних багів зі застарілими даними. Вибір стратегії залежить від вимог до свіжості даних та архітектури системи.

Основні стратегії

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:

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 хв При оновленні
Список товарів 5 хв При змінах товару
Конфіг додатку 1 год При деплою
Курси валют 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_keyspace_hits_total / (redis_keyspace_hits_total + redis_keyspace_misses_total)

Строки виконання

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