Django Performance Issue: N+1 Foreign Key Access

Low Risk Performance
djangopythonperformancen-plus-oneforeign-keysdatabaseorm

What it is

The Django application accesses foreign key relationships without proper optimization, leading to N+1 query problems that can cause performance degradation and potential denial of service. While primarily a performance issue, it can become a security concern when it enables resource exhaustion attacks.

# Vulnerable: N+1 query problem in Django from django.shortcuts import render from .models import Author, Book # Model definitions class Author(models.Model): name = models.CharField(max_length=100) email = models.EmailField() class Book(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey(Author, on_delete=models.CASCADE) publication_date = models.DateField() # Problematic view - N+1 queries def book_list_view(request): books = Book.objects.all() # 1 query book_data = [] for book in books: # N additional queries (one per book) book_data.append({ 'title': book.title, 'author_name': book.author.name, # Triggers additional query 'author_email': book.author.email, # Same author, but still hits DB 'publication_date': book.publication_date }) return render(request, 'books/list.html', {'books': book_data}) # Another problematic pattern def author_books_view(request): authors = Author.objects.all() # 1 query author_data = [] for author in authors: # Each access to author.book_set triggers a new query book_count = author.book_set.count() # N queries recent_books = author.book_set.order_by('-publication_date')[:3] # N more queries author_data.append({ 'name': author.name, 'book_count': book_count, 'recent_books': list(recent_books) }) return author_data # Template that causes N+1 queries # books/list.html # {% for book in books %} #
#

{{ book.title }}

#

Author: {{ book.author.name }}

#

Email: {{ book.author.email }}

