Django Performance Checklist Before Launch

A pre-launch performance checklist for Django: query counts and indexes, caching, static and media delivery, Gunicorn and database sizing, background work, timeouts, and a load test against targets.

Problem statement

Performance problems found after launch are expensive: they appear under real traffic, often at the worst moment (a campaign, a press mention), and fixing them means emergency changes to a live system. Most of them are predictable from a short review before launch: pages with hundreds of queries, missing indexes, static files served by Django, no caching for anonymous traffic, and timeouts that let one slow request take down the site.

This checklist covers what to verify before going live, in the order that usually gives the biggest wins. Each item has a concrete check, so it can be done by someone other than the original developer.

Pre-launch performance reviewQueriescounts and indexesCachingpages, fragments, dataDeliverystatic, media, gzipCapacityworkers, DB, load testSet targets first; every item is checked against a number, not a feeling.
  1. Define targets: p95 latency for key pages (for example under 500 ms server time) and the peak requests per second you must handle.
  2. Check query counts and slow queries on the key pages with production-sized data.
  3. Cache what anonymous users see and what is expensive to compute.
  4. Serve static files with long cache lifetimes and compression; keep media off Django workers.
  5. Size Gunicorn workers, database connections, and Celery concurrency to the server.
  6. Set timeouts at every layer and move slow work to background tasks.
  7. Load test the critical paths and fix what fails before launch day.

Checklist

1. Targets and key pages

  • Latency targets are written down for the five to ten most important pages and API endpoints (home, product list, product detail, search, login, checkout, main API calls).
  • Expected peak traffic is estimated (from analytics, marketing plans, or a comparable site), with a safety factor.
  • A dataset of realistic size exists for testing: production copy (sanitised) or generated data with similar row counts.
write targets down before measuringp95 targetMax queriesHome page300 ms10Product list500 ms15, constantCheckout step✓400 ms20Admin list1 sconstant per pageConstant means the count does not grow with the number of rowsdjango-deployment.com
Pick the pages that carry revenue or traffic; the rest can wait.

2. Database queries

  • Each key page runs a bounded number of queries that does not grow with list length (check with Django Debug Toolbar or assertNumQueries).
  • select_related and prefetch_related are used for relations shown in lists.
  • Filters, ordering, and joins on large tables are backed by indexes; EXPLAIN ANALYZE shows index scans for key queries.
  • List views and APIs are paginated; no view loads an unbounded queryset.
  • count() on large tables is avoided where not needed (admin changelists included).
  • Slow query logging (log_min_duration_statement) and pg_stat_statements are enabled for after-launch analysis.
the query count must not grow with rowsN+11 query for 50 orders✕+50 queries for customers✕grows with every page sizeJoined or prefetched✓select_related("customer")✓prefetch_related("items")✓2 or 3 queries, whatever the sizedjango-debug-toolbar or assertNumQueries catches this earlydjango-deployment.com
A test with assertNumQueries keeps the fix from regressing.

3. Caching

  • A shared cache (Redis or Memcached) is configured; LocMemCache is not used across multiple processes where consistency matters.
  • Pages for anonymous users are cached (per-view cache, template fragment cache, or a CDN or Nginx cache) where content allows.
  • Expensive computed data (menus, counts, aggregates) is cached with explicit timeouts and invalidation.
  • Sessions use a fast backend (cached_db or cache) if session reads are frequent.
  • The cache is not a single point of failure: pages still work (slower) if Redis is unavailable.

4. Static and media delivery

  • collectstatic runs in the build; static files use hashed filenames (manifest storage) and Cache-Control of a year.
  • Static files are served by WhiteNoise, Nginx, or a CDN, never by Django's development static view.
  • Gzip or Brotli compression is enabled for text assets and HTML.
  • Images are sized appropriately (thumbnails for lists, modern formats where possible); no multi-megabyte originals on list pages.
  • Media files are served by Nginx, object storage, or a CDN; private files use X-Accel-Redirect or signed URLs.

5. Application server and capacity

  • Gunicorn worker count fits the server's CPU and memory (measure per-worker memory; leave room for the database and cache if colocated).
  • --max-requests with jitter is set to contain slow memory growth.
  • Database max_connections covers all workers, Celery processes, and tools (or a pooler such as PgBouncer is in place).
  • CONN_MAX_AGE (or Django's connection pool) is configured to avoid reconnecting per request.
  • Server resources have headroom at expected peak (CPU under about 70 percent, memory without swapping).
capacity is workers times threadsgunicorn config.wsgi:application \--workers5\--threads2\--timeout301231A common start is 2 × cores + 1; check memory per worker.210 requests at once; threads help I/O-bound views.3Slow work belongs in a task queue, not a longer timeout.Every concurrent request also needs a database connectiondjango-deployment.com
Check the database max_connections against the total across all servers.

6. Timeouts and slow work

  • Timeouts are aligned: client or load balancer > Nginx proxy_read_timeout > Gunicorn --timeout > database statement_timeout.
  • Every outbound HTTP call (payment, email, APIs) has an explicit timeout.
  • Emails, exports, imports, image processing, and third-party syncs run in background tasks, not in requests.
  • Celery (or another worker) is sized and routed so urgent tasks are not stuck behind slow ones.

7. Frontend weight

  • Key pages have been checked with Lighthouse or WebPageTest on a mobile profile.
  • JavaScript and CSS are bundled and minified; unused libraries are removed.
  • Third-party scripts (analytics, chat widgets) are loaded asynchronously and their impact measured.

8. Load test

  • A load test covers the critical user journeys with realistic think times and data, against a production-like environment.
  • The system meets latency targets at expected peak and degrades gracefully (slower, not failing) at 2× peak.
  • Bottlenecks found (database CPU, worker saturation, a slow endpoint) are fixed and retested.
  • Monitoring during the test shows where limits are reached, so the same dashboards can be used on launch day.
a load test that means something1Production-like environmentsame sizes, real data volume2Script realistic journeysnot one URL in a loop3Ramp up graduallyto 2× expected peak4Watch p95, errors, saturationapp and database5Fix the first bottleneckthen test againNever load test production without telling everyone firstdjango-deployment.com
Locust and k6 both script journeys in code that can live in the repository.

Explanation

Django performance problems in production cluster in a few places: too many or too slow queries, work done in the request that should be cached or deferred, assets served inefficiently, and capacity that was never measured. Checking these with production-sized data and concrete targets turns vague confidence into evidence. The load test ties everything together: it verifies that the parts work at the traffic level you expect, and shows which limit you will hit first as traffic grows.

Edge cases / notes

Launching with a CDN

A CDN in front of anonymous pages can absorb most read traffic, but only if cache headers allow it. Check Cache-Control on cacheable pages and make sure personalised pages are never cached publicly.

Admin and internal tools

Staff tools often run heavy queries on large tables. Include the admin changelists your team will use daily in the review.

When to automate this

Add query count assertions for key views to the test suite, a Lighthouse check in CI for key pages, and a scheduled load test before major releases. That keeps the launch-level performance from eroding release by release.

Next steps

FAQ

What should I check first for Django performance before launch?

Query counts and slow queries on key pages with production-sized data. They are the most common and most impactful problems.

How many Gunicorn workers should I use at launch?

As many as fit in memory and CPU with headroom, based on measured per-worker memory. Load test to confirm.

Do I need a CDN for a Django launch?

Not always. Long-lived cached static files and compression are the essentials. A CDN helps with global audiences and heavy anonymous traffic.

How realistic does the load test need to be?

It should cover critical user journeys with realistic data size and request mix. A test hammering one cached page proves little.

Learning path · step 12 of 12

Making Django fast

Keep going