Back to Blog

Optimizing Django Performance: A Complete Checklist

admin
November 22, 2025 3 min read
9 views
Speed up your Django application with these proven optimization techniques covering database, caching, and frontend.

# Optimizing Django Performance

Slow applications frustrate users and hurt your bottom line. Here's a comprehensive checklist to make your Django app faster.

## Database Optimization

### 1. Use select_related and prefetch_related

The N+1 query problem is the most common performance killer.

```python
# BAD: N+1 queries
for order in Order.objects.all():
print(order.user.email) # Query for each order!

# GOOD: Single query with JOIN
for order in Order.objects.select_related('user'):
print(order.user.email)

# For many-to-many relationships
products = Product.objects.prefetch_related('categories', 'tags')
```

### 2. Add Database Indexes

```python
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_at = models.DateTimeField(db_index=True)

class Meta:
indexes = [
models.Index(fields=['category', 'created_at']),
models.Index(fields=['name', 'is_active']),
]
```

### 3. Use only() and defer()

Fetch only the fields you need:

```python
# Fetch only specific fields
products = Product.objects.only('id', 'name', 'price')

# Exclude heavy fields
products = Product.objects.defer('description', 'long_content')
```

### 4. Bulk Operations

```python
# BAD: Individual inserts
for item in items:
Product.objects.create(**item)

# GOOD: Bulk insert
Product.objects.bulk_create([
Product(**item) for item in items
])

# GOOD: Bulk update
Product.objects.filter(category='old').update(category='new')
```

## Caching Strategies

### 1. Database Query Caching

```python
from django.core.cache import cache

def get_featured_products():
key = 'featured_products'
products = cache.get(key)

if products is None:
products = list(Product.objects.filter(
is_featured=True
).select_related('category')[:10])
cache.set(key, products, 60 * 15) # 15 minutes

return products
```

### 2. View Caching

```python
from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # 15 minutes
def product_list(request):
products = Product.objects.all()
return render(request, 'products.html', {'products': products})
```

### 3. Template Fragment Caching

```django
{% load cache %}

{% cache 500 sidebar request.user.id %}
<!-- Expensive sidebar content -->
{% endcache %}
```

### 4. Redis Configuration

```python
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
```

## Query Analysis

### Django Debug Toolbar

Install and use it in development:

```python
INSTALLED_APPS = [
...
'debug_toolbar',
]

MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
...
]
```

### Query Logging

```python
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
},
}
```

## Frontend Optimization

### 1. Static File Compression

```python
# settings.py
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
```

### 2. Image Optimization

```python
from PIL import Image

def optimize_image(image_path, max_size=(800, 800), quality=85):
img = Image.open(image_path)
img.thumbnail(max_size, Image.LANCZOS)
img.save(image_path, optimize=True, quality=quality)
```

### 3. Lazy Loading

```html
<img src="placeholder.jpg"
data-src="actual-image.jpg"
loading="lazy"
alt="Product">
```

## Application Settings

### 1. Persistent Database Connections

```python
DATABASES = {
'default': {
...
'CONN_MAX_AGE': 600, # 10 minutes
}
}
```

### 2. Session Backend

```python
# Use cache-based sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
```

### 3. Disable Debug in Production

```python
DEBUG = False
```

## Async Views (Django 4.0+)

```python
async def async_product_list(request):
products = await sync_to_async(list)(
Product.objects.filter(is_active=True)[:20]
)
return render(request, 'products.html', {'products': products})
```

## Performance Checklist

### Database
- [ ] Add indexes to frequently queried fields
- [ ] Use select_related/prefetch_related
- [ ] Avoid N+1 queries
- [ ] Use bulk operations
- [ ] Enable connection pooling

### Caching
- [ ] Cache expensive queries
- [ ] Use Redis for session storage
- [ ] Implement template fragment caching
- [ ] Set appropriate cache timeouts

### Frontend
- [ ] Minify CSS/JS
- [ ] Optimize images
- [ ] Use CDN for static files
- [ ] Enable browser caching
- [ ] Lazy load images

### Infrastructure
- [ ] Use production-grade server (Gunicorn)
- [ ] Enable gzip compression
- [ ] Use connection pooling (PgBouncer)
- [ ] Monitor with APM tools

## Measuring Performance

Tools to use:

- **Django Debug Toolbar**: Development queries
- **New Relic / Datadog**: Production monitoring
- **Lighthouse**: Frontend performance
- **Apache Bench**: Load testing

```bash
# Load test with Apache Bench
ab -n 1000 -c 100 http://localhost:8000/products/
```

## Conclusion

Performance optimization is iterative. Measure first, optimize the bottlenecks, and measure again. Don't optimize prematurely—focus on real problems.

Need performance-optimized templates? Check our marketplace!

Comments (0)

Please login to leave a comment.

No comments yet. Be the first to comment!