#
# {% endfor %} # API endpoint with N+1 problem from rest_framework.views import APIView from rest_framework.response import Response class BookAPIView(APIView): def get(self, request): books = Book.objects.all() book_list = [] for book in books: # Each iteration causes additional database queries book_list.append({ 'id': book.id, 'title': book.title, 'author': { 'id': book.author.id, # DB query 'name': book.author.name, # Same query repeated 'email': book.author.email }, 'publication_date': book.publication_date }) return Response(book_list)
# Secure: Optimized Django queries to avoid N+1 problems from django.shortcuts import render from django.db import models from .models import Author, Book # Optimized view using select_related def book_list_view_optimized(request): # Single query with JOIN to fetch related author data books = Book.objects.select_related('author').all() book_data = [] for book in books: book_data.append({ 'title': book.title, 'author_name': book.author.name, # No additional query 'author_email': book.author.email, # No additional query 'publication_date': book.publication_date }) return render(request, 'books/list.html', {'books': book_data}) # Optimized reverse foreign key access def author_books_view_optimized(request): # Use prefetch_related for reverse foreign key relationships authors = Author.objects.prefetch_related( 'book_set' # Prefetch all related books ).all() author_data = [] for author in authors: # These operations use the prefetched data (no additional queries) books = author.book_set.all() book_count = len(books) recent_books = sorted(books, key=lambda x: x.publication_date, reverse=True)[:3] author_data.append({ 'name': author.name, 'book_count': book_count, 'recent_books': [ { 'title': book.title, 'publication_date': book.publication_date } for book in recent_books ] }) return author_data # Advanced optimization with custom prefetch from django.db.models import Prefetch def author_recent_books_view(request): # Custom prefetch with filtering and ordering recent_books_prefetch = Prefetch( 'book_set', queryset=Book.objects.filter( publication_date__year__gte=2020 ).order_by('-publication_date'), to_attr='recent_books' ) authors = Author.objects.prefetch_related(recent_books_prefetch).all() author_data = [] for author in authors: # Uses the custom prefetched data author_data.append({ 'name': author.name, 'recent_books': [ { 'title': book.title, 'publication_date': book.publication_date } for book in author.recent_books # Custom prefetched attribute ] }) return author_data # Optimized API endpoint from rest_framework.views import APIView from rest_framework.response import Response from django.db.models import Count class OptimizedBookAPIView(APIView): def get(self, request): # Single query with JOIN and aggregation books = Book.objects.select_related('author').annotate( author_book_count=Count('author__book') ).all() book_list = [] for book in books: # All data available from the single optimized query book_list.append({ 'id': book.id, 'title': book.title, 'author': { 'id': book.author.id, 'name': book.author.name, 'email': book.author.email, 'total_books': book.author_book_count }, 'publication_date': book.publication_date }) return Response(book_list) # Query optimization with only() and defer() def book_list_minimal_view(request): # Only fetch necessary fields books = Book.objects.select_related('author').only( 'title', 'publication_date', 'author__name' ).all() book_data = [] for book in books: book_data.append({ 'title': book.title, 'author_name': book.author.name, 'publication_date': book.publication_date }) return render(request, 'books/minimal_list.html', {'books': book_data}) # Performance monitoring decorator from django.db import connection from functools import wraps import time def monitor_queries(func): @wraps(func) def wrapper(*args, **kwargs): # Reset query log connection.queries_log.clear() start_time = time.time() result = func(*args, **kwargs) end_time = time.time() query_count = len(connection.queries) execution_time = end_time - start_time # Log performance metrics print(f"Function: {func.__name__}") print(f"Queries executed: {query_count}") print(f"Execution time: {execution_time:.4f} seconds") # Alert if too many queries if query_count > 10: print(f"WARNING: High query count ({query_count}) detected!") return result return wrapper # Usage with monitoring @monitor_queries def monitored_book_view(request): return book_list_view_optimized(request) # Caching for frequently accessed data from django.core.cache import cache from django.conf import settings def cached_author_list_view(request): cache_key = 'author_list_with_books' cached_data = cache.get(cache_key) if cached_data is None: # Build optimized query only when cache miss authors = Author.objects.prefetch_related('book_set').all() author_data = [] for author in authors: author_data.append({ 'name': author.name, 'book_count': author.book_set.count(), 'books': [ { 'title': book.title, 'publication_date': book.publication_date.isoformat() } for book in author.book_set.all() ] }) # Cache for 15 minutes cache.set(cache_key, author_data, 900) cached_data = author_data return render(request, 'authors/cached_list.html', {'authors': cached_data})

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Django views, API endpoints, or business logic iterate over QuerySets and access foreign key relationships individually, causing the ORM to execute a separate database query for each related object access rather than using SQL JOINs to fetch related data in a single query. The pattern books = Book.objects.all(); for book in books: print(book.author.name) executes one query to fetch all books, then N additional queries to fetch each book's author individually, where N is the number of books. Django's lazy evaluation means book.author.name triggers a database query the first time the author attribute is accessed for each book object, even though the author foreign key relationship is defined in the model. Web application endpoints serving lists of objects with foreign key relationships—product catalogs with categories, blog posts with authors, orders with customers—commonly exhibit this pattern when developers access related.field without explicitly requesting join optimization. REST API serializers that access foreign key attributes during serialization execute N+1 queries when serializing collections: for book in Book.objects.all(): serialized.append({'title': book.title, 'author': book.author.name}) hits the database once per book to fetch author data. The performance impact scales linearly with result set size: 10 books = 11 queries, 100 books = 101 queries, 1000 books = 1001 queries, creating response time degradation and database server load that can enable resource exhaustion attacks where attackers request large result sets to overwhelm database connections.

Root causes

Accessing Foreign Key Attributes in Loops Without select_related()

Django views, API endpoints, or business logic iterate over QuerySets and access foreign key relationships individually, causing the ORM to execute a separate database query for each related object access rather than using SQL JOINs to fetch related data in a single query. The pattern books = Book.objects.all(); for book in books: print(book.author.name) executes one query to fetch all books, then N additional queries to fetch each book's author individually, where N is the number of books. Django's lazy evaluation means book.author.name triggers a database query the first time the author attribute is accessed for each book object, even though the author foreign key relationship is defined in the model. Web application endpoints serving lists of objects with foreign key relationships—product catalogs with categories, blog posts with authors, orders with customers—commonly exhibit this pattern when developers access related.field without explicitly requesting join optimization. REST API serializers that access foreign key attributes during serialization execute N+1 queries when serializing collections: for book in Book.objects.all(): serialized.append({'title': book.title, 'author': book.author.name}) hits the database once per book to fetch author data. The performance impact scales linearly with result set size: 10 books = 11 queries, 100 books = 101 queries, 1000 books = 1001 queries, creating response time degradation and database server load that can enable resource exhaustion attacks where attackers request large result sets to overwhelm database connections.

Iterating Over QuerySets and Accessing Related Objects Individually

