performance2024-04-28·8 min·309/348

Django Cache 설정 및 활용 가이드

Django의 캐시 프레임워크를 설정하고 다양한 캐싱 전략을 활용하는 방법을 알아봅니다.

Django Cache 설정 및 활용 가이드

Django는 다양한 캐시 백엔드를 지원하며, 적절한 캐싱 전략을 사용하면 애플리케이션 성능을 크게 향상시킬 수 있습니다.

Environment

$ python --version
Python 3.11.5

$ pip show django
Name: Django
Version: 4.2.5

$ pip show django-redis
Name: django-redis
Version: 5.4.0

Problem: 반복적인 DB 쿼리로 인한 성능 저하

# views.py - 매 요청마다 DB 쿼리 실행
def product_list(request):
    # These queries run every time
    products = Product.objects.filter(is_active=True)
    categories = Category.objects.all()
    featured = Product.objects.filter(featured=True)[:5]
    
    return render(request, 'products/list.html', {
        'products': products,
        'categories': categories,
        'featured': featured,
    })

실행 결과:

# Each request executes 3+ queries
[SQL] SELECT * FROM products WHERE is_active = True
[SQL] SELECT * FROM categories
[SQL] SELECT * FROM products WHERE featured = True LIMIT 5

Analysis: Django 캐시 프레임워크 이해

# settings.py - 캐시 설정 구조
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

# Available backends:
# - locmem: Local memory (development)
# - memcached: Memcached server
# - redis: Redis server
# - file: File-based cache
# - database: Database cache

Solution: 다양한 캐시 설정 방법

기본 LocMem 캐시 설정

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'product-cache',
        'TIMEOUT': 300,  # 5 minutes default
        'OPTIONS': {
            'MAX_ENTRIES': 1000,
        }
    }
}

Redis 캐시 설정

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
            'CONNECTION_POOL_KWARGS': {'max_connections': 100},
            'SOCKET_CONNECT_TIMEOUT': 5,
            'SOCKET_TIMEOUT': 5,
        },
        'KEY_PREFIX': 'myproject',
        'TIMEOUT': 300,
    }
}

뷰 레벨 캐싱

from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie

@cache_page(60 * 15)  # Cache for 15 minutes
@vary_on_cookie  # Cache per user
def product_list(request):
    products = Product.objects.filter(is_active=True)
    categories = Category.objects.all()
    return render(request, 'products/list.html', {
        'products': products,
        'categories': categories,
    })

Low-level Cache API

from django.core.cache import cache

def get_products():
    # Try to get from cache
    products = cache.get('active_products')
    
    if products is None:
        # Cache miss - query database
        products = list(Product.objects.filter(is_active=True))
        
        # Store in cache for 10 minutes
        cache.set('active_products', products, timeout=600)
    
    return products

# Cache with version
cache.set('products', products, timeout=600, version=1)

# Get with default value
products = cache.get('products', default=[])

# Delete specific cache
cache.delete('active_products')

# Clear all cache
cache.clear()

Template Fragment Caching

{% load cache %}

{# Cache this fragment for 30 minutes #}
{% cache 300 product_list %}
    {% for product in products %}
        
{{ product.name }}
{% endfor %} {% endcache %} {# Cache per user #} {% cache 300 user_profile request.user.id %}
{{ user.profile }}
{% endcache %}

Low-Level Cache with Stampede Prevention

from django.core.cache import cache
import time

def get_or_set(key, callback, timeout=300):
    """Get from cache or set with callback, preventing cache stampede"""
    value = cache.get(key)
    
    if value is None:
        # Try to acquire lock
        lock_key = f"lock_{key}"
        if cache.add(lock_key, True, timeout=10):
            try:
                # We got the lock, compute value
                value = callback()
                cache.set(key, value, timeout=timeout)
            finally:
                cache.delete(lock_key)
        else:
            # Another process is computing, wait and retry
            time.sleep(0.1)
            return cache.get(key)
    
    return value

# Usage
products = get_or_set(
    'active_products',
    lambda: list(Product.objects.filter(is_active=True)),
    timeout=600
)

Lessons Learned

  1. 캐시 백엔드 선택: 개발 환경은 LocMem, 프로덕션은 Redis나 Memcached를 사용하세요.

  2. 캐시 키 관리: 캐시 키는 명확하고 구조화된 이름을 사용하여 충돌을 방지하세요.

  3. 캐시 무효화: 데이터가 변경될 때 관련 캐시를 명시적으로 삭제하는 것을 잊지 마세요.

  4. 캐시 스탬피드: 여러 프로세스가 동시에 캐시 만료 시 DB를 요청하는 현상을 방지하세요.

  5. 캐시 히트율 모니터링: Django Debug Toolbar로 캐시 히트율을 모니터링하여 효과를 확인하세요.


This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.