
Optimizing PostgreSQL Query Performance & Indexing: 800ms to 320ms
When API latency is dominated by database execution time, optimizing database queries yields the highest return on investment. In this post, I detail how we investigated slow endpoints and reduced average API latency from 800ms down to 320ms—a 60% performance improvement.
Pinpointing Bottlenecks with EXPLAIN ANALYZE
The first step in any database tuning effort is quantifying where the database engine spends CPU cycles and I/O reads.
EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE)
SELECT s.id, s.title, f.name, COUNT(a.id) as attendee_count
FROM sessions s
JOIN faculties f ON f.id = s.faculty_id
LEFT JOIN attendance a ON a.session_id = s.id
WHERE s.scheduled_at >= NOW() - INTERVAL '30 days'
AND s.status = 'COMPLETED'
GROUP BY s.id, f.name
ORDER BY s.scheduled_at DESC
LIMIT 50;The Problem: Sequential Scans on High-Churn Tables
The query plan revealed sequential table scans across over 2 million attendance records because composite keys were not properly indexed, and filter predicates evaluated unindexed boolean expressions.
Optimization Strategies
1. Composite & Partial Indexes
Rather than indexing whole columns indiscriminately, we created targeted composite and partial indexes for hot queries:
-- Partial index targeting active completed sessions
CREATE INDEX idx_sessions_completed_scheduled
ON sessions (scheduled_at DESC, faculty_id)
WHERE status = 'COMPLETED';
-- Covering index on foreign keys with included columns
CREATE INDEX idx_attendance_session_covered
ON attendance (session_id)
INCLUDE (student_id, attended_at);2. Eliminating N+1 Joins in ORMs
In our Laravel and Node.js repositories, we replaced implicit nested relationships with eager loading and window functions (ROW_NUMBER() / DENSE_RANK()), avoiding multiple roundtrips to Cloud SQL.
3. Connection Pooling with PgBouncer
We introduced connection pooling to eliminate the overhead of TCP handshakes and PostgreSQL backend process forking on every request.
Results
- Average API Response Time: 800ms ➡️ 320ms (60% decrease)
- p99 Latency: 2.4s ➡️ 650ms
- Database CPU Utilization: Reduced from 78% peak to 34% steady state.
Comments