Application code retrieves Django QuerySets and processes them in loops where each iteration accesses related model attributes, method calls, or computed properties that trigger additional database queries without batching or prefetching optimization. List comprehensions and generator expressions that access foreign keys execute queries during iteration: author_names = [book.author.name for book in Book.objects.all()] performs N database queries despite appearing as a single expression. Django template rendering iterates over QuerySets passed from views and accesses related objects, executing queries during template evaluation: {% for book in books %}{{ book.author.name }}{% endfor %} triggers N author queries if books QuerySet wasn't optimized with select_related(). API pagination that retrieves pages of objects and serializes them: paginated_books = paginator.page(page_num); for book in paginated_books: serialize(book) executes N+1 queries per page, multiplying inefficiency across all pages. Background tasks and Celery workers processing database records in batches suffer from N+1 queries when iterating: for order in Order.objects.filter(status='pending'): process_customer(order.customer) executes a query per order to fetch customer data. Data export functionality generating CSV, JSON, or XML representations of model collections exhibits N+1 patterns when accessing relationships during export: for record in Model.objects.all(): export_row(record.related_field) creates excessive queries proportional to export size. QuerySet operations like count(), exists(), or aggregate() on related managers within loops multiply query counts: for author in Authors.objects.all(): book_count = author.book_set.count() executes N counting queries instead of using annotation or aggregation.

Missing prefetch_related() for Reverse Foreign Keys and Many-to-Many Relationships

Django applications access reverse foreign key relationships (accessing related_set from the 'one' side of a one-to-many) or many-to-many fields without using prefetch_related(), causing multiple database queries when accessing collections of related objects. Reverse foreign key access through related manager: for author in Author.objects.all(): books = author.book_set.all() executes one query per author to fetch their books, totaling N+1 queries. Many-to-many relationships exhibit identical patterns: for book in Book.objects.all(): categories = book.categories.all() triggers separate queries for each book's categories. The related_name attribute on foreign keys and many-to-many fields provides reverse access that developers use without optimization: Order.customer creates forward foreign key (use select_related), while Customer.order_set creates reverse relationship (use prefetch_related). Counting related objects in loops executes separate COUNT queries: for user in User.objects.all(): post_count = user.posts.count() performs N count queries instead of annotating counts in the original query. Filtering related objects within loops multiplies query complexity: for category in Category.objects.all(): active_products = category.product_set.filter(active=True) executes N filtered queries. Template iteration over reverse relationships commonly triggers N+1 queries: {% for author in authors %}{% for book in author.book_set.all %}{{ book.title }}{% endfor %}{% endfor %} executes author count + 1 queries. Django admin list displays with related object counts or attributes configured in list_display exhibit N+1 queries when accessing reverse relationships or many-to-many without proper ModelAdmin optimization using select_related() or prefetch_related() in get_queryset().

Lazy Loading of Related Objects in Django Template Rendering

Django templates receive context QuerySets from views and access foreign key attributes, reverse relationships, or related object methods during template rendering, triggering lazy database queries within the template execution context where query optimization is difficult to implement or debug. Template variable resolution follows Django's lookup chain: {{ book.author.name }} checks dictionary keys, attributes, method calls, and list indices, executing database queries when accessing related model attributes during this resolution. Templates passed unoptimized QuerySets from views execute queries during iteration: view returns {'books': Book.objects.all()}, template renders {% for book in books %}{{ book.author.name }}{% endfor %}, triggering N author queries during page rendering. Template filters and tags that access related objects multiply query counts: {% for book in books %}{{ book.author.name|upper }}{% endfor %} or custom template tags that process related data without query optimization. Related object method calls in templates execute business logic that may trigger additional queries: {{ book.get_author_biography }} might access book.author.profile.bio, chaining multiple relationship traversals. Django template inheritance and inclusion patterns compound N+1 issues: base template includes {% include 'author_snippet.html' %} which accesses book.author for every book, multiplying queries across included templates. Template debugging complexity: developers see template syntax {{ book.author.name }} without realizing it triggers database queries, and Django Debug Toolbar shows queries after template rendering completes, making query-to-template mapping difficult. Generic views like ListView that use default context_object_name and template rendering pass QuerySets without optimization, expecting developers to override get_queryset() with select_related()/prefetch_related() which is often forgotten.

Inadequate Query Optimization Knowledge in Django ORM Usage

