# PostgreSQL Performance Tuning Guide
Optimizing PostgreSQL performance involves proper indexing, configuration tuning, and query optimization. Here are key strategies:
-- Index strategies for different query patternsCREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, category VARCHAR(100), price DECIMAL(10,2), tags TEXT[], created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW());
-- B-tree index for exact matches and range queriesCREATE INDEX idx_products_category ON products (category);CREATE INDEX idx_products_price_range ON products (price) WHERE price > 0;
-- Composite index for multiple columnsCREATE INDEX idx_products_category_price ON products (category, price DESC);
-- Partial index for specific conditionsCREATE INDEX idx_recent_products ON products (created_at)WHERE created_at >= '2024-01-01';
-- GIN index for array searchesCREATE INDEX idx_products_tags ON products USING GIN (tags);
Query optimization techniques:
-- Use EXPLAIN ANALYZE to understand query performanceEXPLAIN (ANALYZE, BUFFERS, VERBOSE)SELECT p.name, p.price, c.name as category_nameFROM products pJOIN categories c ON p.category_id = c.idWHERE p.price BETWEEN 100 AND 500 AND p.created_at >= CURRENT_DATE - INTERVAL '30 days'ORDER BY p.price DESCLIMIT 20;
-- Optimize with appropriate indexes and query structure-- Instead of LIKE with leading wildcard (slow)SELECT * FROM products WHERE name LIKE '%widget%';
-- Use full-text search (faster)ALTER TABLE products ADD COLUMN search_vector tsvector;UPDATE products SET search_vector = to_tsvector('english', name);CREATE INDEX idx_products_search ON products USING GIN (search_vector);
SELECT * FROM productsWHERE search_vector @@ plainto_tsquery('english', 'widget');
Connection pooling and configuration optimization:
-- Monitor active connections and performanceSELECT state, COUNT(*) as connection_count, AVG(EXTRACT(EPOCH FROM (now() - query_start))) as avg_duration_secondsFROM pg_stat_activityWHERE state IS NOT NULLGROUP BY state;
-- Check for slow queriesSELECT query, calls, total_time, mean_time, rowsFROM pg_stat_statementsWHERE mean_time > 1000 -- queries taking more than 1 second on averageORDER BY mean_time DESCLIMIT 10;
Essential postgresql.conf optimizations:
# Memory settings (adjust based on available RAM)shared_buffers = 256MB # 25% of RAMeffective_cache_size = 1GB # 75% of RAMwork_mem = 4MB # Per operation memorymaintenance_work_mem = 64MB # For maintenance operations
# Connection settingsmax_connections = 200shared_preload_libraries = 'pg_stat_statements'
# WAL and checkpointingwal_buffers = 16MBcheckpoint_completion_target = 0.9max_wal_size = 1GBmin_wal_size = 80MB
# Query plannerrandom_page_cost = 1.1 # For SSD storageeffective_io_concurrency = 200 # For SSD storage
# Logginglog_min_duration_statement = 1000 # Log slow queries (1 second+)log_statement = 'mod' # Log DDL and DML statements
# Check database size and table statisticspsql -c "SELECT pg_size_pretty(pg_database_size('myapp'))"psql -c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC"
# Vacuum and analyze for optimal performancepsql -c "VACUUM ANALYZE;"
# Monitor active queriespsql -c "SELECT pid, now() - query_start as duration, query FROM pg_stat_activity WHERE state = 'active'"