PostgreSQL Development Environment Best Practices¶
Objective: Master senior-level PostgreSQL development environment setup for production systems. When you need to set up a robust development environment, when you want to optimize your PostgreSQL workflow, when you need enterprise-grade development patterns—these best practices become your weapon of choice.
Core Principles¶
- Version Management: Use multiple PostgreSQL versions for testing
- Extension Management: Essential extensions for development
- Configuration Optimization: Tuned settings for development
- Development Tools: Modern tooling for PostgreSQL development
- Testing Environment: Isolated testing with proper data management
Development Environment Setup¶
PostgreSQL Installation¶
# Install PostgreSQL using official repository
sudo apt update
sudo apt install -y postgresql-16 postgresql-client-16 postgresql-contrib-16
# Or using Homebrew on macOS
brew install postgresql@16
# Or using Docker for development
docker run --name postgres-dev \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=development \
-p 5432:5432 \
-d postgres:16
Version Management¶
# Using pg_versions for multiple PostgreSQL versions
curl -sSL https://raw.githubusercontent.com/markw/pg_versions/master/install.sh | bash
# Install multiple versions
pg_versions install 14
pg_versions install 15
pg_versions install 16
# Switch between versions
pg_versions use 16
Essential Extensions¶
-- Connect to your development database
\c development
-- Essential extensions for development
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
CREATE EXTENSION IF NOT EXISTS "btree_gin";
CREATE EXTENSION IF NOT EXISTS "btree_gist";
CREATE EXTENSION IF NOT EXISTS "hstore";
CREATE EXTENSION IF NOT EXISTS "ltree";
CREATE EXTENSION IF NOT EXISTS "unaccent";
-- For geospatial development
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";
-- For full-text search
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
-- For JSON operations
CREATE EXTENSION IF NOT EXISTS "jsquery";
-- For time series (if needed)
CREATE EXTENSION IF NOT EXISTS "timescaledb";
Development Configuration¶
PostgreSQL Configuration¶
# postgresql.conf for development
# /etc/postgresql/16/main/postgresql.conf
# Memory settings (adjust based on your system)
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
# WAL settings
wal_level = replica
max_wal_size = 1GB
min_wal_size = 80MB
checkpoint_completion_target = 0.9
# Logging for development
log_destination = 'stderr'
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
# Development-specific settings
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
max_parallel_workers_per_gather = 2
max_parallel_workers = 8
max_parallel_maintenance_workers = 2
# Security (development only)
ssl = off
Development Database Setup¶
-- Create development databases
CREATE DATABASE development;
CREATE DATABASE testing;
CREATE DATABASE staging;
-- Create development user with appropriate privileges
CREATE USER dev_user WITH PASSWORD 'dev_password';
GRANT ALL PRIVILEGES ON DATABASE development TO dev_user;
GRANT ALL PRIVILEGES ON DATABASE testing TO dev_user;
GRANT ALL PRIVILEGES ON DATABASE staging TO dev_user;
-- Grant schema privileges
\c development
GRANT ALL ON SCHEMA public TO dev_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO dev_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO dev_user;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO dev_user;
-- Set default privileges for future objects
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO dev_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO dev_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO dev_user;
Development Tools¶
Database Management Tools¶
# Install pgAdmin (GUI)
sudo apt install pgadmin4
# Install DBeaver (Cross-platform GUI)
wget https://dbeaver.io/files/dbeaver-ce_latest_amd64.deb
sudo dpkg -i dbeaver-ce_latest_amd64.deb
# Install psql (command line - usually included)
psql --version
Command Line Tools¶
# Install additional PostgreSQL tools
sudo apt install postgresql-client-common
# Install pg_dump and pg_restore utilities
sudo apt install postgresql-client-16
# Install pgbench for performance testing
sudo apt install postgresql-contrib-16
Development Scripts¶
#!/bin/bash
# scripts/dev-setup.sh
# Create development environment
echo "Setting up PostgreSQL development environment..."
# Create databases
psql -U postgres -c "CREATE DATABASE IF NOT EXISTS development;"
psql -U postgres -c "CREATE DATABASE IF NOT EXISTS testing;"
# Create user
psql -U postgres -c "CREATE USER IF NOT EXISTS dev_user WITH PASSWORD 'dev_password';"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE development TO dev_user;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE testing TO dev_user;"
# Install extensions
psql -U dev_user -d development -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"
psql -U dev_user -d development -c "CREATE EXTENSION IF NOT EXISTS \"pgcrypto\";"
psql -U dev_user -d development -c "CREATE EXTENSION IF NOT EXISTS \"pg_stat_statements\";"
echo "Development environment setup complete!"
Testing Environment¶
Test Database Setup¶
-- Create test database with isolated schema
CREATE DATABASE testing;
-- Connect to test database
\c testing
-- Create test schema
CREATE SCHEMA test_schema;
-- Create test user
CREATE USER test_user WITH PASSWORD 'test_password';
GRANT ALL PRIVILEGES ON DATABASE testing TO test_user;
GRANT ALL PRIVILEGES ON SCHEMA test_schema TO test_user;
-- Set up test data
CREATE TABLE test_schema.users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insert test data
INSERT INTO test_schema.users (username, email) VALUES
('testuser1', 'test1@example.com'),
('testuser2', 'test2@example.com'),
('testuser3', 'test3@example.com');
Automated Testing Setup¶
# tests/conftest.py
import pytest
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
@pytest.fixture(scope="session")
def test_db():
"""Create test database for the session."""
conn = psycopg2.connect(
host="localhost",
database="postgres",
user="postgres",
password="postgres"
)
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
with conn.cursor() as cur:
cur.execute("DROP DATABASE IF EXISTS test_db;")
cur.execute("CREATE DATABASE test_db;")
conn.close()
# Return connection to test database
test_conn = psycopg2.connect(
host="localhost",
database="test_db",
user="postgres",
password="postgres"
)
yield test_conn
test_conn.close()
# Cleanup
cleanup_conn = psycopg2.connect(
host="localhost",
database="postgres",
user="postgres",
password="postgres"
)
cleanup_conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
with cleanup_conn.cursor() as cur:
cur.execute("DROP DATABASE test_db;")
cleanup_conn.close()
@pytest.fixture
def test_table(test_db):
"""Create test table for each test."""
with test_db.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS test_table (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
value INTEGER
);
""")
test_db.commit()
yield test_db
# Cleanup after each test
with test_db.cursor() as cur:
cur.execute("DROP TABLE IF EXISTS test_table;")
test_db.commit()
Development Workflow¶
Database Migrations¶
# migrations/001_initial_schema.sql
-- Initial schema creation
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
title VARCHAR(200) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(published);
CREATE INDEX idx_posts_created_at ON posts(created_at);
Schema Versioning¶
-- Create schema versioning table
CREATE TABLE schema_migrations (
version VARCHAR(50) PRIMARY KEY,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insert initial version
INSERT INTO schema_migrations (version) VALUES ('001_initial_schema');
Development Data Management¶
# scripts/seed_data.py
import psycopg2
import uuid
from datetime import datetime, timedelta
import random
def seed_development_data():
"""Seed development database with sample data."""
conn = psycopg2.connect(
host="localhost",
database="development",
user="dev_user",
password="dev_password"
)
with conn.cursor() as cur:
# Clear existing data
cur.execute("TRUNCATE TABLE posts, users RESTART IDENTITY CASCADE;")
# Insert sample users
users_data = [
('john_doe', 'john@example.com', 'hashed_password_1'),
('jane_smith', 'jane@example.com', 'hashed_password_2'),
('bob_wilson', 'bob@example.com', 'hashed_password_3'),
('alice_brown', 'alice@example.com', 'hashed_password_4'),
('charlie_davis', 'charlie@example.com', 'hashed_password_5')
]
for username, email, password_hash in users_data:
cur.execute("""
INSERT INTO users (username, email, password_hash)
VALUES (%s, %s, %s)
""", (username, email, password_hash))
# Insert sample posts
post_titles = [
'Getting Started with PostgreSQL',
'Advanced SQL Techniques',
'Database Design Best Practices',
'Performance Optimization Tips',
'Security Considerations',
'Backup and Recovery Strategies',
'Monitoring and Maintenance',
'Scaling PostgreSQL',
'Replication Setup',
'Troubleshooting Common Issues'
]
for i in range(50):
user_id = random.randint(1, 5)
title = random.choice(post_titles)
content = f"This is sample content for post {i+1}. " * 10
published = random.choice([True, False])
created_at = datetime.now() - timedelta(days=random.randint(0, 30))
cur.execute("""
INSERT INTO posts (user_id, title, content, published, created_at)
VALUES (%s, %s, %s, %s, %s)
""", (user_id, title, content, published, created_at))
conn.commit()
print("Development data seeded successfully!")
conn.close()
if __name__ == "__main__":
seed_development_data()
Performance Monitoring¶
Query Performance Analysis¶
-- Enable query statistics
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- View slow queries
SELECT
query,
calls,
total_time,
mean_time,
rows,
100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
-- View table statistics
SELECT
schemaname,
tablename,
n_tup_ins,
n_tup_upd,
n_tup_del,
n_live_tup,
n_dead_tup,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;
Development Monitoring¶
# monitoring/dev_monitor.py
import psycopg2
import time
import json
from datetime import datetime
class PostgreSQLMonitor:
def __init__(self, connection_params):
self.conn_params = connection_params
self.conn = None
def connect(self):
"""Connect to PostgreSQL."""
self.conn = psycopg2.connect(**self.conn_params)
def get_connection_stats(self):
"""Get connection statistics."""
with self.conn.cursor() as cur:
cur.execute("""
SELECT
count(*) as total_connections,
count(*) FILTER (WHERE state = 'active') as active_connections,
count(*) FILTER (WHERE state = 'idle') as idle_connections
FROM pg_stat_activity;
""")
return cur.fetchone()
def get_database_size(self):
"""Get database size."""
with self.conn.cursor() as cur:
cur.execute("""
SELECT pg_size_pretty(pg_database_size(current_database()));
""")
return cur.fetchone()[0]
def get_table_sizes(self):
"""Get table sizes."""
with self.conn.cursor() as cur:
cur.execute("""
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;
""")
return cur.fetchall()
def monitor_loop(self, interval=60):
"""Run monitoring loop."""
while True:
try:
stats = {
'timestamp': datetime.now().isoformat(),
'connections': self.get_connection_stats(),
'database_size': self.get_database_size(),
'table_sizes': self.get_table_sizes()
}
print(json.dumps(stats, indent=2))
time.sleep(interval)
except Exception as e:
print(f"Monitoring error: {e}")
time.sleep(interval)
# Usage
if __name__ == "__main__":
monitor = PostgreSQLMonitor({
'host': 'localhost',
'database': 'development',
'user': 'dev_user',
'password': 'dev_password'
})
monitor.connect()
monitor.monitor_loop(interval=30)
TL;DR Runbook¶
Quick Start¶
# 1. Install PostgreSQL
sudo apt install postgresql-16 postgresql-client-16 postgresql-contrib-16
# 2. Create development databases
psql -U postgres -c "CREATE DATABASE development;"
psql -U postgres -c "CREATE DATABASE testing;"
# 3. Create development user
psql -U postgres -c "CREATE USER dev_user WITH PASSWORD 'dev_password';"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE development TO dev_user;"
# 4. Install extensions
psql -U dev_user -d development -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"
psql -U dev_user -d development -c "CREATE EXTENSION IF NOT EXISTS \"pgcrypto\";"
Essential Patterns¶
# Complete PostgreSQL development setup
def setup_postgresql_development():
# 1. PostgreSQL installation
# 2. Version management
# 3. Essential extensions
# 4. Development configuration
# 5. Testing environment
# 6. Development tools
# 7. Performance monitoring
# 8. Data management
print("PostgreSQL development environment setup complete!")
This guide provides the complete machinery for PostgreSQL development excellence. Each pattern includes implementation examples, configuration strategies, and real-world usage patterns for enterprise PostgreSQL development systems.