Django 성능 향상을 위한 캐싱: 프로퍼티부터 Redis까지
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
웹 개발의 세계에서 애플리케이션의 속도와 응답성은 매우 중요합니다. 사용자들은 빠르게 로딩되는 페이지와 원활한 상호작용을 기대하며, 느린 애플리케이션은 불만과 이탈로 이어질 수 있습니다. Django 개발자에게는 종종 데이터베이스 쿼리부터 템플릿 렌더링까지, 애플리케이션의 모든 측면을 최적화할 방법을 찾아야 합니다. 캐싱은 비용이 많이 드는 계산이나 데이터 검색 결과를 저장하여 동일한 정보에 대한 후속 요청을 훨씬 더 빠르게 처리할 수 있게 함으로써 이 문제를 해결하는 강력한 기술입니다. 이 문서는 Django의 포괄적인 캐싱 프레임워크를 자세히 살펴보고, cached_property를 사용한 개별 객체 속성 최적화부터 템플릿 렌더링 가속화 및 Redis와 같은 고성능 외부 캐시 통합까지 다양한 측면을 탐구합니다. 실용적인 예제를 통해 이러한 개념을 풀어내고, Django 애플리케이션의 성능과 확장성을 크게 향상시키기 위해 캐싱을 효과적으로 구현하는 방법을 시연할 것입니다.
Django 캐싱 활용
구체적인 내용을 살펴보기 전에 Django 캐싱과 관련된 주요 용어에 대한 공통된 이해를 확립해 봅시다.
- 캐시 백엔드: 캐시된 데이터가 상주하는 저장 메커니즘입니다. Django는 로컬 메모리, 파일 기반, 데이터베이스 및 Memcached 또는 Redis와 같은 외부 솔루션을 포함한 다양한 백엔드를 지원합니다.
- 캐시 키: 캐시된 데이터 조각의 고유 식별자입니다. 캐시에 데이터를 저장할 때 키를 연관시킵니다. 해당 데이터를 검색하고 싶을 때 동일한 키를 사용합니다.
- 캐시 타임아웃: 캐시된 항목이 유효하게 유지되는 시간(초)입니다. 이 기간이 지나면 항목은 만료된 것으로 간주되며 요청 시 다시 계산되거나 다시 검색됩니다.
- 캐시 무효화: 캐시된 데이터를 제거하거나 신선하지 않은 것으로 표시하는 프로세스입니다. 이는 기본 데이터가 변경될 때 사용자가 항상 최신 정보를 볼 수 있도록 보장하는 데 중요합니다.
Django는 애플리케이션의 다양한 계층에 적용할 수 있는 유연하고 강력한 캐싱 프레임워크를 제공합니다. 몇 가지 주요 영역을 살펴보겠습니다.
cached_property를 사용한 객체 속성 최적화
종종 Django 모델에는 속성마다 계산이나 데이터베이스 조회가 포함되는 프로퍼티가 있을 수 있습니다. 이 프로퍼티가 요청 내에서 여러 번 액세스되면 불필요한 오버헤드가 발생할 수 있습니다. cached_property(Django의 django.utils.functional 모듈에서 사용 가능)는 객체 인스턴스의 수명 동안 프로퍼티의 첫 번째 액세스 결과를 캐싱하는 우아한 솔루션을 제공합니다.
평균 별점을 계산하는 Product 모델을 생각해 봅시다.
# models.py from django.db import models from django.utils.functional import cached_property class Product(models.Model): name = models.CharField(max_length=200) description = models.TextField() def _calculate_average_rating(self): # 복잡한 계산 또는 데이터베이스 쿼리 시뮬레이션 print(f"{self.name}의 평균 평점 계산 중...") ratings = self.review_set.all().values_list('rating', flat=True) return sum(ratings) / len(ratings) if ratings else 0 @cached_property def average_rating(self): return self._calculate_average_rating() class Review(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE) rating = models.IntegerField() comment = models.TextField()
이 예에서 @cached_property는 product.average_rating이 각 Product 인스턴스당 한 번만 계산되도록 보장합니다. 동일한 인스턴스 내에서 후속 액세스는 캐시된 값을 반환합니다.
# 셸 또는 뷰 내부 product = Product.objects.first() print(product.average_rating) # 출력: ProductX의 평균 평점 계산 중... (첫 번째 시도) print(product.average_rating) # 출력: (즉시 캐시된 값 반환)
이것은 인수가 없고 결과가 인스턴스의 상태에만 의존하는 메서드에 특히 유용합니다.
템플릿 캐싱: 페이지 렌더링 가속화
Django의 템플릿 캐싱을 사용하면 HTML 템플릿의 전체 섹션이나 전체 페이지를 캐싱하여 응답 렌더링에 걸리는 시간을 크게 줄일 수 있습니다. 이는 네비게이션 바, 푸터 또는 정적 콘텐츠 블록과 같이 자주 변경되지 않는 사이트 부분에 특히 효과적입니다.
템플릿 캐싱을 활성화하려면 먼저 settings.py에서 캐시 백엔드를 구성해야 합니다. 시작점으로 로컬 메모리 캐시를 사용해 봅시다.
# settings.py CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } }
이제 {% cache %} 템플릿 태그를 사용할 수 있습니다.
<!-- my_template.html --> {% load cache %} <h1>우리 사이트에 오신 것을 환영합니다!</h1> {% cache 500 sidebar %} <div class="sidebar"> <!-- 거의 변경되지 않는 콘텐츠, 예: 카테고리 목록, 광고 --> <h3>카테고리</h3> <ul> {% for category in categories %} <li>{{ category.name }}</li> {% endfor %} </ul> <p>이 사이드바는 500초 동안 캐시됩니다.</p> </div> {% endcache %} <div class="main-content"> <!-- 동적 콘텐츠 --> <p>마지막 업데이트: {{ current_time }}</p> </div>
{% cache %} 태그는 최소 두 개의 인수, 즉 초 단위 타임아웃과 고유한 프래그먼트 이름(예: sidebar)을 받습니다. {% cache %} 블록 내의 모든 콘텐츠는 캐시됩니다. 템플릿이 처음 렌더링될 때 categories가 가져와집니다. 후속 요청(500초 이내)에서 sidebar 프래그먼트가 요청되면 캐시된 HTML이 직접 제공되어 {% for category in categories %} 루프와 데이터베이스 쿼리를 건너뜁니다.
사용자 ID 또는 객체의 기본 키와 같은 추가 인수를 캐시 태그에 전달하여 더 구체적인 캐시 키를 만들 수 있습니다.
{% cache 3600 product_detail_123 product.pk %} <!-- 특정 상품에 대한 콘텐츠 --> {% endcache %}
여기서 product_detail_123 product.pk는 상품의 기본 키를 기반으로 고유한 캐시 키를 생성하여 각 상품의 상세 페이지가 독립적으로 캐시되도록 보장합니다.
Redis 통합으로 강력한 캐싱
LocMemCache는 개발 및 소규모 애플리케이션에 적합하지만, 프로덕션 환경, 특히 여러 애플리케이션 서버가 있는 환경에서는 더 강력하고 공유되는 캐시 백엔드가 필수적입니다. Redis는 속도, 다용성 및 다양한 데이터 구조 지원 덕분에 훌륭한 선택입니다.
Django에서 Redis를 캐시로 사용하려면 일반적으로 django-redis와 같은 패키지를 사용합니다. 먼저 설치합니다.
pip install django-redis
그런 다음 settings.py를 구성합니다.
# settings.py CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", # 캐싱에 데이터베이스 1 사용 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100}, } }, # 다른 용도로 여러 캐시 백엔드를 정의할 수 있습니다. "fast_cache": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/2", # 다른 캐시를 위해 데이터베이스 2 사용 "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, "TIMEOUT": 60 * 5 # 5분 타임아웃 } }
이제 Django의 모든 캐싱 작업( {% cache %} 태그 및 직접 cache API 호출 포함)은 Redis를 활용합니다.
Django의 캐시 API를 직접 사용하여 캐시에 액세스할 수도 있습니다.
from django.core.cache import cache # 300초 타임아웃으로 기본 캐시에 값 설정 cache.set('my_data_key', {'foo': 'bar'}, 300) # 캐시에서 값 가져오기 value = cache.get('my_data_key') print(value) # {'foo': 'bar'} # 캐시에서 값 삭제 cache.delete('my_data_key') # 특정 캐시 백엔드에 값 설정 from django.core.cache import caches fast_cache = caches['fast_cache'] fast_cache.set('my_other_key', 'some_fast_data', 60)
이 직접 API 상호 작용은 복잡한 API 호출 결과, 자주 액세스되는 데이터베이스 쿼리 세트 또는 사용자 세션 데이터를 캐싱하는 데 매우 강력합니다.
cache_page 데코레이터를 사용한 저수준 캐싱
전체 뷰 응답을 캐싱하기 위해 Django는 cache_page 데코레이터를 제공합니다. 이것은 지정된 기간 동안 뷰 함수 또는 메서드의 HTML 출력을 캐싱하는 편리한 방법입니다.
# views.py from django.views.decorators.cache import cache_page from django.shortcuts import render import datetime @cache_page(60 * 15) # 15분(900초) 동안 캐시 def my_cached_view(request): now = datetime.datetime.now() context = {'current_time': now} return render(request, 'my_template.html', context)
my_cached_view에 처음 액세스하면 템플릿이 렌더링되고 전체 HTTP 응답이 캐시에 저장됩니다. 15분 창 내의 후속 요청은 뷰 로직을 실행하거나 템플릿을 다시 렌더링하지 않고 캐시된 응답을 직접 반환합니다.
이것은 대부분 정적이거나 드물게 업데이트되는 페이지에 강력한 도구이며 서버 부하를 크게 줄입니다.
결론
Django의 캐싱 프레임워크는 웹 애플리케이션의 성능과 확장성을 크게 향상시키도록 설계된 정교하고 고도로 사용자 정의 가능한 시스템입니다. 객체 수준 최적화를 위한 cached_property, 템플릿 조각 캐싱을 위한 {% cache %} 태그, 전체 페이지 캐싱을 위한 cache_page와 같은 기술을 전략적으로 사용하고, Redis와 같은 고성능 솔루션으로 모든 것을 지원함으로써 개발자는 다양한 부하에서도 애플리케이션이 빠르고 반응적으로 유지되도록 보장할 수 있습니다. 효과적인 캐싱은 단순히 최적화가 아니라 고성능의 탄력적인 Django 애플리케이션을 구축하는 데 중요한 구성 요소입니다.