Development teams lack understanding of Django ORM query optimization techniques, performance monitoring tools, or awareness of N+1 query patterns, leading to widespread inefficient database access throughout applications. Junior developers learning Django follow tutorial examples that demonstrate ORM basics without covering select_related(), prefetch_related(), or query optimization patterns, establishing inefficient coding habits that persist into production code. Code reviews focus on functionality and business logic without performance analysis: reviews approve code that works correctly but executes hundreds of unnecessary queries because reviewers don't identify N+1 patterns. Development environments use small test datasets where N+1 queries execute quickly enough that performance problems aren't apparent until production deployment with realistic data volumes: 10 test records mask problems that become critical with 10,000 production records. Lack of query monitoring in development: Django Debug Toolbar, django-silk, or query logging aren't configured in local environments, preventing developers from seeing query counts during feature development. Misunderstanding Django ORM lazy evaluation: developers expect Model.objects.all() to fetch all data immediately, not realizing that related object access triggers additional queries due to lazy loading behavior. Fast database servers in development (localhost PostgreSQL with SSD) mask query inefficiency that becomes obvious on production databases with network latency, connection pooling limits, or concurrent load. Feature velocity pressure prioritizes rapid delivery over performance optimization: teams merge code that works without profiling or query analysis, accumulating technical debt as N+1 patterns proliferate across the codebase. Absent performance testing in CI/CD: no automated tests verify query counts, execution time, or database load, allowing performance regressions to deploy to production undetected.

Fixes

1

Use select_related() for Forward Foreign Key Relationships

Apply select_related() to Django QuerySets when accessing forward foreign key relationships to execute SQL JOINs that fetch related objects in a single database query rather than executing separate queries per object. Use select_related() for one-to-one and foreign key fields: Book.objects.select_related('author').all() generates SQL with JOIN clause fetching both books and authors in one query, eliminating N+1 pattern. Chain multiple relationships with select_related('author', 'publisher') or use double underscore notation for nested relationships: Order.objects.select_related('customer__address') follows foreign keys through multiple levels. Implement select_related() in Django views before passing QuerySets to templates: def book_list(request): books = Book.objects.select_related('author', 'publisher').all(); return render(request, 'books.html', {'books': books}) ensures template access to book.author.name doesn't trigger queries. Apply select_related() in Django REST Framework serializers using Meta class: class BookSerializer(serializers.ModelSerializer): class Meta: model = Book; fields = '__all__'; select_related = ['author', 'publisher'] optimizes serialization. Override get_queryset() in generic views to add optimization: class BookListView(ListView): def get_queryset(self): return Book.objects.select_related('author').all() applies optimization to all list view responses. Use select_related() with filtering and ordering: Book.objects.select_related('author').filter(author__country='US').order_by('author__name') combines JOIN optimization with query conditions. For Django admin, customize ModelAdmin: class BookAdmin(admin.ModelAdmin): def get_queryset(self, request): return super().get_queryset(request).select_related('author', 'publisher') optimizes admin list displays. Monitor SQL queries using Django's connection.queries in development to verify JOIN generation: from django.db import connection; books = Book.objects.select_related('author').all(); print(connection.queries[-1]) shows generated SQL with JOIN. Document select_related() usage in team coding standards with examples of when to apply: any loop or template iteration accessing foreign key attributes requires select_related() on the QuerySet.

2

Use prefetch_related() for Reverse Foreign Keys and Many-to-Many Relationships

Apply prefetch_related() to Django QuerySets when accessing reverse foreign key relationships (related_set attributes) or many-to-many fields to execute efficient bulk queries that fetch related objects rather than querying individually for each parent object. Use prefetch_related() for reverse relationships: Author.objects.prefetch_related('book_set').all() executes two queries—one for authors, one for all related books—then associates books with authors in Python, avoiding N+1 queries. Apply to many-to-many fields: Book.objects.prefetch_related('categories').all() fetches books and categories in separate queries, combining results efficiently. Implement custom Prefetch objects for filtered prefetching: from django.db.models import Prefetch; Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(published=True), to_attr='published_books')) prefetches only published books to a custom attribute. Chain prefetch_related() with select_related() for complex relationships: Book.objects.select_related('author').prefetch_related('categories', 'reviews') optimizes both forward foreign keys (select_related) and many-to-many/reverse relationships (prefetch_related) in a single QuerySet. Use prefetch_related() in views serving templates with reverse relationship iteration: def author_detail(request, author_id): author = Author.objects.prefetch_related('book_set__reviews').get(id=author_id) enables templates to iterate {% for book in author.book_set.all %}{% for review in book.reviews.all %} without N+1 queries. Optimize Django REST Framework nested serializers: class AuthorSerializer(serializers.ModelSerializer): books = BookSerializer(source='book_set', many=True); class Meta: model = Author; prefetch_related = ['book_set'] reduces queries during nested serialization. For counting related objects, combine prefetch_related() with Python's len(): authors = Author.objects.prefetch_related('book_set'); for author in authors: count = len(author.book_set.all()) uses prefetched data avoiding COUNT queries. Configure Django admin for reverse relationships: class AuthorAdmin(admin.ModelAdmin): def get_queryset(self, request): return super().get_queryset(request).prefetch_related('book_set') optimizes admin displays showing related object counts or inline editing. Use Prefetch with select_related for nested optimization: Prefetch('book_set', queryset=Book.objects.select_related('publisher')) prefetches books while also joining their publishers.

