LogixVast

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 queryset

Model-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 response

Best 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