Skip to main content
Advertisement

Pro Tips for Production

Key patterns for optimizing performance, caching, and security in Django production environments.


Query Optimization

# 1. Fetch only required columns
# ❌ All columns
products = Product.objects.all()

# ✅ Only needed columns (values)
products = Product.objects.values("id", "name", "price")

# ✅ Only needed columns (only / defer)
products = Product.objects.only("id", "name", "price") # load only these columns
products = Product.objects.defer("description", "metadata") # exclude these columns


# 2. exists() vs count() — checking existence
# ❌ Inefficient: count all then compare
if Product.objects.filter(owner=user).count() > 0:
pass

# ✅ Efficient: uses LIMIT 1
if Product.objects.filter(owner=user).exists():
pass


# 3. Bulk operations — minimize DB round trips
# ❌ 1000 INSERT calls
for i in range(1000):
Product.objects.create(name=f"Product {i}", price=1000 * i, owner=user)

# ✅ 1 INSERT call
products = [
Product(name=f"Product {i}", price=1000 * i, owner=user)
for i in range(1000)
]
Product.objects.bulk_create(products, batch_size=100)

# ✅ 1 UPDATE call
Product.objects.filter(category=old_cat).bulk_update(
[Product(id=p.id, category=new_cat) for p in products],
fields=["category"],
)


# 4. iterator() — memory efficiency for large datasets
# ❌ Loads entire QuerySet into memory
for product in Product.objects.all(): # risk of OOM with 1M rows
process(product)

# ✅ Process in chunks
for product in Product.objects.iterator(chunk_size=1000):
process(product)


# 5. Query debugging
from django.db import connection

def query_debug(qs):
list(qs) # trigger evaluation
for query in connection.queries[-5:]:
print(f"{query['time']}s: {query['sql'][:100]}")

Redis Caching

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, # default 5 minutes
}
}

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# Cache usage patterns
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. Low-level cache 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 minutes

return products


# 2. cache_page decorator (full view cache)
@cache_page(60 * 15) # 15 minutes
def product_list_page(request):
...


# 3. Cache invalidation pattern
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_*") # pattern delete


# 4. Cache layering
def get_product_detail(product_id: int) -> dict:
# L1: Local memory (fastest)
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

Deployment Checklist

# settings/production.py
import os

DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # load from environment variable
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")

# Security headers
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

# Static files
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

# Logging
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,
},
},
}
# Pre-deployment checks
python manage.py check --deploy # security settings check
python manage.py collectstatic --noinput # collect static files
python manage.py migrate --check # check for unapplied migrations

# 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 — Development Optimization Tool

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))]

# Information available:
# - Number and timing of SQL queries executed
# - Cache hits/misses
# - Template rendering time
# - HTTP headers

Summary

Optimization AreaMethod
Eliminate N+1select_related, prefetch_related
Limit columnsonly(), defer(), values()
Batch operationsbulk_create(), bulk_update()
Large iterationiterator(chunk_size=1000)
Frequent queriesRedis cache + invalidation strategy
Existence checkexists() (instead of count())
Deployment securitycheck --deploy + enforce HTTPS
Query analysisdjango-debug-toolbar

The key to Django performance is eliminating N+1 queries + appropriate caching.

Advertisement