Programování

Redis jako cache: cache-aside, TTL, invalidace a časté chyby

Jak zrychlit aplikaci pomocí Redisu: instalace, vzor cache-aside v Pythonu a Node.js, nastavení TTL a maxmemory, invalidace a ochrana před cache stampede.

12. 10. 2026 · 4 min čtení

Když databáze opakovaně počítá stejnou věc – přehled produktů, počty, výsledek drahého dotazu – je čas na cache. Redis je in-memory úložiště klíč–hodnota s odezvou v řádu stovek mikrosekund a pro tuto roli je nejčastější volbou. Ukážeme, jak ho nasadit správně a neudělat si z cache zdroj chyb.

Instalace a základní nastavení

Na Ubuntu/Debianu:

sudo apt install -y redis-server
redis-cli ping   # odpověď: PONG

Konfigurace je v /etc/redis/redis.conf. Pro cache jsou důležité tři věci:

bind 127.0.0.1 -::1
maxmemory 256mb
maxmemory-policy allkeys-lru
  • bind – Redis má naslouchat jen lokálně. Redis vystavený do internetu bez hesla je jeden z nejčastějších způsobů, jak přijít o server.
  • maxmemory – horní limit paměti.
  • maxmemory-policy allkeys-lru – při zaplnění Redis zahodí nejdéle nepoužité klíče. Výchozí noeviction by místo toho začal odmítat zápisy.
sudo systemctl restart redis-server

Pokud k Redisu přistupuje více aplikací nebo jiný server, nastavte heslo (requirepass) nebo uživatele přes ACL.

Poznámka k licenci: Redis od verze 7.4 změnil licenci a od 8.0 přidal i AGPL. Některé distribuce proto nabízejí kompatibilní fork Valkey – příkazy i klientské knihovny fungují stejně.

Vzor cache-aside

Nejběžnější a nejbezpečnější vzor:

  1. Aplikace se podívá do cache.
  2. Pokud data najde (hit), vrátí je.
  3. Pokud ne (miss), načte je z databáze, uloží do cache s expirací a vrátí.

Python (redis-py)

import json
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def get_product(product_id: int) -> dict:
    key = f"product:{product_id}:v1"
    cached = r.get(key)
    if cached is not None:
        return json.loads(cached)

    product = db_fetch_product(product_id)  # váš dotaz do databáze
    r.set(key, json.dumps(product), ex=600)  # TTL 10 minut
    return product

Node.js (node-redis v4+)

import { createClient } from "redis";

const redis = createClient({ url: "redis://localhost:6379" });
await redis.connect();

export async function getProduct(id) {
  const key = `product:${id}:v1`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const product = await dbFetchProduct(id);
  await redis.set(key, JSON.stringify(product), { EX: 600 });
  return product;
}

Vždy nastavte TTL

Klíč bez expirace v cache žije, dokud ho Redis nevyhodí kvůli paměti – a mezitím může servírovat zastaralá data. TTL je pojistka: i když zapomenete cache invalidovat, data se nejpozději po uplynutí TTL obnoví.

Kontrola v redis-cli:

redis-cli TTL product:42:v1    # zbývající sekundy, -1 = bez expirace, -2 = neexistuje

Invalidace

Při změně dat v databázi klíč smažte:

def update_product(product_id: int, data: dict) -> None:
    db_update_product(product_id, data)
    r.delete(f"product:{product_id}:v1")

Klíč mažte, nepřepisujte. Při souběžných požadavcích by přepis mohl uložit starší verzi dat přes novější. Smazání znamená, že další čtení si načte aktuální stav.

Přípona :v1 v názvu klíče se hodí při změně formátu dat: zvýšíte verzi na v2 a staré klíče postupně vyprší.

Nepoužívejte KEYS v produkci

KEYS product:* projde celou databázi a zablokuje Redis (je jednovláknový pro příkazy). Pro procházení klíčů použijte SCAN:

redis-cli --scan --pattern 'product:*' | head

Cache stampede

Populární klíč vyprší a v tu chvíli sto souběžných požadavků zjistí miss a všechny se zeptají databáze. Obrana:

  • Zámek – jen první požadavek načítá data, ostatní chvíli počkají nebo dostanou starou hodnotu:
if r.set(f"lock:{key}", "1", nx=True, ex=10):
    try:
        value = load_from_db()
        r.set(key, json.dumps(value), ex=600)
    finally:
        r.delete(f"lock:{key}")
  • Náhodný rozptyl TTL (ex=600 + random.randint(0, 60)), aby klíče nevypršely všechny najednou.

Co do cache patří a co ne

Ano: výsledky drahých dotazů, data z pomalých externích API, vyrenderované fragmenty stránek, session, počítadla pro rate limiting.

Ne: data, která musí být vždy přesná (zůstatek na účtu, sklad při objednávce), a nic, co nemáte jinde uloženo – cache může kdykoli zmizet.

Monitoring

redis-cli INFO stats | grep -E 'keyspace_(hits|misses)|evicted_keys'
redis-cli INFO memory | grep used_memory_human
redis-cli --latency

Poměr keyspace_hits ku keyspace_misses ukazuje, jestli cache pomáhá. Rostoucí evicted_keys znamená, že je maxmemory malé.

Shrnutí

  • Redis jen na localhost nebo za heslem.
  • maxmemory + allkeys-lru.
  • Cache-aside, TTL u každého klíče, při změně dat klíč smazat.
  • SCAN místo KEYS.
  • Aplikace musí fungovat i při výpadku Redisu – chybu cache zalogujte a čtěte z databáze.