3

Implement Database Query Optimization and Performance Monitoring

Establish comprehensive database query monitoring, profiling, and optimization practices using Django's debugging tools, third-party packages, and performance testing to identify and eliminate N+1 queries throughout application lifecycle. Install Django Debug Toolbar in development: pip install django-debug-toolbar; add to INSTALLED_APPS and middleware; configure INTERNAL_IPS settings to enable toolbar showing query counts, execution time, and SQL statements for each request. Use django-silk for production-safe profiling: pip install django-silk provides request profiling, SQL query inspection, and historical performance data accessible through admin interface without debug mode performance penalties. Enable Django query logging in settings.py: configure LOGGING with django.db.backends logger at DEBUG level to log all SQL queries during development, making query patterns visible in console output. Implement query counting assertions in tests: from django.test.utils import override_settings; from django.db import connection; with self.assertNumQueries(3): response = self.client.get('/books/') ensures specific views execute expected query counts, failing tests when N+1 regressions occur. Use Django's QuerySet.explain() method to analyze query execution plans: Book.objects.select_related('author').explain(verbose=True) shows database query planner decisions, index usage, and performance characteristics. Profile views with custom middleware: create middleware tracking request query counts and execution time, logging warnings when thresholds exceed acceptable limits, alerting developers to performance regressions. Implement application performance monitoring (APM): integrate New Relic, DataDog, or Scout APM to track database query performance in production, identifying slow queries and N+1 patterns affecting real users. Configure database slow query logging: set PostgreSQL's log_min_duration_statement or MySQL's slow_query_log to capture queries exceeding time thresholds, providing production visibility into inefficient queries. Use django-querycount package: pip install django-querycount; add middleware to print query counts and duplicates for each request during development, highlighting N+1 patterns in terminal output. Establish performance budgets in CI/CD: configure pytest-django with query count assertions for critical endpoints, failing builds when query counts exceed defined limits.

4

Use Django Debug Toolbar to Identify and Fix N+1 Query Patterns

Leverage Django Debug Toolbar's SQL panel to identify N+1 query patterns during development, analyze query execution, and validate optimization effectiveness before deploying code to production. Install and configure Debug Toolbar: pip install django-debug-toolbar; add 'debug_toolbar' to INSTALLED_APPS; include 'debug_toolbar.middleware.DebugToolbarMiddleware' in MIDDLEWARE; configure URL patterns with path('__debug__/', include('debug_toolbar.urls')) in development settings. Enable toolbar for development: set INTERNAL_IPS = ['127.0.0.1'] in settings.py ensuring toolbar appears on localhost requests; configure SHOW_TOOLBAR_CALLBACK for custom display logic in Docker or remote development environments. Use SQL panel to identify duplicate queries: open toolbar, navigate to SQL panel, examine query list for patterns showing identical or similar queries executing multiple times indicating N+1 problems, with queries grouped by similarity showing repetition counts. Analyze query stack traces: click individual queries in toolbar to view Python stack traces showing which view, template, or code line triggered each query, enabling precise identification of N+1 sources in templates or views. Compare query execution before and after optimization: record query count from toolbar's SQL panel for baseline (e.g., 101 queries for book list), apply select_related(), refresh page, verify reduction to expected count (e.g., 1 query), confirming optimization effectiveness. Use 'Similar' query grouping: toolbar highlights similar queries with slight variations (same structure, different parameters) indicating loop-based queries; clicking 'Similar' shows all instances helping identify iteration causing N+1. Enable timeline panel: configure toolbar to show query timeline visualizing when queries execute during request processing, revealing template rendering queries, view processing queries, or middleware queries. Profile template rendering: use template panel alongside SQL panel to correlate template variable access with query execution, identifying which template lines trigger database queries during rendering. Test with realistic data volumes: populate development database with production-like data quantities to make N+1 performance impact visible in toolbar, revealing problems that small datasets mask. Document findings: create performance checklists from toolbar analysis listing endpoints requiring select_related() or prefetch_related(), incorporating into code review requirements and development practices.

