PostgreSQL Database Engineering
A comprehensive skill for professional PostgreSQL database engineering, covering everything from query optimization and indexing strategies to high availability, replication, and production database management. This skill enables you to design, optimize, and maintain high-performance PostgreSQL databases at scale.
When to Use This Skill
Use this skill when:
- Designing database schemas for high-performance applications
- Optimizing slow queries and improving database performance
- Implementing indexing strategies for complex query patterns
- Setting up partitioning for large tables (100M+ rows)
- Configuring streaming replication and high availability
- Tuning PostgreSQL configuration for production workloads
- Implementing backup and recovery procedures
- Debugging performance issues and query bottlenecks
- Setting up connection pooling with pgBouncer or PgPool
- Monitoring database health and performance metrics
- Planning database migrations and schema changes
- Implementing database security and access controls
- Scaling PostgreSQL databases horizontally or vertically
- Managing VACUUM operations and database maintenance
- Setting up logical replication for data distribution
Core Concepts
PostgreSQL Architecture
PostgreSQL uses a process-based architecture with several key components:
- Postmaster Process: Main server process that manages connections
- Backend Processes: One per client connection, handles queries
- Shared Memory: Shared buffers, WAL buffers, lock tables
- Background Workers: Autovacuum, checkpointer, WAL writer, statistics collector
- Write-Ahead Log (WAL): Transaction log for durability and replication
- Storage Layer: TOAST for large values, FSM for free space, VM for visibility
MVCC (Multi-Version Concurrency Control)
PostgreSQL's foundational concurrency mechanism:
- Snapshots: Each transaction sees a consistent snapshot of data
- Tuple Versions: Multiple row versions coexist for concurrent access
- Transaction IDs: xmin (creating transaction), xmax (deleting transaction)
- Visibility Rules: Determines which row versions are visible to transactions
- VACUUM: Reclaims space from dead tuples and prevents transaction wraparound
- FREEZE: Marks old rows as visible to all transactions
Key Implications:
- No read locks - readers never block writers
- Writers never block readers
- Updates create new row versions
- Regular VACUUM is essential
- Dead tuples accumulate until vacuumed
Transaction Isolation Levels
PostgreSQL supports four isolation levels:
- Read Uncommitted: Treated as Read Committed in PostgreSQL
- Read Committed (default): Sees committed data at statement start
- Repeatable Read: Sees snapshot from transaction start
- Serializable: True serializable isolation with SSI
Choosing Isolation:
- Read Committed: Most applications, best performance
- Repeatable Read: Reports, analytics needing consistency
- Serializable: Financial transactions, critical consistency needs
Index Types
PostgreSQL offers multiple index types for different use cases:
1. B-Tree (Default)
- Use for: Equality, range queries, sorting
- Supports: <, <=, =, >=, >, BETWEEN, IN, IS NULL
- Best for: Most general-purpose indexing
- Example: Primary keys, foreign keys, timestamps
2. Hash
- Use for: Equality comparisons only
- Supports: = operator
- Best for: Large tables with equality lookups
- Limitation: Not WAL-logged before PG 10, no range queries
3. GiST (Generalized Search Tree)
- Use for: Geometric data, full-text search, custom types
- Supports: Overlaps, contains, nearest neighbor
- Best for: Spatial data, ranges, full-text search
- Example: PostGIS geometries, tsvector, ranges
4. GIN (Generalized Inverted Index)
- Use for: Multi-valued columns (arrays, JSONB, full-text)
- Supports: Contains, exists operators
- Best for: JSONB queries, array operations, full-text search
- Tradeoff: Slower updates, faster queries
5. BRIN (Block Range Index)
- Use for: Very large tables with natural ordering
- Supports: Range queries on sorted data
- Best for: Time-series data, append-only tables
- Advantage: Tiny index size, scales to billions of rows
6. SP-GiST (Space-Partitioned GiST)
- Use for: Non-balanced data structures
- Supports: Points, ranges, IP addresses
- Best for: Quadtrees, k-d trees, radix trees
Query Planning and Optimization
PostgreSQL's query planner determines execution strategies:
Planner Components:
- Statistics: Table and column statistics for cardinality estimation
- Cost Model: CPU, I/O, and memory cost estimation
- Plan Types: Sequential scan, index scan, bitmap scan, joins
- Join Methods: Nested loop, hash join, merge join
- Optimization: Query rewriting, predicate pushdown, join reordering
Key Statistics:
n_distinct: Number of distinct values (for selectivity)correlation: Physical row ordering correlationmost_common_vals: MCV list for skewed distributionshistogram_bounds: Value distribution histogram
Understanding EXPLAIN:
- Cost: Startup cost .. total cost (arbitrary units)
- Rows: Estimated row count
- Width: Average row size in bytes
- Actual Time: Real execution time (with ANALYZE)
- Loops: Number of times node executed
Partitioning Strategies
Table partitioning for managing large datasets:
Range Partitioning
- Use for: Time-series data, sequential values
- Example: Partition by date ranges (daily, monthly, yearly)
- Benefit: Easy data lifecycle management, faster queries
List Partitioning
- Use for: Discrete categorical values
- Example: Partition by country, region, status
- Benefit: Logical data separation, partition pruning
Hash Partitioning
- Use for: Even data distribution
- Example: Partition by hash(user_id)
- Benefit: Balanced partition sizes, parallel queries
Partition Pruning:
- Planner eliminates irrelevant partitions
- Drastically reduces query scope
- Essential for partition performance
Partition-Wise Operations:
- Partition-wise joins: Join matching partitions directly
- Partition-wise aggregation: Aggregate within partitions
- Parallel partition processing
Replication and High Availability
PostgreSQL replication options:
Streaming Replication (Physical)
- Type: Binary WAL streaming to standby servers
- Modes: Asynchronous, synchronous, quorum-based
- Use for: High availability, read scalability
- Failover: Automatic with tools like Patroni, repmgr
Synchronous vs Asynchronous:
- Synchronous: Zero data loss, higher latency
- Asynchronous: Low latency, potential data loss
- Quorum: Balance between safety and performance
Logical Replication
- Type: Row-level change stream
- Use for: Selective replication, upgrades, multi-master
- Benefit: Replicate specific tables, cross-version
- Limitation: No DDL replication, overhead
Cascading Replication
- Standbys replicate from other standbys
- Reduces load on primary
- Geographic distribution
Connection Pooling
Managing database connections efficiently:
pgBouncer
- Type: Lightweight connection pooler
- Modes: Session, transaction, statement pooling
- Use for: High connection count applications
- Benefit: Reduced connection overhead, resource limits
Pooling Modes:
- Session: Client connects for entire session
- Transaction: Connection per transaction
- Statement: Connection per statement (rarely used)
PgPool-II
- Type: Feature-rich middleware
- Features: Connection pooling, load balancing, query caching
- Use for: Read/write splitting, connection management
- Benefit: Advanced routing, in-memory cache
VACUUM and Maintenance
Critical maintenance operations:
VACUUM
- Purpose: Reclaim dead tuple space, update statistics
- Types: Regular VACUUM, VACUUM FULL
- When: After large updates/deletes, regularly via autovacuum
- Impact: Regular VACUUM is non-blocking
ANALYZE
- Purpose: Update planner statistics
- When: After data changes, schema modifications
- Impact: Minimal, fast on most tables
REINDEX
- Purpose: Rebuild indexes, fix bloat
- When: Index corruption, significant bloat
- Impact: Locks table, use REINDEX CONCURRENTLY (PG 12+)
Autovacuum
- Purpose: Automated VACUUM and ANALYZE
- Configuration: Threshold-based triggering
- Tuning: Balance resource usage vs. responsiveness
- Monitoring: Track autovacuum runs, prevent wraparound
Performance Tuning
Key configuration parameters:
Memory Settings
Checkpoint and WAL
Query Planner
Connection Settings
Index Strategies
Choosing the Right Index
Decision Matrix:
Composite Indexes
Multi-column indexes for complex queries:
Column Ordering Rules:
- Equality columns first
- Sort/range columns last
- High-selectivity columns first
- Match query patterns exactly
Example:
Partial Indexes
Index subset of rows:
Benefits:
- Smaller index size
- Faster updates on non-indexed rows
- Targeted query optimization
Use Cases:
- Index only active records:
WHERE deleted_at IS NULL - Index recent data:
WHERE created_at > NOW() - INTERVAL '90 days' - Index specific states:
WHERE status IN ('pending', 'processing')
Expression Indexes
Index computed values:
Examples:
Covering Indexes (INCLUDE)
Include non-key columns for index-only scans:
Benefit: Query satisfied entirely from index, no table lookup
Index Maintenance
Monitoring Index Usage:
Detecting Bloat:
Query Optimization
Using EXPLAIN ANALYZE
Understanding query execution:
Key Metrics:
- Planning Time: Time to generate plan
- Execution Time: Actual query runtime
- Shared Hit vs Read: Buffer cache hits vs disk reads
- Rows: Estimated vs actual row counts
- Filter vs Index Cond: Post-scan filtering vs index usage
Common Query Anti-Patterns
1. N+1 Queries
Problem: One query per row in a loop Solution: JOIN or batch queries
2. SELECT *
Problem: Fetches unnecessary columns Solution: Select only needed columns
3. Implicit Type Conversions
Problem: Index not used due to type mismatch Solution: Ensure query types match column types
4. Function on Indexed Column
Problem: WHERE UPPER(email) = '[email protected]'
Solution: Use expression index or compare correctly
5. OR Conditions
Problem: WHERE status = 'A' OR status = 'B'
Solution: Use IN: WHERE status IN ('A', 'B')
Join Optimization
Join Types:
-
Nested Loop
- Best for: Small outer table, indexed inner table
- How: For each outer row, scan inner table
- When: Small result sets, good indexes
-
Hash Join
- Best for: Large tables, no good indexes
- How: Build hash table of smaller table
- When: Equality joins, sufficient memory
-
Merge Join
- Best for: Pre-sorted data, equality joins
- How: Sort both inputs, merge scan
- When: Both inputs sorted or can be sorted cheaply
Join Order Matters:
- Planner reorders joins for optimization
- Statistics guide join order decisions
- Can force order with
SET join_collapse_limit
Aggregation Optimization
Techniques:
- Partial Aggregates: Partition-wise aggregation
- Hash Aggregates: In-memory grouping
- Sorted Aggregates: Pre-sorted input
- Parallel Aggregation: Multiple workers
Materialized Views:
- Pre-compute expensive aggregations
- Refresh on schedule or trigger
- Trade freshness for query speed
Query Caching
Levels:
- Shared Buffers: PostgreSQL page cache
- OS Page Cache: Operating system cache
- Application Cache: Redis, Memcached
- Prepared Statements: Reuse query plans
Partitioning
Implementing Range Partitioning
Time-series example:
Partition Automation
Automated partition management:
Partition Maintenance
Dropping old partitions:
High Availability and Replication
Setting Up Streaming Replication
Primary server configuration (postgresql.conf):
Create replication user:
pg_hba.conf on primary:
Standby server setup:
Standby configuration (created by -R flag):
Monitoring Replication
On primary:
On standby:
Failover and Switchover
Promoting standby to primary:
Controlled switchover:
Logical Replication Setup
On publisher (source):
On subscriber (destination):
Backup and Recovery
Physical Backups
pg_basebackup:
Continuous archiving (WAL archiving):
Logical Backups
pg_dump:
pg_restore:
Point-in-Time Recovery (PITR)
Setup:
- Take base backup
- Configure WAL archiving
- Store WAL files safely
Recovery:
Backup Strategies
3-2-1 Rule:
- 3 copies of data
- 2 different media types
- 1 offsite backup
Backup Schedule:
- Daily: Incremental WAL archiving
- Weekly: Full pg_basebackup
- Monthly: Long-term retention
Testing Backups:
- Regularly restore to test environment
- Verify data integrity
- Measure restore time
Performance Monitoring
Key Metrics to Monitor
Database Health:
- Active connections
- Transaction rate
- Cache hit ratio
- Deadlocks
- Checkpoint frequency
- Autovacuum runs
Query Performance:
- Slow query log
- Query execution time
- Lock waits
- Sequential scans
System Resources:
- CPU utilization
- Memory usage
- Disk I/O
- Network bandwidth
Essential Monitoring Queries
Connection stats:
Cache hit ratio:
Table bloat:
Long-running queries:
Lock monitoring:
pg_stat_statements
Installation:
Configuration (postgresql.conf):
Top queries by total time:
Top queries by average time:
Best Practices
Schema Design
Normalization:
- Normalize to 3NF for transactional systems
- Denormalize selectively for read-heavy workloads
- Use foreign keys for data integrity
- Consider partitioning for very large tables
Data Types:
- Use smallest appropriate data type
- BIGINT for large IDs, INTEGER for smaller ranges
- NUMERIC for exact decimal values
- TIMESTAMP WITH TIME ZONE for timestamps
- TEXT over VARCHAR unless length constraint needed
- UUID for distributed ID generation
- JSONB for semi-structured data
Constraints:
- Primary keys on all tables
- Foreign keys for referential integrity
- CHECK constraints for business rules
- NOT NULL where appropriate
- UNIQUE constraints for uniqueness
- Use constraint names for maintainability
Migration Strategies
Zero-Downtime Migrations:
-
Add new column
-
Backfill data (in batches)
-
Add NOT NULL constraint
Index Creation:
- Use
CREATE INDEX CONCURRENTLYin production - No table locks, allows reads/writes
- Takes longer but doesn't block
- Monitor progress with
pg_stat_progress_create_index
Large Table Modifications:
- Use
pg_repackfor table rewrites - Partition large tables before modifications
- Schedule during maintenance windows
- Test on production-like datasets
Security Best Practices
Authentication:
- Use strong passwords or certificate authentication
- SCRAM-SHA-256 for password encryption
- Separate users for different applications
- Avoid superuser for application connections
Authorization:
- Grant minimal required privileges
- Use role-based access control
- Revoke PUBLIC access
- Row-level security for multi-tenant
Network Security:
- Configure pg_hba.conf restrictively
- Use SSL/TLS for connections
- Firewall database ports
- VPN or private networks for replication
Audit Logging:
- Enable connection logging
- Log DDL statements
- Use pgAudit extension for detailed auditing
- Monitor for suspicious activity
Maintenance Schedule
Daily:
- Monitor slow queries
- Check replication lag
- Review autovacuum activity
- Monitor disk space
Weekly:
- Analyze top queries
- Review index usage
- Check for bloat
- Backup verification
Monthly:
- Full VACUUM on critical tables
- REINDEX bloated indexes
- Review configuration parameters
- Capacity planning
Quarterly:
- Review and optimize indexes
- Schema optimization opportunities
- Upgrade planning
- Performance baseline updates
Advanced Topics
Parallel Query Execution
Configuration:
Forcing parallel execution:
When parallelism helps:
- Large sequential scans
- Large aggregations
- Hash joins on large tables
- Bitmap heap scans
Custom Functions and Procedures
Stored procedures:
Functions with proper error handling:
Foreign Data Wrappers
Access external data sources:
JSON and JSONB Operations
Indexing JSONB:
Efficient JSONB queries:
Full-Text Search
Basic setup:
Search queries:
Troubleshooting
Common Issues
Problem: Slow Queries
- Check EXPLAIN ANALYZE output
- Verify indexes exist and are used
- Update table statistics:
ANALYZE table_name - Check for missing indexes on foreign keys
- Look for function calls on indexed columns
Problem: High CPU Usage
- Identify expensive queries with pg_stat_statements
- Check for missing indexes causing sequential scans
- Review parallel query settings
- Look for inefficient joins or aggregations
Problem: Connection Exhaustion
- Increase max_connections (requires restart)
- Implement connection pooling (pgBouncer)
- Identify connection leaks in application
- Monitor with
pg_stat_activity
Problem: Autovacuum Not Keeping Up
- Increase autovacuum_max_workers
- Adjust autovacuum thresholds
- Reduce autovacuum_naptime
- Increase autovacuum_work_mem
- Check for long-running transactions blocking VACUUM
Problem: Replication Lag
- Check network bandwidth between primary and standby
- Verify standby hardware resources
- Check for long-running queries on standby
- Monitor WAL generation rate
- Consider increasing wal_sender_timeout
Problem: Transaction ID Wraparound
- Monitor age of oldest transaction
- Run VACUUM FREEZE on old tables
- Check autovacuum_freeze_max_age
- Increase autovacuum aggressiveness
- Run manual VACUUM FREEZE if necessary
Diagnostic Queries
Find missing indexes on foreign keys:
Identify blocking queries:
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Database Engineering, Performance Optimization, Data Architecture Compatible With: PostgreSQL 12+, 13, 14, 15, 16 Prerequisites: SQL knowledge, basic database concepts, Linux command line


