Skip to content

Enhancement: Implement Comprehensive API Response Caching Strategy for Performance Optimization #50

@smirk-dev

Description

@smirk-dev

Overview

After thoroughly reviewing the CVImprover API codebase, I've identified a significant opportunity to enhance performance and reduce costs through a comprehensive caching strategy. While the project already has Redis configured for session/cache management and implements VerifyCheckoutSessionView with basic caching, there's substantial room for improvement across multiple endpoints.

Current State Analysis

What I Found:

  1. Limited Caching Implementation: Only VerifyCheckoutSessionView uses caching (with 1-hour TTL)
  2. Redis Already Configured: Infrastructure is ready via django-redis and CACHE_URL
  3. High-Traffic Endpoints Without Caching:
    • PlanListView - Fetches plans on every request
    • CustomUserDetailsView - User profile data
    • RateLimitStatusView - Rate limit info (new endpoint)
    • HealthCheckView - System health checks
  4. Expensive Operations: OpenAI API calls, Stripe API interactions, and database queries that could benefit from intelligent caching

Proposed Solution

Implement a multi-layered caching strategy that balances performance with data freshness:

1. View-Level Caching (High Priority)

A. Plan Data Caching

Target: PlanListView in core/views.py

  • Rationale: Plan data changes infrequently (only when admins update pricing/features)
  • Implementation:
    from django.views.decorators.cache import cache_page
    from django.utils.decorators import method_decorator
    
    @method_decorator(cache_page(60 * 60 * 24), name='dispatch')  # 24 hours
    class PlanListView(ListAPIView):
        # existing code
  • Cache Invalidation: Add signal handlers to clear cache when Plan objects are updated
  • Expected Impact: Reduces database queries by ~95% for this endpoint

B. User Profile Caching

Target: CustomUserDetailsView

  • Strategy: Per-user caching with 15-minute TTL
  • Implementation:
    def get(self, request, *args, **kwargs):
        cache_key = f'user_profile_{request.user.id}'
        cached_data = cache.get(cache_key)
        
        if cached_data:
            return Response(cached_data)
        
        response = super().get(request, *args, **kwargs)
        cache.set(cache_key, response.data, timeout=60 * 15)  # 15 minutes
        return response
  • Expected Impact: Reduces load on authentication/user queries

C. Rate Limit Status Caching

Target: RateLimitStatusView

  • Current Issue: Fetches rate limit data for every request
  • Strategy: Short-lived cache (1-2 minutes) to reduce Redis calls
  • Implementation:
    cache_key = f'rate_limit_status_{user.id}_{int(time.time() / 60)}'
  • Expected Impact: Reduces rate limit calculations while maintaining accuracy

2. Query-Level Caching (Medium Priority)

A. Questionnaire Queries

Target: CVQuestionnaireViewSet.get_queryset()

  • Strategy: Cache user's questionnaire list with 5-minute TTL
  • Invalidation: Clear on create/update/delete operations

B. AI Response Queries

Target: AIResponseViewSet.get_queryset()

  • Strategy: Similar to questionnaires - short-lived cache

3. External API Result Caching (High Priority)

A. Stripe Checkout Session

Already Implemented ✅ - Good pattern to follow!

  • Current implementation in VerifyCheckoutSessionView is excellent
  • Can be extended to other Stripe operations

B. OpenAI Response Caching (Consideration)

Target: AIResponseViewSet.create()

  • Approach: Cache similar prompts (using hash of questionnaire + prompt)
  • Benefits: Saves OpenAI API costs, faster responses
  • Considerations:
    • Need careful cache key design
    • Balance between cost savings and response uniqueness
    • Implement TTL of 7-30 days for stale content removal

4. Health Check Optimization

Target: HealthCheckView

  • Current Issue: Checks DB, Redis, and OpenAI on every request
  • Strategy: Cache health status for 30 seconds
  • Benefit: Allows frequent monitoring without overwhelming services

Implementation Plan

