DEV Community

Deep Fix
Deep Fix

Posted on

Optimizing PostgreSQL Query Performance: Indexing, EXPLAIN, and Real‑World Tuning

Introduction

PostgreSQL is a powerful open‑source RDBMS, but poorly written queries can quickly become a bottleneck. In this post we’ll walk through practical steps to speed up your PostgreSQL queries, from analyzing execution plans to applying the right indexes and configuration tweaks.


1. Diagnose with EXPLAIN ANALYZE

The first step is to understand how PostgreSQL executes a query.

EXPLAIN ANALYZE
SELECT o.id, o.total, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.created_at >= '2024-01-01'::date
  AND o.total > 1000;
Enter fullscreen mode Exit fullscreen mode

Key fields to watch:

  • Seq Scan – indicates a full table scan.
  • Index Scan – good; verify index usage.
  • Actual Total Time vs. Planner Estimated Time – large gaps hint at stale statistics.

If you see Seq Scan on a large table, it’s time to add an index.


2. Indexing Strategies

a) Simple B‑Tree Index

CREATE INDEX idx_orders_created_total ON orders (created_at, total);
Enter fullscreen mode Exit fullscreen mode

This multi‑column index matches the WHERE clause order, allowing PostgreSQL to filter rows efficiently.

b) Partial Index for Hot Data

When only recent rows are queried often:

CREATE INDEX idx_orders_recent ON orders (total)
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
Enter fullscreen mode Exit fullscreen mode

Partial indexes keep the index size small and improve insert performance.

c) Covering Index (Include Columns) – PostgreSQL 12+

CREATE INDEX idx_orders_covering ON orders (customer_id) INCLUDE (total, created_at);
Enter fullscreen mode Exit fullscreen mode

The INCLUDE clause stores extra columns in the index leaf pages, enabling an Index‑Only Scan and eliminating heap lookups.


3. Tune PostgreSQL Configuration

Parameter Typical Value Description
shared_buffers 25% of RAM Memory PostgreSQL uses for caching data pages.
work_mem 64MB per sort/join Controls memory for internal operations; increase for large sorts.
effective_cache_size 75% of RAM Informs the planner about OS cache availability.
random_page_cost 1.1 (SSD) / 4.0 (HDD) Adjust to reflect storage latency.

After changing postgresql.conf, reload the config:

pg_ctl reload -D /var/lib/postgresql/data
Enter fullscreen mode Exit fullscreen mode

4. Common Pitfalls & Troubleshooting

a) Stale Statistics

Run ANALYZE or VACUUM ANALYZE after bulk data loads.

ANALYZE VERBOSE orders;
Enter fullscreen mode Exit fullscreen mode

b) Parameter Mis‑match

If work_mem is too low, large hash joins fall back to disk‑based operations, causing spikes.
Monitor with:

SELECT pid, query, state, wait_event_type, wait_event
FROM pg_stat_activity
WHERE state <> 'idle';
Enter fullscreen mode Exit fullscreen mode

c) Unexpected Seq Scan after Index Creation

Check operator class mismatches (e.g., text_pattern_ops for LIKE queries) or collations.

CREATE INDEX idx_name_pattern ON customers (name text_pattern_ops);
Enter fullscreen mode Exit fullscreen mode

5. Real‑World Benchmark

-- Baseline (no index)
EXPLAIN ANALYZE SELECT * FROM orders WHERE total BETWEEN 5000 AND 6000;

-- After adding a partial index
CREATE INDEX idx_orders_total_range ON orders (total) WHERE total BETWEEN 5000 AND 6000;
EXPLAIN ANALYZE SELECT * FROM orders WHERE total BETWEEN 5000 AND 6000;
Enter fullscreen mode Exit fullscreen mode

Typical result: 5‑10× faster execution time and dramatically lower I/O.


6. Automation & Continuous Improvement

Integrate performance checks into CI pipelines. For example, use pgbench or custom query‑latency tests and fail builds when regressions exceed a threshold.

# .github/workflows/perf.yml
name: PostgreSQL Performance Test
on: [push]
jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run pgbench
        run: |
          docker run --rm -e POSTGRES_PASSWORD=pass \
            -p 5432:5432 postgres:15 \
            pgbench -i -s 10 mydb && \
            pgbench -c 10 -j 2 -T 60 mydb
Enter fullscreen mode Exit fullscreen mode

7. Take Action

Ready to apply these techniques to your production environment? Download the pre-configured script here, explore the Get the complete patch tool, or Access the full repository fix for a turnkey solution.


Conclusion

Optimizing PostgreSQL queries is a blend of analysis, proper indexing, and tuned server settings. By regularly reviewing EXPLAIN output, keeping statistics fresh, and aligning configuration with workload characteristics, you can achieve consistent, low‑latency performance that scales with your data.

Happy querying!

Top comments (0)