Django에서 Redis 캐싱 전략 구현하기

Django에서 Redis 캐싱 전략 구현하기

안녕하세요! 오늘은 Django 프로젝트에서 Redis를 활용한 효율적인 캐싱 전략을 구현하는 방법에 대해 알아보겠습니다.

왜 Redis를 사용할까요?

Redis를 캐싱 솔루션으로 선택하는 이유는 다음과 같습니다:

  1. 빠른 성능: 인메모리 데이터베이스로 초고속 데이터 접근
  2. 데이터 구조 다양성: 문자열, 해시, 리스트, 세트 등 다양한 데이터 구조 지원
  3. 영구성: 데이터를 디스크에 저장하여 서버 재시작 시에도 데이터 유지
  4. 분산 시스템 지원: 클러스터링을 통한 확장성
  5. 풍부한 기능: TTL, Pub/Sub, 트랜잭션 등 다양한 기능 제공

1. 기본 설정

1.1 Redis 설치

# Ubuntu/Debian
sudo apt-get install redis-server

# macOS
brew install redis

1.2 Django 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",
            "PARSER_CLASS": "redis.connection.HiredisParser",
            "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
            "CONNECTION_POOL_CLASS_KWARGS": {
                "max_connections": 50,
                "timeout": 20
            },
            "MAX_CONNECTIONS": 1000,
            "COMPRESSOR_CLASS": "django_redis.compressors.zlib.ZlibCompressor",
        }
    }
}

# 세션 저장소로 Redis 사용
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

2. 기본 캐싱 패턴

2.1 뷰 레벨 캐싱

from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator

@method_decorator(cache_page(60 * 15), name='dispatch')  # 15분 캐싱
class ProductListView(ListView):
    model = Product
    template_name = 'products/list.html'

    def get_queryset(self):
        return Product.objects.filter(is_active=True)

2.2 템플릿 프래그먼트 캐싱

{% load cache %}

{% cache 500 sidebar %}
    <div class="sidebar">
        {% for category in categories %}
            <a href="{{ category.get_absolute_url }}">{{ category.name }}</a>
        {% endfor %}
    </div>
{% endcache %}

2.3 로우 레벨 캐싱

from django.core.cache import cache

def get_product_data(product_id):
    cache_key = f'product_{product_id}'
    data = cache.get(cache_key)

    if data is None:
        data = Product.objects.get(id=product_id)
        cache.set(cache_key, data, timeout=3600)  # 1시간 캐싱

    return data

3. 고급 캐싱 전략

3.1 캐시 버전 관리

def get_cached_data(key, version=1):
    cache_key = f'{key}_v{version}'
    return cache.get(cache_key)

def invalidate_cache(key, version=1):
    cache_key = f'{key}_v{version}'
    cache.delete(cache_key)

3.2 캐시 프리패칭

from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Product)
def prefetch_product_cache(sender, instance, **kwargs):
    cache_key = f'product_{instance.id}'
    cache.set(cache_key, instance, timeout=3600)

3.3 조건부 캐싱

def get_conditional_cached_data(key, condition_func):
    data = cache.get(key)

    if data is None or not condition_func(data):
        data = fetch_data()
        cache.set(key, data, timeout=3600)

    return data

4. Redis 특화 기능 활용

4.1 해시 데이터 구조 활용

def cache_product_details(product_id, details):
    cache_key = f'product_details:{product_id}'
    cache.hmset(cache_key, details)
    cache.expire(cache_key, 3600)  # 1시간 TTL

def get_product_details(product_id):
    cache_key = f'product_details:{product_id}'
    return cache.hgetall(cache_key)

4.2 리스트 활용 (최근 조회)

def add_to_recently_viewed(user_id, product_id):
    cache_key = f'recently_viewed:{user_id}'
    cache.lpush(cache_key, product_id)
    cache.ltrim(cache_key, 0, 9)  # 최근 10개만 유지

4.3 세트 활용 (유니크 방문자)

def track_unique_visitors(page_id, visitor_id):
    cache_key = f'unique_visitors:{page_id}'
    cache.sadd(cache_key, visitor_id)
    cache.expire(cache_key, 86400)  # 24시간 TTL

5. 성능 최적화

5.1 캐시 압축

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "COMPRESSOR_CLASS": "django_redis.compressors.zlib.ZlibCompressor",
            "COMPRESSOR_CLASS_KWARGS": {
                "level": 5
            }
        }
    }
}

5.2 연결 풀링

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
            "CONNECTION_POOL_CLASS_KWARGS": {
                "max_connections": 50,
                "timeout": 20
            }
        }
    }
}

6. 모니터링 및 디버깅

6.1 캐시 히트율 모니터링

from django.core.cache import cache
from django.db import connection

def get_cache_stats():
    info = cache.client.info()
    return {
        'hits': info.get('keyspace_hits', 0),
        'misses': info.get('keyspace_misses', 0),
        'hit_rate': info.get('keyspace_hits', 0) / 
                   (info.get('keyspace_hits', 0) + info.get('keyspace_misses', 0))
    }

6.2 캐시 키 관리

def get_cache_keys(pattern='*'):
    return cache.client.keys(pattern)

def delete_cache_keys(pattern='*'):
    keys = cache.client.keys(pattern)
    if keys:
        cache.client.delete(*keys)

7. 실전 예제

7.1 상품 목록 캐싱

from django.core.cache import cache
from django.db.models import Prefetch

def get_cached_products(category_id):
    cache_key = f'products_category_{category_id}'
    products = cache.get(cache_key)

    if products is None:
        products = Product.objects.filter(
            category_id=category_id,
            is_active=True
        ).select_related(
            'category'
        ).prefetch_related(
            Prefetch('images', queryset=ProductImage.objects.filter(is_primary=True))
        )
        cache.set(cache_key, products, timeout=3600)

    return products

7.2 사용자 세션 관리

from django.core.cache import cache
from django.contrib.auth import get_user_model

def get_user_sessions(user_id):
    cache_key = f'user_sessions:{user_id}'
    sessions = cache.get(cache_key)

    if sessions is None:
        sessions = Session.objects.filter(
            session_data__contains=str(user_id)
        ).values_list('session_key', flat=True)
        cache.set(cache_key, sessions, timeout=300)

    return sessions

결론

Django에서 Redis를 활용한 캐싱 전략을 구현하는 방법을 알아보았습니다. 적절한 캐싱 전략을 통해 애플리케이션의 성능을 크게 향상시킬 수 있습니다. 추가적인 질문이나 궁금한 점이 있으시면 댓글로 남겨주세요!

댓글