Phase 1: Quick Wins (Week 1)

  1. Implement Plan list caching with signal-based invalidation
  2. Add user profile caching
  3. Optimize health check endpoint

Phase 2: Advanced Caching (Week 2)

  1. Implement rate limit status caching
  2. Add query-level caching for questionnaires/responses
  3. Create cache management utilities (clear_user_cache, etc.)

Phase 3: Monitoring & Optimization (Week 3)

  1. Add cache hit/miss metrics
  2. Implement cache warming for frequently accessed data
  3. Fine-tune TTL values based on usage patterns

Files to Modify

core/
├── views.py           # Add caching to PlanListView, CustomUserDetailsView, etc.
├── signals.py         # Add cache invalidation signals
├── cache_utils.py     # NEW: Centralized cache management
└── tests/
    └── test_caching.py  # NEW: Comprehensive cache tests

cv/
├── views.py           # Add caching to viewsets
└── tests/
    └── test_caching.py  # NEW: CV-specific cache tests

cvimprover/
└── settings.py        # Add cache configuration options

Testing Strategy

  1. Unit Tests:

    • Verify cache hits/misses
    • Test cache invalidation
    • Verify TTL behavior
  2. Integration Tests:

    • Test cache consistency across multiple requests
    • Verify signal-based invalidation
  3. Performance Tests:

    • Measure response time improvement
    • Monitor Redis memory usage

Expected Benefits

  1. Performance:

    • 40-60% reduction in response times for cached endpoints
    • Reduced database load
    • Better handling of traffic spikes
  2. Cost Reduction:

    • Fewer database queries
    • Reduced OpenAI API calls (if implemented)
    • Lower infrastructure costs
  3. Scalability:

    • Better resource utilization
    • Improved support for concurrent users
  4. User Experience:

    • Faster page loads
    • More responsive API

Configuration Examples

Django Settings Enhancement

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': os.getenv('CACHE_URL'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'PARSER_CLASS': 'redis.connection.HiredisParser',
            'CONNECTION_POOL_CLASS_KWARGS': {
                'max_connections': 50,
                'retry_on_timeout': True,
            }
        },
        'KEY_PREFIX': 'cvimprover',
        'TIMEOUT': 300,  # Default 5 minutes
    }
}

# Cache timeouts for different data types
CACHE_TIMEOUTS = {
    'plans': 60 * 60 * 24,      # 24 hours
    'user_profile': 60 * 15,    # 15 minutes  
    'rate_limits': 60 * 2,      # 2 minutes
    'health_check': 30,         # 30 seconds
    'questionnaires': 60 * 5,   # 5 minutes
}

Potential Concerns & Mitigations

  1. Stale Data:

    • Mitigation: Implement proper cache invalidation via signals
    • Use appropriate TTLs based on data volatility
  2. Memory Usage:

    • Mitigation: Monitor Redis memory, implement cache eviction policies
    • Use Redis maxmemory policies (e.g., allkeys-lru)
  3. Cache Stampede:

    • Mitigation: Implement cache warming and staggered invalidation
    • Use cache locks for expensive operations
  4. Testing Complexity:

    • Mitigation: Create comprehensive test fixtures
    • Use @override_settings for cache-related tests

Why This Enhancement?

  1. Production Ready: The codebase is mature with proper logging, error handling, and monitoring
  2. Infrastructure Available: Redis is already configured and working
  3. High Impact: Significant performance gains with moderate effort
  4. Best Practices: Follows Django/DRF caching patterns
  5. Hacktoberfest Ready: Clear scope, well-defined tasks, good for intermediate contributors

Additional Resources

Contributing

I'm happy to work on this enhancement and would appreciate guidance on:

  1. Preferred cache TTL values for different endpoints
  2. Whether OpenAI response caching is desired
  3. Monitoring/metrics preferences

Labels: enhancement, performance, good first issue, hacktoberfest


Note: This issue is part of my Hacktoberfest 2025 contribution. I've thoroughly analyzed the codebase and am confident this enhancement aligns with the project's architecture and goals.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions