Caching and Performance
LogixVast APIs implement various caching strategies and performance optimizations to ensure fast response times and efficient resource usage.
Cache Configuration
We use Django's caching framework with Redis as the backend:
# settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PARSER_CLASS': 'redis.connection.HiredisParser',
'CONNECTION_POOL_CLASS': 'redis.BlockingConnectionPool',
'CONNECTION_POOL_CLASS_KWARGS': {
'max_connections': 50,
'timeout': 20,
}
}
}
}
# Cache time to live is 15 minutes
CACHE_TTL = 60 * 15
# Cache key prefix
CACHE_KEY_PREFIX = 'logixvast:'View-Level Caching
Implement caching at the view level using decorators or mixins:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
class VehicleViewSet(viewsets.ModelViewSet):
@method_decorator(cache_page(60 * 15)) # Cache for 15 minutes
@method_decorator(vary_on_cookie)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def get_cache_key(self, request):
"""Generate a cache key based on query parameters"""
query_params = request.query_params.copy()
query_params.pop('page', None) # Don't cache based on page number
return f"vehicles:list:{hash(frozenset(query_params.items()))}"
def get_queryset(self):
cache_key = self.get_cache_key(self.request)
queryset = cache.get(cache_key)
if queryset is None:
queryset = super().get_queryset()
cache.set(cache_key, queryset, timeout=CACHE_TTL)
return querysetModel-Level Caching
Cache frequently accessed model instances and querysets:
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
class Vehicle(models.Model):
# ... model fields ...
def get_cache_key(self):
return f"vehicle:{self.id}"
@classmethod
def get_by_id(cls, vehicle_id):
cache_key = f"vehicle:{vehicle_id}"
vehicle = cache.get(cache_key)
if vehicle is None:
try:
vehicle = cls.objects.get(id=vehicle_id)
cache.set(cache_key, vehicle, timeout=CACHE_TTL)
except cls.DoesNotExist:
return None
return vehicle
def invalidate_vehicle_cache(sender, instance, **kwargs):
"""Invalidate cache when vehicle is updated or deleted"""
cache.delete(instance.get_cache_key())
cache.delete('vehicles:list') # Invalidate list cache
post_save.connect(invalidate_vehicle_cache, sender=Vehicle)
post_delete.connect(invalidate_vehicle_cache, sender=Vehicle)Template Fragment Caching
Cache specific parts of templates:
{% load cache %}
{% cache 300 vehicle_stats vehicle.id %}
<div class="stats">
<h3>Vehicle Statistics</h3>
{{ vehicle.get_complex_statistics }}
</div>
{% endcache %}Query Optimization
Optimize database queries using various techniques:
# 1. Use select_related for foreign keys
vehicles = Vehicle.objects.select_related('owner', 'manufacturer')
# 2. Use prefetch_related for reverse relationships
vehicles = Vehicle.objects.prefetch_related(
'maintenance_tasks',
'service_history'
)
# 3. Use Prefetch objects for custom prefetching
from django.db.models import Prefetch
vehicles = Vehicle.objects.prefetch_related(
Prefetch(
'maintenance_tasks',
queryset=MaintenanceTask.objects.filter(status='pending'),
to_attr='pending_tasks'
)
)
# 4. Use database functions for complex calculations
from django.db.models import F, ExpressionWrapper, DurationField
from django.utils import timezone
vehicles = Vehicle.objects.annotate(
days_since_maintenance=ExpressionWrapper(
timezone.now() - F('last_maintenance'),
output_field=DurationField()
)
)Performance Monitoring
Monitor performance using Django Debug Toolbar and custom middleware:
# middleware.py
import time
from django.db import connection
class QueryCountMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.time()
initial_queries = len(connection.queries)
response = self.get_response(request)
total_time = time.time() - start_time
total_queries = len(connection.queries) - initial_queries
if total_queries > 10 or total_time > 1.0:
print(f"""
Path: {request.path}
Queries: {total_queries}
Time: {total_time:.2f}s
""")
return responseBest Practices
- Use appropriate cache backend for your use case (Redis, Memcached)
- Set reasonable cache timeouts based on data volatility
- Implement cache invalidation strategies
- Monitor cache hit rates and adjust accordingly
- Use database indexes for frequently filtered fields
- Optimize database queries using select_related and prefetch_related
- Implement proper cache versioning for deployments
Common Pitfalls
- N+1 Queries: Always check for and fix N+1 query patterns
- Over-caching: Don't cache everything, focus on expensive operations
- Cache Invalidation: Implement proper cache invalidation to prevent stale data
- Memory Usage: Monitor cache memory usage and set appropriate limits