Turtor Docs
API

Cache

Redis CacheService, stampede protection, skupinová invalidace a event-driven vzory

API používá Redis-backed CacheService s ochranou proti cache stampede a event-driven invalidací. Služba je registrována jako globální modul -- je dostupná ve všech modulech bez explicitního importu.

Umístění

apps/api/src/common/cache/
├── cache.service.ts              # get(), set(), getOrSet(), invalidateGroup()
├── cache.constants.ts            # CACHE_TTL a CACHE_GROUP konstanty
├── cache-invalidation.listener.ts # Event-driven invalidace (@OnEvent)
└── cache.module.ts               # @Global() modul

Základní použití

import { CacheService } from 'src/common/cache/cache.service';
import { CACHE_TTL, CACHE_GROUP } from 'src/common/cache/cache.constants';

// Sestavení klíče: cache:regions:all
const key = this.cache.buildKey(CACHE_GROUP.REGIONS, 'all');

// Get-or-set s ochranou proti stampede
const data = await this.cache.getOrSet(key, CACHE_TTL.STATIC, () =>
  this.regionsService.findAll(),
);

// Vyvolání invalidace přes event
this.eventEmitter.emit('region.changed');

TTL konstanty

KonstantaSekundyPoužití
STATIC3600 (1h)Kraje, specializace, lokace, type configs
GEO86400 (24h)Města (seed data, nemění se)
ENTITY_DETAIL900 (15min)Certifikace instruktorů, lokace kurzů
STATS300 (5min)Statistické endpointy
GENERATION_LOG1800 (30min)Neměnné po vytvoření

Cache skupiny

Skupiny umožňují hromadnou invalidaci. Když se vyvolá event, všechny klíče ve skupině jsou vymazány přes Redis SCAN + DEL.

SkupinaInvalidace na event
regionsregion.changed
specializationsspecialization.changed
geogeo.changed
coursescourse.changed, booking.changed
locationscourse.changed
instructorsinstructor.changed, booking.changed
companiescompany.changed
bookingsbooking.changed
coursegencourse-generation.changed
genlogscourse-generation.changed

Stampede protection

Metoda getOrSet() používá Redis mutex (SET NX EX 5) pro ochranu proti stampede:

  1. Pokud cache klíč neexistuje, první požadavek získá mutex
  2. Ostatní požadavky čekají (polling 1s) na výsledek
  3. Pokud mutex vyprší, fallback -- každý spustí factory funkci
// Pouze jeden volající spustí databázový dotaz
const data = await this.cache.getOrSet(
  key,
  CACHE_TTL.STATIC,
  () => this.heavyQuery(), // Factory -- spustí se jen jednou
);

Mutex má expiraci 5 sekund. Pokud factory funkce trvá déle, ostatní požadavky propadnou do fallbacku a spustí factory samy.

Event-driven invalidace

Listener cache-invalidation.listener.ts reaguje na NestJS eventy a maže příslušné cache skupiny:

// V service -- po změně dat
await this.regionsService.update(id, dto);
this.eventEmitter.emit('region.changed');

// Listener automaticky invaliduje skupinu 'regions'
@OnEvent('region.changed')
async handleRegionChanged() {
  await this.cacheService.invalidateGroup(CACHE_GROUP.REGIONS);
}

Použití v controllerech

CacheService je použitý v 9 controllerech:

  • Courses -- seznam kurzů, kanban data
  • Bookings -- seznam rezervací
  • Companies -- seznam firem
  • Instructors -- seznam instruktorů, certifikace
  • Regions -- kraje a oblastní spolky
  • Specializations -- typy specializací
  • Geo -- města, pokrytí
  • Course Generation -- konfigurace generování
  • Generation Logs -- logy generátoru

API metod CacheService

MetodaPopis
get<T>(key)Získání hodnoty z cache
set(key, value, ttl)Uložení hodnoty do cache
getOrSet<T>(key, ttl, factory)Získání nebo vytvoření s mutex ochranou
invalidateGroup(group)Smazání všech klíčů ve skupině (SCAN + DEL)
buildKey(group, ...parts)Sestavení klíče: cache:{group}:{parts}
del(key)Smazání jednoho klíče