5

Use only() and defer() to Limit Field Selection and Reduce Query Size

Apply Django's only() and defer() QuerySet methods to restrict which model fields are fetched from the database, reducing query size, memory usage, and network transfer when full model instances aren't required. Use only() to fetch specific fields: Book.objects.only('title', 'publication_date').all() generates SELECT queries fetching only specified columns, reducing data transfer when views or APIs need subset of model attributes. Apply only() with select_related() for optimized joins: Book.objects.select_related('author').only('title', 'author__name').all() fetches only title from books and name from authors via JOIN, excluding unnecessary columns from both tables. Use defer() to exclude specific fields: Book.objects.defer('content', 'summary').all() fetches all fields except specified ones, useful for excluding large text or binary fields from list queries while keeping them available for detail views. Optimize API responses with only(): class BookListView(APIView): def get(request): books = Book.objects.only('id', 'title', 'author_id'); return JsonResponse({'books': list(books.values())}) reduces payload size and query execution time. Combine with prefetch_related() using Prefetch: Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.only('title', 'publication_date'))) prefetches books with limited fields, optimizing memory usage for large related sets. Avoid accessing deferred fields in loops: accessing deferred fields triggers additional queries per instance; if book.content is deferred, accessing it in loop causes N queries; validate field requirements before applying defer(). Use values() or values_list() for dictionary or tuple results: Book.objects.values('title', 'author__name') returns dictionaries with specified fields, more efficient than model instances when ORM features aren't needed. Implement field selection in serializers: create separate serializers for list and detail views with different field sets; ListSerializer uses only() with fewer fields, DetailSerializer fetches complete objects. Document field selection patterns: establish coding standards defining when to use only()/defer() versus full model fetching, with examples for common scenarios (list views use only(), detail views fetch all, exports use values()). Monitor query explain plans: use QuerySet.explain() to verify only() generates SELECT statements with specified columns, confirming database query optimization translates to actual SQL reduction.

6

Implement Query Result Caching to Reduce Database Load

Cache Django QuerySet results, rendered templates, or computed values derived from database queries to serve repeated requests from cache rather than executing redundant database queries, particularly for data that changes infrequently. Use Django's cache framework with cache.get()/cache.set(): from django.core.cache import cache; cache_key = 'book_list'; books = cache.get(cache_key); if books is None: books = list(Book.objects.select_related('author').all()); cache.set(cache_key, books, 900) caches query results for 15 minutes. Configure cache backends appropriately: use Redis or Memcached for production (CACHES = {'default': {'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1'}}) providing fast distributed caching across multiple application servers. Implement cache invalidation strategies: use cache.delete(cache_key) when models change through signals (post_save, post_delete) ensuring cached data remains current; from django.db.models.signals import post_save; @receiver(post_save, sender=Book): delete_cache('book_list'). Use cache_page decorator for view caching: from django.views.decorators.cache import cache_page; @cache_page(60 * 15) def book_list_view(request): caches entire view response including database queries and rendering for 15 minutes. Apply template fragment caching for partial page caching: {% load cache %}{% cache 900 book_list %}{% for book in books %}...{% endfor %}{% endcache %} caches template sections with expensive queries while keeping other page elements dynamic. Implement query-level caching with django-cacheops: pip install django-cacheops; configure in settings to automatically cache QuerySets with TTL, providing transparent caching at ORM level: @cached_as(Book, timeout=900) def get_recent_books(): return Book.objects.select_related('author').filter(published__gte=date.today()-timedelta(days=30)). Cache expensive aggregations and counts: cache.get_or_set('total_books', lambda: Book.objects.count(), 3600) caches count queries avoiding repeated COUNT operations. Use vary_on_headers or vary_on_cookie for personalized caching: cache different versions of responses based on user authentication, language preferences, or request headers. Implement cache warming: use management commands or background tasks to pre-populate cache with frequently accessed data before traffic peaks, avoiding cache miss storms. Monitor cache hit rates: configure cache backend monitoring tracking hit/miss ratios, eviction rates, and memory usage; optimize cache TTL and invalidation based on metrics ensuring balance between data freshness and database load reduction.

Detect This Vulnerability in Your Code

Sourcery automatically identifies django performance issue: n+1 foreign key access and many other security issues in your codebase.