실무 고수 팁
Django 프로덕션 환경에서 성능, 캐싱, 보안을 최적화하는 핵심 패턴입니다.
쿼리 최적화
# 1. 필요한 컬럼만 가져오기
# ❌ 모든 컬럼
products = Product.objects.all()
# ✅ 필요한 컬럼만 (values)
products = Product.objects.values("id", "name", "price")
# ✅ 필요한 컬럼만 (only / defer)
products = Product.objects.only("id", "name", "price") # 이 컬럼만 로드
products = Product.objects.defer("description", "metadata") # 이 컬럼 제외
# 2. exists() vs count() — 존재 여부 확인
# ❌ 비효율: 전체 카운트 후 비교
if Product.objects.filter(owner=user).count() > 0:
pass
# ✅ 효율: LIMIT 1 사용
if Product.objects.filter(owner=user).exists():
pass
# 3. bulk 연산 — DB 왕복 최소화
# ❌ 1000번 INSERT
for i in range(1000):
Product.objects.create(name=f"상품 {i}", price=1000 * i, owner=user)
# ✅ 1번 INSERT
products = [
Product(name=f"상품 {i}", price=1000 * i, owner=user)
for i in range(1000)
]
Product.objects.bulk_create(products, batch_size=100)
# ✅ 1번 UPDATE
Product.objects.filter(category=old_cat).bulk_update(
[Product(id=p.id, category=new_cat) for p in products],
fields=["category"],
)
# 4. iterator() — 대량 데이터 메모리 효율
# ❌ 전체 QuerySet을 메모리에 로드
for product in Product.objects.all(): # 100만 건이면 OOM 위험
process(product)
# ✅ 청크 단위 처리
for product in Product.objects.iterator(chunk_size=1000):
process(product)
# 5. 쿼리 디버깅
from django.db import connection
def query_debug(qs):
list(qs) # 평가 실행
for query in connection.queries[-5:]:
print(f"{query['time']}s: {query['sql'][:100]}")
Redis 캐싱
pip install django-redis
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://localhost:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
"TIMEOUT": 300, # 기본 5분
}
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# 캐시 사용 패턴
from django.core.cache import cache
from django.views.decorators.cache import cache_page
from rest_framework.decorators import api_view
from rest_framework.response import Response
# 1. 저수준 캐시 API
def get_popular_products():
cache_key = "popular_products"
products = cache.get(cache_key)
if products is None:
products = list(
Product.objects.filter(is_active=True)
.order_by("-view_count")
.values("id", "name", "price")[:10]
)
cache.set(cache_key, products, timeout=600) # 10분
return products
# 2. cache_page 데코레이터 (뷰 전체 캐시)
@cache_page(60 * 15) # 15분
def product_list_page(request):
...
# 3. 캐시 무효화 패턴
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
@receiver([post_save, post_delete], sender=Product)
def invalidate_product_cache(sender, instance, **kwargs):
cache.delete("popular_products")
cache.delete(f"product_{instance.id}")
cache.delete_pattern("product_list_*") # 패턴 삭제
# 4. 캐시 계층화
def get_product_detail(product_id: int) -> dict:
# L1: 로컬 메모리 (가장 빠름)
from django.core.cache import caches
local_cache = caches["local"]
key = f"product_{product_id}"
data = local_cache.get(key)
if data:
return data
# L2: Redis
data = cache.get(key)
if data:
local_cache.set(key, data, timeout=60)
return data
# L3: DB
product = Product.objects.select_related("category").get(pk=product_id)
data = {"id": product.id, "name": product.name, "price": float(product.price)}
cache.set(key, data, timeout=300)
local_cache.set(key, data, timeout=60)
return data
배포 체크리스트
# settings/production.py
import os
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # 환경 변수에서 로드
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
# 보안 헤더
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_HSTS_SECONDS = 31536000 # 1년
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
# 정적 파일
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
# 로깅
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "verbose",
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
},
"loggers": {
"django": {
"handlers": ["console"],
"level": os.getenv("DJANGO_LOG_LEVEL", "WARNING"),
"propagate": False,
},
},
}
# 배포 전 체크
python manage.py check --deploy # 보안 설정 검사
python manage.py collectstatic --noinput # 정적 파일 수집
python manage.py migrate --check # 미적용 마이그레이션 확인
# gunicorn + nginx
pip install gunicorn
gunicorn myproject.wsgi:application \
--workers 4 \
--worker-class gthread \
--threads 2 \
--bind 0.0.0.0:8000 \
--timeout 30
django-debug-toolbar — 개발 최적화 도구
pip install django-debug-toolbar
# settings/development.py
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
INTERNAL_IPS = ["127.0.0.1"]
# urls.py
import debug_toolbar
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
# 확인 가능한 정보:
# - 실행된 SQL 쿼리 수와 시간
# - 캐시 히트/미스
# - 템플릿 렌더링 시간
# - HTTP 헤더
정리
| 최적화 항목 | 방법 |
|---|---|
| N+1 제거 | select_related, prefetch_related |
| 컬럼 제한 | only(), defer(), values() |
| 배치 처리 | bulk_create(), bulk_update() |
| 대량 순회 | iterator(chunk_size=1000) |
| 자주 조회 | Redis 캐시 + 무효화 전략 |
| 존재 확인 | exists() (count() 대신) |
| 배포 보안 | check --deploy + HTTPS 강제 |
| 쿼리 분석 | django-debug-toolbar |
Django 성능의 핵심은 N+1 쿼리 제거 + 적절한 캐싱입니다.