Django 캐시 전략과 시그널 메커니즘 활용법

1. 캐시의 필요성과 원리

동적 웹 애플리케이션에서 모든 사용자 요청은 일반적으로 데이터베이스 조회, 로직 처리, 템플릿 렌더링을 거칩니다. 방문자가 급증하면 서버 리소스 소모가 급격히 늘어나며 응답 지연이 발생할 수 있습니다. 이를 해결하기 위해 자주 접근하는 데이터를 메모리나 외부 저장소에 임시 보관하는 캐싱 기법을 사용합니다. 캐시가 유효한 동안에는 실제 비즈니스 로직을 실행하지 않고 미리 저장된 결과를 반환함으로써 성능을 크게 향상시킬 수 있습니다.

2. Django에서 지원하는 캐시 백엔드

  • 개발용 더미 캐시: DEBUG 모드에서 캐시 동작을 시뮬레이션
  • 메모리 기반 캐시: 프로세스 내 메모리 사용 (간단하지만 공유 불가)
  • 파일 시스템 캐시: 지정 디렉터리에 파일 형태로 저장
  • 데이터베이스 캐시: 별도 테이블에 캐시 레코드 저장
  • Memcached + python-memcached
  • Memcached + pylibmc: 고성능 바이너리 프로토콜 지원

실제 운영 환경에서는 Memcached 또는 Redis 기반 솔루션이 가장 일반적입니다.

3. 캐시 적용 범위: 세 가지 레벨

Django는 유연한 캐시 레이어를 제공하며, 다음과 같은 범위로 설정할 수 있습니다:

  1. 전체 사이트 캐싱: 모든 응답에 대해 자동 캐싱
  2. 뷰 단위 캐싱: 특정 URL 패턴에 대한 응답 전체를 캐시
  3. 템플릿 조각 캐싱: 페이지 내 일부 영역만 캐시

4. 파일 기반 캐시 실습 예제

전체 사이트 캐싱 설정

# settings.py
MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',  # 반드시 최상단
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',  # 반드시 최하단
]

CACHE_MIDDLEWARE_SECONDS = 60

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/tmp/django_cache',
        'TIMEOUT': 300,
        'OPTIONS': {
            'MAX_ENTRIES': 500,
            'CULL_FREQUENCY': 2  # 만료 시 절반 제거
        }
    }
}

특정 뷰 캐싱

# views.py
from django.views.decorators.cache import cache_page
from django.shortcuts import render
import time

@cache_page(30)  # 30초 캐시
def home_view(request):
    context = {'server_time': time.strftime('%H:%M:%S')}
    return render(request, 'home.html', context)

템플릿 내 부분 캐싱

<!-- home.html -->
<p>실시간 시간: {{ server_time }}</p>

{% load cache %}
{% cache 10 "sidebar_content" %}
<div class="sidebar">
  <p>자주 변하지 않는 사이드바 정보</p>
  <p>캐시된 시간: {{ server_time }}</p>
</div>
{% endcache %}

5. API 기반 아키텍처에서의 캐시 전략

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.cache import cache
from .models import Product
from .serializers import ProductSerializer

class ProductListView(APIView):
    def get(self, request):
        cache_key = 'product_list_json'
        cached_data = cache.get(cache_key)
        
        if cached_data:
            print("Cache hit")
            return Response(cached_data)
            
        queryset = Product.objects.filter(active=True)
        serializer = ProductSerializer(queryset, many=True)
        
        cache.set(cache_key, serializer.data, timeout=60*5)  # 5분
        print("Cache miss - data stored")
        return Response(serializer.data)

6. Django 시그널 시스템

시그널은 특정 이벤트 발생 시 사전에 등록된 핸들러 함수를 자동으로 호출하는 옵저버 패턴 구현입니다. 주요 내장 시그널은 다음과 같습니다:

핵심 시그널 종류

  • 모델 관련: pre_save, post_save, pre_delete, post_delete, m2m_changed
  • 요청 주기: request_started, request_finished
  • 마이그레이션: pre_migrate, post_migrate
  • 설정 변경: setting_changed

시그널 등록 방법

# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.userprofile.save()
# 수동 연결 방식
from django.core.signals import request_finished
def log_request_finish(sender, **kwargs):
    print("HTTP 요청 처리 완료")

request_finished.connect(log_request_finish)

7. 실무 적용 시나리오

  • 감사 로그: 사용자 계정 생성/삭제 시 기록
  • 외부 서비스 연동: 회원 가입 시 메일 발송 트리거
  • 캐시 무효화: 데이터 갱신 후 관련 캐시 삭제
  • 분석 데이터 수집: 특정 행동 추적

시그널은 관심사 분리를 가능하게 하지만 남용 시 디버깅이 어려워질 수 있으므로, 핵심 비즈니스 로직보다는 부수 작업에 적합합니다.

태그: Django Caching signals REST framework middleware

6월 14일 00:27에 게시됨