django-performance
SKILL.md
Django Performance Optimization
You are a Django performance expert. Your goal is to identify and fix performance bottlenecks.
Initial Assessment
Check for project context first:
If .agents/django-project-context.md exists, read it for database backend, cache backend, and key models.
Before optimizing, measure. The most impactful fixes are almost always:
- N+1 query problems →
select_related/prefetch_related - Missing database indexes
- Unoptimized QuerySets loading too much data
- Missing caching on expensive reads
Diagnosing N+1 Queries
Django Debug Toolbar (Development)
pip install django-debug-toolbar
# settings/dev.py
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
INTERNAL_IPS = ['127.0.0.1']
# urls.py (dev only)
if settings.DEBUG:
import debug_toolbar
urlpatterns = [path('__debug__/', include(debug_toolbar.urls))] + urlpatterns
Count Queries in Code
from django.db import connection, reset_queries
from django.conf import settings
settings.DEBUG = True
reset_queries()
# ... run your code ...
print(f"Query count: {len(connection.queries)}")
for q in connection.queries:
print(q['sql'])
select_related (Follow ForeignKey/OneToOne)
Use when accessing a ForeignKey or OneToOne field on multiple objects.
# BAD — N+1: 1 query for articles + 1 per article for author
articles = Article.objects.all()
for article in articles:
print(article.author.email) # Extra query per article
# GOOD — 1 JOIN query
articles = Article.objects.select_related('author').all()
# Multiple levels
articles = Article.objects.select_related('author__profile').all()
# Multiple relationships
articles = Article.objects.select_related('author', 'category').all()
prefetch_related (Follow ManyToMany / Reverse FK)
Use when accessing ManyToMany or reverse ForeignKey relationships.
# BAD — N+1: separate query for each article's tags
articles = Article.objects.all()
for article in articles:
tags = article.tags.all() # Extra query per article
# GOOD — 2 queries total (articles + all their tags)
articles = Article.objects.prefetch_related('tags').all()
# Prefetch with filtering using Prefetch object
from django.db.models import Prefetch
articles = Article.objects.prefetch_related(
Prefetch(
'comments',
queryset=Comment.objects.filter(approved=True).select_related('author'),
to_attr='approved_comments',
)
).all()
# Then in template/code:
for article in articles:
for comment in article.approved_comments: # No extra query
...
QuerySet Optimization
Only Fetch What You Need
# Fetch only specific fields (returns dicts)
Article.objects.values('id', 'title', 'status')
# Fetch only specific fields (returns model instances, defers others)
Article.objects.only('id', 'title', 'status')
# Explicitly defer fields you don't need
Article.objects.defer('content') # Skip large content field
# Existence check (don't use .count() or len())
if Article.objects.filter(author=user).exists():
...
# Count efficiently
count = Article.objects.filter(status='published').count()
# Aggregate
from django.db.models import Count, Avg, Sum
stats = Article.objects.aggregate(
total=Count('id'),
avg_views=Avg('view_count'),
)
Bulk Operations
# BAD — N queries
for title in titles:
Article.objects.create(title=title, author=user)
# GOOD — 1 query
Article.objects.bulk_create([
Article(title=title, author=user) for title in titles
])
# Bulk update
Article.objects.filter(status='draft').update(status='archived')
# NEVER use update() when you need signals or save() logic
Database Indexes
class Article(models.Model):
status = models.CharField(max_length=20, db_index=True) # Single field index
author = models.ForeignKey(User, on_delete=models.CASCADE) # FK auto-indexed
created_at = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(unique=True) # unique=True also creates index
class Meta:
indexes = [
# Composite index for common filter + order pattern
models.Index(fields=['status', '-created_at'], name='article_status_date_idx'),
# Index for author list ordered by date
models.Index(fields=['author', '-created_at'], name='article_author_date_idx'),
]
When to add an index:
- Fields used frequently in
filter(),exclude(), orget() - Fields used in
order_by() - Fields used in
JOINconditions (FKs are auto-indexed) - Composite indexes for multi-field filter patterns
Check with EXPLAIN: Use Django's queryset.explain() to see if indexes are being used.
Caching
Per-View Caching
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 15 minutes
def article_list(request):
...
Low-Level Cache API
from django.core.cache import cache
def get_published_articles():
cache_key = 'published_articles'
articles = cache.get(cache_key)
if articles is None:
articles = list(Article.objects.published().select_related('author'))
cache.set(cache_key, articles, timeout=300) # 5 minutes
return articles
# Invalidate on change
def publish_article(article):
article.status = Article.Status.PUBLISHED
article.save()
cache.delete('published_articles')
cache.delete(f'article_{article.pk}')
Cache Settings (Redis)
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': env('REDIS_URL', default='redis://127.0.0.1:6379/1'),
}
}
Template Performance
# Avoid database calls in templates — pass everything from the view
# BAD in template:
{% for article in article.comments.all %} {# Hidden query #}
# GOOD — prefetch in view, iterate in template:
# view: articles = Article.objects.prefetch_related('comments').all()
{% for comment in article.comments.all %} {# Uses prefetch cache #}
For comprehensive query optimization patterns: See references/query-optimization.md
Related Skills
- django-models: Adding indexes, designing efficient model structure
- django-debug: Django Debug Toolbar setup and query profiling
- django-drf: Optimizing
get_queryset()in ViewSets - django-deployment: Production caching configuration
Weekly Installs
2
Repository
ristemingov/dja…de-setupFirst Seen
5 days ago
Security Audits
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2