PostgreSQL is an extremely capable database, but Django applications frequently under-use it. A few well-placed indexes and query changes can reduce page load times by an order of magnitude.

Enable pg_stat_statements in PostgreSQL to track which queries run most often and which take the most cumulative time. This gives you a ranked hit list of queries to optimise — focus on the ones with the highest total_time first.

Run EXPLAIN ANALYZE on suspicious queries. Look for Sequential Scans on large tables — these almost always indicate a missing index. Add indexes with CREATE INDEX CONCURRENTLY to avoid locking production tables.

In Django, use select_related for ForeignKey traversals and prefetch_related for ManyToMany or reverse FK relationships. Use .only() or .defer() to fetch only the columns you need — this reduces memory usage and speeds up serialisation.

For high-traffic apps, add PgBouncer in front of PostgreSQL in transaction-mode pooling. Django opens a connection per request; without pooling, you'll exhaust PostgreSQL's max_connections under load.