Database Optimization Best Practices¶
This document establishes production-ready database optimization patterns for geospatial systems, covering PostgreSQL/PostGIS tuning, spatial indexing, and query optimization.
PostgreSQL/PostGIS Tuning¶
Spatial Database Configuration¶
-- PostGIS spatial database optimization
-- Enable PostGIS extensions
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;
CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder;
-- Optimize PostgreSQL for spatial workloads
ALTER SYSTEM SET shared_buffers = '4GB';
ALTER SYSTEM SET effective_cache_size = '12GB';
ALTER SYSTEM SET maintenance_work_mem = '1GB';
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
ALTER SYSTEM SET wal_buffers = '64MB';
ALTER SYSTEM SET default_statistics_target = 100;
-- Spatial indexing best practices
CREATE INDEX CONCURRENTLY idx_spatial_data_geom
ON spatial_data USING GIST (geometry);
-- Partial indexes for common queries
CREATE INDEX CONCURRENTLY idx_spatial_data_active
ON spatial_data USING GIST (geometry)
WHERE status = 'active';
-- Covering indexes for performance
CREATE INDEX CONCURRENTLY idx_spatial_data_covering
ON spatial_data USING GIST (geometry)
INCLUDE (id, name, created_at);
-- Analyze tables for query optimization
ANALYZE spatial_data;
Why: PostGIS extensions provide spatial data types and functions. Optimized PostgreSQL settings improve spatial query performance. Spatial indexes (GIST) enable fast geometric operations.
Query Optimization Patterns¶
-- Optimized spatial queries
-- Use spatial indexes effectively
EXPLAIN (ANALYZE, BUFFERS)
SELECT s1.id, s1.name, ST_Distance(s1.geometry, s2.geometry) as distance
FROM spatial_data s1
CROSS JOIN LATERAL (
SELECT s2.geometry
FROM spatial_data s2
WHERE s2.id != s1.id
ORDER BY s1.geometry <-> s2.geometry
LIMIT 1
) s2;
-- Spatial joins with proper indexing
SELECT a.id, b.id, ST_Area(ST_Intersection(a.geometry, b.geometry)) as intersection_area
FROM polygons_a a
JOIN polygons_b b ON ST_Intersects(a.geometry, b.geometry)
WHERE ST_Area(ST_Intersection(a.geometry, b.geometry)) > 1000;
-- Clustering for spatial locality
CLUSTER spatial_data USING idx_spatial_data_geom;
Why: LATERAL joins enable efficient nearest neighbor queries. Spatial clustering improves cache locality and query performance for spatially-related data.
Spatial Indexing Strategies¶
Advanced Spatial Indexing¶
-- Multi-dimensional spatial index
CREATE INDEX CONCURRENTLY idx_spatial_multi
ON spatial_data USING GIST (geometry, created_at, status);
-- Functional indexes for computed values
CREATE INDEX CONCURRENTLY idx_spatial_area
ON spatial_data USING BTREE (ST_Area(geometry));
-- Partial spatial indexes for filtered data
CREATE INDEX CONCURRENTLY idx_spatial_high_priority
ON spatial_data USING GIST (geometry)
WHERE priority = 'high';
-- Covering indexes for common queries
CREATE INDEX CONCURRENTLY idx_spatial_covering
ON spatial_data USING GIST (geometry)
INCLUDE (id, name, area, centroid);
Why: Multi-dimensional indexes support complex spatial queries. Functional indexes enable efficient queries on computed spatial properties. Partial indexes reduce index size and maintenance overhead.
Spatial Index Maintenance¶
-- Monitor spatial index usage
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE indexname LIKE '%spatial%';
-- Reindex spatial indexes
REINDEX INDEX CONCURRENTLY idx_spatial_data_geom;
-- Update table statistics
ANALYZE spatial_data;
-- Vacuum spatial tables
VACUUM ANALYZE spatial_data;
Why: Regular index maintenance ensures optimal query performance. Monitoring index usage helps identify unused or inefficient indexes.
Query Performance Optimization¶
Spatial Query Patterns¶
-- Efficient spatial filtering
SELECT id, name, geometry
FROM spatial_data
WHERE geometry && ST_MakeEnvelope(-180, -90, 180, 90, 4326)
AND ST_Intersects(geometry, ST_MakeEnvelope(-180, -90, 180, 90, 4326));
-- Optimized spatial joins
SELECT a.id, b.id, ST_Distance(a.geometry, b.geometry) as distance
FROM points_a a
JOIN points_b b ON ST_DWithin(a.geometry, b.geometry, 1000)
WHERE ST_Distance(a.geometry, b.geometry) < 1000;
-- Spatial aggregation with proper indexing
SELECT
ST_ClusterKMeans(geometry, 10) as cluster_id,
COUNT(*) as point_count,
ST_Centroid(ST_Collect(geometry)) as centroid
FROM spatial_points
GROUP BY ST_ClusterKMeans(geometry, 10);
Why: Bounding box operations (&&) are faster than precise geometric operations. ST_DWithin enables efficient distance-based queries. Spatial clustering provides data aggregation capabilities.
Query Plan Analysis¶
-- Analyze query execution plans
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT s1.id, s1.name, s2.id, s2.name
FROM spatial_data s1
JOIN spatial_data s2 ON ST_Intersects(s1.geometry, s2.geometry)
WHERE s1.id != s2.id;
-- Monitor slow queries
SELECT
query,
calls,
total_time,
mean_time,
rows
FROM pg_stat_statements
WHERE query LIKE '%spatial%'
ORDER BY mean_time DESC
LIMIT 10;
Why: Query plan analysis identifies performance bottlenecks. Monitoring slow queries enables targeted optimization efforts.
Database Partitioning¶
Spatial Data Partitioning¶
-- Create partitioned table for spatial data
CREATE TABLE spatial_data_partitioned (
id SERIAL,
name VARCHAR(255),
geometry GEOMETRY,
created_at TIMESTAMP DEFAULT NOW()
) PARTITION BY RANGE (created_at);
-- Create monthly partitions
CREATE TABLE spatial_data_2024_01 PARTITION OF spatial_data_partitioned
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE spatial_data_2024_02 PARTITION OF spatial_data_partitioned
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Create spatial indexes on partitions
CREATE INDEX CONCURRENTLY idx_spatial_part_2024_01_geom
ON spatial_data_2024_01 USING GIST (geometry);
CREATE INDEX CONCURRENTLY idx_spatial_part_2024_02_geom
ON spatial_data_2024_02 USING GIST (geometry);
-- Automatic partition creation
CREATE OR REPLACE FUNCTION create_monthly_partition(table_name TEXT, start_date DATE)
RETURNS VOID AS $$
DECLARE
partition_name TEXT;
end_date DATE;
BEGIN
partition_name := table_name || '_' || to_char(start_date, 'YYYY_MM');
end_date := start_date + INTERVAL '1 month';
EXECUTE format('CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
partition_name, table_name, start_date, end_date);
EXECUTE format('CREATE INDEX CONCURRENTLY %I ON %I USING GIST (geometry)',
'idx_' || partition_name || '_geom', partition_name);
END;
$$ LANGUAGE plpgsql;
Why: Partitioning improves query performance for large spatial datasets. Automatic partition creation reduces maintenance overhead.
Partition Pruning¶
-- Enable partition pruning
SET enable_partition_pruning = on;
-- Query with partition pruning
SELECT COUNT(*)
FROM spatial_data_partitioned
WHERE created_at >= '2024-01-01'
AND created_at < '2024-02-01'
AND ST_Intersects(geometry, ST_MakeEnvelope(-180, -90, 180, 90, 4326));
-- Monitor partition usage
SELECT
schemaname,
tablename,
n_tup_ins,
n_tup_upd,
n_tup_del
FROM pg_stat_user_tables
WHERE tablename LIKE 'spatial_data_%'
ORDER BY n_tup_ins DESC;
Why: Partition pruning eliminates unnecessary partition scans. Monitoring partition usage helps optimize partition strategy.
Connection Pooling and Resource Management¶
Connection Pool Configuration¶
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
from sqlalchemy.orm import sessionmaker
import psycopg2
from psycopg2 import pool
# SQLAlchemy connection pooling
engine = create_engine(
"postgresql://user:password@localhost/geospatial",
poolclass=QueuePool,
pool_size=20,
max_overflow=30,
pool_pre_ping=True,
pool_recycle=3600,
pool_timeout=30
)
# Direct psycopg2 connection pooling
connection_pool = psycopg2.pool.ThreadedConnectionPool(
minconn=5,
maxconn=20,
host="localhost",
database="geospatial",
user="user",
password="password"
)
def get_connection():
"""Get connection from pool"""
return connection_pool.getconn()
def return_connection(conn):
"""Return connection to pool"""
connection_pool.putconn(conn)
Why: Connection pooling reduces connection overhead and improves resource utilization. Pre-ping ensures connection health.
Database Resource Monitoring¶
-- Monitor database connections
SELECT
state,
COUNT(*) as connection_count
FROM pg_stat_activity
GROUP BY state;
-- Monitor database size
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- Monitor spatial index usage
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch,
pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_stat_user_indexes
WHERE indexname LIKE '%spatial%'
ORDER BY idx_scan DESC;
Why: Resource monitoring enables capacity planning and performance optimization. Connection monitoring helps identify connection leaks.
Backup and Recovery¶
Spatial Data Backup¶
#!/bin/bash
# Spatial database backup script
# Create backup directory
BACKUP_DIR="/backups/geospatial"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/geospatial_backup_$DATE.sql"
# Create backup
pg_dump \
--host=localhost \
--port=5432 \
--username=postgres \
--dbname=geospatial \
--verbose \
--clean \
--if-exists \
--create \
--format=plain \
--file="$BACKUP_FILE"
# Compress backup
gzip "$BACKUP_FILE"
# Remove old backups (keep last 7 days)
find "$BACKUP_DIR" -name "geospatial_backup_*.sql.gz" -mtime +7 -delete
echo "Backup completed: $BACKUP_FILE.gz"
Point-in-Time Recovery¶
-- Enable WAL archiving
ALTER SYSTEM SET wal_level = replica;
ALTER SYSTEM SET archive_mode = on;
ALTER SYSTEM SET archive_command = 'cp %p /archive/%f';
ALTER SYSTEM SET max_wal_senders = 3;
ALTER SYSTEM SET hot_standby = on;
-- Create recovery configuration
-- recovery.conf
standby_mode = 'on'
primary_conninfo = 'host=primary_server port=5432 user=replication'
trigger_file = '/tmp/postgresql.trigger'
Why: Regular backups ensure data protection. Point-in-time recovery enables recovery to specific timestamps.
Performance Tuning¶
Database Configuration Tuning¶
-- Memory configuration
ALTER SYSTEM SET shared_buffers = '4GB';
ALTER SYSTEM SET effective_cache_size = '12GB';
ALTER SYSTEM SET work_mem = '256MB';
ALTER SYSTEM SET maintenance_work_mem = '1GB';
-- Checkpoint configuration
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
ALTER SYSTEM SET wal_buffers = '64MB';
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET min_wal_size = '1GB';
-- Query planning
ALTER SYSTEM SET random_page_cost = 1.1;
ALTER SYSTEM SET effective_io_concurrency = 200;
ALTER SYSTEM SET max_parallel_workers_per_gather = 4;
ALTER SYSTEM SET max_parallel_workers = 8;
-- Apply configuration
SELECT pg_reload_conf();
Why: Optimized configuration improves spatial query performance. Memory settings balance between different workload types.
Spatial Query Optimization¶
-- Use appropriate spatial functions
-- Fast bounding box check
SELECT * FROM spatial_data
WHERE geometry && ST_MakeEnvelope(-180, -90, 180, 90, 4326);
-- Efficient distance queries
SELECT * FROM spatial_data
WHERE ST_DWithin(geometry, ST_Point(0, 0), 1000);
-- Spatial joins with proper indexing
SELECT a.id, b.id
FROM spatial_data a
JOIN spatial_data b ON ST_Intersects(a.geometry, b.geometry)
WHERE a.id < b.id; -- Avoid duplicate pairs
-- Use spatial clustering
SELECT ST_ClusterKMeans(geometry, 10) as cluster_id, COUNT(*)
FROM spatial_points
GROUP BY ST_ClusterKMeans(geometry, 10);
Why: Appropriate spatial functions leverage spatial indexes effectively. Spatial clustering enables efficient data aggregation and analysis.