Use this file to discover all available pages before exploring further.
FKApi uses Redis as a caching backend to reduce database queries and improve API response times. This guide covers the caching architecture, configuration, and best practices.
Automatic hashing: Keys longer than 200 characters are MD5 hashed
Prefix support: All keys prefixed with fkapi:
Type safety: Handles various argument types
def generate_cache_key(prefix: str, *args: Any, **kwargs: Any) -> str: """ Generate a consistent cache key from prefix and arguments. Args: prefix: Cache key prefix *args: Positional arguments to include in key **kwargs: Keyword arguments to include in key Returns: str: Generated cache key """ parts = [prefix] parts.extend(str(arg) for arg in args) if kwargs: sorted_kwargs = sorted(kwargs.items()) parts.extend(f"{k}={v}" for k, v in sorted_kwargs) key_string = "_".join(parts) if len(key_string) > 200: key_hash = hashlib.md5(key_string.encode()).hexdigest() return f"{prefix}_{key_hash}" return key_string
FKApi uses Django signals for automatic cache invalidation. When a model is saved or deleted, related cache entries are automatically invalidated.The invalidation is configured in core/cache_utils.py:
def setup_cache_invalidation() -> None: """Set up signal handlers for automatic cache invalidation.""" def invalidate_on_save(sender, instance, **kwargs): invalidation_fn = _MODEL_INVALIDATORS.get(type(instance)) if invalidation_fn is not None: invalidation_fn(instance.id) if type(instance) is Kit: _invalidate_kit_related(instance) def invalidate_on_delete(sender, instance, **kwargs): invalidation_fn = _MODEL_INVALIDATORS.get(type(instance)) if invalidation_fn is not None: invalidation_fn(instance.id) for model in (Club, Season, Kit, Brand, Competition): post_save.connect(invalidate_on_save, sender=model) post_delete.connect(invalidate_on_delete, sender=model)
def invalidate_club_cache(club_id: int) -> None: """Invalidate all cache entries related to a specific club.""" patterns = [ f"{CACHE_PREFIX_CLUB}_{club_id}_*", f"{CACHE_PREFIX_SEASON}_club_{club_id}_*", f"{CACHE_PREFIX_KIT}_club_{club_id}_*", f"{CACHE_PREFIX_SEARCH}_club_{club_id}_*", ] _invalidate_patterns(patterns)
def invalidate_season_cache(season_id: int) -> None: """Invalidate all cache entries related to a specific season.""" patterns = [ f"{CACHE_PREFIX_SEASON}_{season_id}_*", f"{CACHE_PREFIX_KIT}_season_{season_id}_*", f"{CACHE_PREFIX_SEARCH}_season_{season_id}_*", ] _invalidate_patterns(patterns)
def invalidate_kit_cache(kit_id: int) -> None: """Invalidate all cache entries related to a specific kit.""" patterns = [ f"{CACHE_PREFIX_KIT}_{kit_id}_*", f"{CACHE_PREFIX_SEARCH}_kit_{kit_id}_*", ] _invalidate_patterns(patterns)def _invalidate_kit_related(kit: Kit) -> None: """Invalidate club and season cache when kit changes.""" if kit.team: invalidate_club_cache(kit.team.id) if kit.season: invalidate_season_cache(kit.season.id)
def invalidate_user_collection_cache(userid: int) -> None: """Invalidate cache entry for a specific user collection.""" from django.core.cache import cache cache_key = generate_cache_key("user_collection", userid) cache.delete(cache_key) logger.info(f"Invalidated user collection cache for userid: {userid}")
from core.cache_utils import ( invalidate_club_cache, invalidate_season_cache, invalidate_kit_cache, invalidate_search_cache,)# Invalidate specific clubinvalidate_club_cache(club_id=1)# Invalidate all search cachesinvalidate_search_cache()# Invalidate using Django cache directlyfrom django.core.cache import cachecache.delete('specific_cache_key')cache.clear() # Clear all cache (use with caution!)
FKApi includes Prometheus metrics in core/metrics.py:
# Cache hit/miss counterscache_hits = Counter( 'fkapi_cache_hits_total', 'Total number of cache hits', ['cache_type'])cache_misses = Counter( 'fkapi_cache_misses_total', 'Total number of cache misses', ['cache_type'])# Cache entries gaugecache_entries = Gauge( 'fkapi_cache_entries', 'Number of entries in cache', ['cache_type'])
Use in your code:
from core.metrics import cache_hits, cache_missesfrom django.core.cache import cachedef get_cached_data(key): data = cache.get(key) if data is not None: cache_hits.labels(cache_type='kit').inc() return data else: cache_misses.labels(cache_type='kit').inc() # Fetch from database data = fetch_from_db() cache.set(key, data, timeout=3600) return data