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() modulZá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
| Konstanta | Sekundy | Použití |
|---|---|---|
STATIC | 3600 (1h) | Kraje, specializace, lokace, type configs |
GEO | 86400 (24h) | Města (seed data, nemění se) |
ENTITY_DETAIL | 900 (15min) | Certifikace instruktorů, lokace kurzů |
STATS | 300 (5min) | Statistické endpointy |
GENERATION_LOG | 1800 (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.
| Skupina | Invalidace na event |
|---|---|
regions | region.changed |
specializations | specialization.changed |
geo | geo.changed |
courses | course.changed, booking.changed |
locations | course.changed |
instructors | instructor.changed, booking.changed |
companies | company.changed |
bookings | booking.changed |
coursegen | course-generation.changed |
genlogs | course-generation.changed |
Stampede protection
Metoda getOrSet() používá Redis mutex (SET NX EX 5) pro ochranu proti stampede:
- Pokud cache klíč neexistuje, první požadavek získá mutex
- Ostatní požadavky čekají (polling 1s) na výsledek
- 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
| Metoda | Popis |
|---|---|
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 |