Optimization starts with a measured workload
An attractive database schema is not necessarily an efficient production schema. First identify the slow user journey, query frequency, data size, selectivity and acceptable response budget. Collect query logs or application tracing with sensitive values redacted. A single 300-millisecond query repeated thirty times can dominate a page even when no query appears catastrophic alone. Establish a representative fixture and compare results under realistic cardinality; timing a nearly empty development database rarely predicts production behavior. Optimize measured bottlenecks, not every query simultaneously.
Read an execution plan, not just elapsed time
EXPLAIN shows the planner's chosen operators and estimated cost; EXPLAIN ANALYZE executes and reports observed timing and row counts, so use caution with modifying statements. Compare estimated and actual rows to identify stale statistics, correlations or skew. Watch for sequential scans on large selective lookups, repeated nested loops, unexpected sorts and temporary disk spills. A sequential scan can still be correct for small or broadly matched tables. Planning and execution time must be interpreted alongside cache warmth, concurrent load and disk behavior.
Design indexes for predicates and ordering
A composite B-tree index on tenant_id, published, published_at may suit a frequent tenant-scoped feed that filters the first two fields and orders by date, but the exact key order depends on workload and selectivity. Adding indexes accelerates some reads while increasing write cost, storage and maintenance. Partial indexes may reduce index size when most records are unpublished, provided the query predicate allows the planner to use them. Do not assume a partial index matches every prepared or parameterized query. Index design requires verification with actual plans.
Avoid N-plus-one retrieval in Django
Rendering a list of fifty articles can accidentally issue fifty additional author queries if the template follows a foreign key lazily. select_related joins single-valued relations; prefetch_related typically batches collections and many-to-many relationships. Choose them based on the rendered fields rather than applying both everywhere. Inspect queryset evaluation points: iterating twice, checking truthiness and serializing nested relations can cause surprise work. Count queries in tests, but also track total row volume and transferred bytes; fewer queries are not always cheaper when a join duplicates large collections.
Keyset pagination and stable feeds
OFFSET-based pagination becomes costly for deep pages and unstable under concurrent inserts. For ordered published posts, a cursor using published_at and id can support deterministic continuation: select rows strictly earlier than the last seen pair, order by timestamp descending and id descending, and limit the result. The corresponding index should be tested. Use a unique tie-breaker to avoid lost records with identical timestamps. Cursor values should be validated or signed if they encode confidential filters. A route must still enforce tenant or ownership limits.
Transactions, locks and deadlock discipline
Long-running transactions retain locks and can block writers even if application code seems idle. Keep transaction scopes minimal and acquire locks in a consistent order. For conflicting edits, select_for_update may serialize operations when the database backend supports it; optimistic version checks can avoid locks for lower-contention workflows. Unique constraints and check constraints defend invariants regardless of ORM behavior. When a deadlock or serialization failure occurs, retry the whole safe transaction under an explicit policy, not an arbitrary side-effectful subset.
Create an evidence-based query optimization report
Record the exact query, representative row counts, pre-change plan, p50 and p95 latency, index change, post-change plan and effect on writes. Test cold and warm cache cases when relevant. Consider vacuum, analyze, partitioning and connection pool behavior only after identifying the limiting mechanism. Use a rollbackable migration for indexes where feasible and test lock impact in the production database version. The winning optimization is one that improves the real user journey without sacrificing integrity. References: https://www.postgresql.org/docs/current/using-explain.html and https://docs.djangoproject.com/en/5.2/topics/db/optimization/ .
Editorial disclosure: Original advanced educational content by Mojtaba Roozegar. Examples are illustrative, not an audit of a particular production deployment or a guarantee of outcomes. Relevant external technical references are linked within each article.
