Skip to content

PostgreSQL Configuration Management Best Practices

Objective: Master senior-level PostgreSQL configuration management patterns for production systems. When you need to manage database configurations, when you want to implement configuration as code, when you need enterprise-grade configuration strategies—these best practices become your weapon of choice.

Core Principles

  • Configuration as Code: Version control all configuration changes
  • Environment Parity: Maintain consistency across environments
  • Automation: Automate configuration deployment
  • Validation: Validate configuration before deployment
  • Rollback: Implement safe configuration rollback

Configuration Management Patterns

Configuration Schema Design

-- Create configuration management tables
CREATE TABLE configuration_settings (
    id SERIAL PRIMARY KEY,
    setting_name VARCHAR(100) UNIQUE NOT NULL,
    setting_value TEXT NOT NULL,
    setting_type VARCHAR(20) NOT NULL,
    description TEXT,
    is_sensitive BOOLEAN DEFAULT FALSE,
    environment VARCHAR(20) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE configuration_history (
    id SERIAL PRIMARY KEY,
    setting_name VARCHAR(100) NOT NULL,
    old_value TEXT,
    new_value TEXT NOT NULL,
    changed_by VARCHAR(100) NOT NULL,
    change_reason TEXT,
    changed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE configuration_templates (
    id SERIAL PRIMARY KEY,
    template_name VARCHAR(100) UNIQUE NOT NULL,
    environment VARCHAR(20) NOT NULL,
    settings JSONB NOT NULL,
    description TEXT,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

Configuration Functions

-- Create function to get configuration value
CREATE OR REPLACE FUNCTION get_config_value(
    p_setting_name VARCHAR(100),
    p_environment VARCHAR(20) DEFAULT 'production'
)
RETURNS TEXT AS $$
DECLARE
    config_value TEXT;
BEGIN
    SELECT setting_value INTO config_value
    FROM configuration_settings
    WHERE setting_name = p_setting_name 
    AND environment = p_environment;

    RETURN config_value;
END;
$$ LANGUAGE plpgsql;

-- Create function to set configuration value
CREATE OR REPLACE FUNCTION set_config_value(
    p_setting_name VARCHAR(100),
    p_setting_value TEXT,
    p_setting_type VARCHAR(20),
    p_environment VARCHAR(20),
    p_description TEXT DEFAULT NULL,
    p_is_sensitive BOOLEAN DEFAULT FALSE,
    p_changed_by VARCHAR(100) DEFAULT current_user
)
RETURNS VOID AS $$
DECLARE
    old_value TEXT;
BEGIN
    -- Get old value for history
    SELECT setting_value INTO old_value
    FROM configuration_settings
    WHERE setting_name = p_setting_name AND environment = p_environment;

    -- Update or insert configuration
    INSERT INTO configuration_settings (
        setting_name, setting_value, setting_type, description, 
        is_sensitive, environment
    ) VALUES (
        p_setting_name, p_setting_value, p_setting_type, p_description,
        p_is_sensitive, p_environment
    ) ON CONFLICT (setting_name, environment) 
    DO UPDATE SET 
        setting_value = EXCLUDED.setting_value,
        setting_type = EXCLUDED.setting_type,
        description = EXCLUDED.description,
        is_sensitive = EXCLUDED.is_sensitive,
        updated_at = CURRENT_TIMESTAMP;

    -- Record change in history
    INSERT INTO configuration_history (
        setting_name, old_value, new_value, changed_by, change_reason
    ) VALUES (
        p_setting_name, old_value, p_setting_value, p_changed_by, 
        'Configuration updated'
    );
END;
$$ LANGUAGE plpgsql;

Environment-Specific Configuration

Environment Configuration Management

-- Create function to apply configuration template
CREATE OR REPLACE FUNCTION apply_configuration_template(
    p_template_name VARCHAR(100),
    p_environment VARCHAR(20),
    p_applied_by VARCHAR(100) DEFAULT current_user
)
RETURNS INTEGER AS $$
DECLARE
    template_settings JSONB;
    setting_key TEXT;
    setting_value TEXT;
    applied_count INTEGER := 0;
BEGIN
    -- Get template settings
    SELECT settings INTO template_settings
    FROM configuration_templates
    WHERE template_name = p_template_name AND environment = p_environment;

    IF template_settings IS NULL THEN
        RAISE EXCEPTION 'Template % not found for environment %', p_template_name, p_environment;
    END IF;

    -- Apply each setting from template
    FOR setting_key, setting_value IN 
        SELECT key, value FROM jsonb_each_text(template_settings)
    LOOP
        PERFORM set_config_value(
            setting_key, 
            setting_value, 
            'string', 
            p_environment, 
            'Applied from template ' || p_template_name,
            FALSE,
            p_applied_by
        );
        applied_count := applied_count + 1;
    END LOOP;

    RETURN applied_count;
END;
$$ LANGUAGE plpgsql;

-- Create function to validate configuration
CREATE OR REPLACE FUNCTION validate_configuration(p_environment VARCHAR(20))
RETURNS TABLE (
    setting_name VARCHAR(100),
    status TEXT,
    message TEXT
) AS $$
BEGIN
    -- Check required settings
    RETURN QUERY
    SELECT 
        'max_connections'::VARCHAR(100),
        CASE WHEN get_config_value('max_connections', p_environment) IS NOT NULL 
             THEN 'PASS' ELSE 'FAIL' END::TEXT,
        CASE WHEN get_config_value('max_connections', p_environment) IS NOT NULL 
             THEN 'max_connections is configured' 
             ELSE 'max_connections is missing' END::TEXT;

    RETURN QUERY
    SELECT 
        'shared_buffers'::VARCHAR(100),
        CASE WHEN get_config_value('shared_buffers', p_environment) IS NOT NULL 
             THEN 'PASS' ELSE 'FAIL' END::TEXT,
        CASE WHEN get_config_value('shared_buffers', p_environment) IS NOT NULL 
             THEN 'shared_buffers is configured' 
             ELSE 'shared_buffers is missing' END::TEXT;

    RETURN QUERY
    SELECT 
        'wal_level'::VARCHAR(100),
        CASE WHEN get_config_value('wal_level', p_environment) IS NOT NULL 
             THEN 'PASS' ELSE 'FAIL' END::TEXT,
        CASE WHEN get_config_value('wal_level', p_environment) IS NOT NULL 
             THEN 'wal_level is configured' 
             ELSE 'wal_level is missing' END::TEXT;
END;
$$ LANGUAGE plpgsql;

Configuration Automation

Configuration Deployment

# configuration/config_manager.py
import psycopg2
import json
import yaml
from datetime import datetime
import logging

class PostgreSQLConfigManager:
    def __init__(self, connection_params):
        self.conn_params = connection_params
        self.setup_logging()

    def setup_logging(self):
        """Setup logging."""
        logging.basicConfig(level=logging.INFO)
        self.logger = logging.getLogger(__name__)

    def load_config_from_file(self, config_file):
        """Load configuration from YAML file."""
        with open(config_file, 'r') as f:
            config = yaml.safe_load(f)
        return config

    def apply_configuration(self, config_data, environment):
        """Apply configuration to database."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                for setting_name, setting_value in config_data.items():
                    cur.execute("""
                        SELECT set_config_value(%s, %s, %s, %s, %s, %s, %s)
                    """, (
                        setting_name,
                        str(setting_value),
                        'string',
                        environment,
                        f'Applied from config file',
                        False,
                        'config_manager'
                    ))

                conn.commit()
                self.logger.info(f"Configuration applied for environment {environment}")

        except Exception as e:
            self.logger.error(f"Configuration application failed: {e}")
            conn.rollback()
            raise
        finally:
            conn.close()

    def get_configuration(self, environment):
        """Get current configuration for environment."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                cur.execute("""
                    SELECT setting_name, setting_value, setting_type, description
                    FROM configuration_settings
                    WHERE environment = %s
                    ORDER BY setting_name
                """, (environment,))

                config = cur.fetchall()
                return config

        except Exception as e:
            self.logger.error(f"Error getting configuration: {e}")
            return []
        finally:
            conn.close()

    def validate_configuration(self, environment):
        """Validate configuration for environment."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                cur.execute("SELECT * FROM validate_configuration(%s)", (environment,))

                validation_results = cur.fetchall()
                return validation_results

        except Exception as e:
            self.logger.error(f"Error validating configuration: {e}")
            return []
        finally:
            conn.close()

    def rollback_configuration(self, environment, rollback_to_datetime):
        """Rollback configuration to specific datetime."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                # Get configuration state at rollback time
                cur.execute("""
                    SELECT DISTINCT ON (setting_name) setting_name, old_value
                    FROM configuration_history
                    WHERE changed_at <= %s
                    ORDER BY setting_name, changed_at DESC
                """, (rollback_to_datetime,))

                rollback_settings = cur.fetchall()

                # Apply rollback settings
                for setting_name, old_value in rollback_settings:
                    if old_value is not None:
                        cur.execute("""
                            SELECT set_config_value(%s, %s, %s, %s, %s, %s, %s)
                        """, (
                            setting_name,
                            old_value,
                            'string',
                            environment,
                            f'Rollback to {rollback_to_datetime}',
                            False,
                            'config_manager'
                        ))

                conn.commit()
                self.logger.info(f"Configuration rolled back for environment {environment}")

        except Exception as e:
            self.logger.error(f"Configuration rollback failed: {e}")
            conn.rollback()
            raise
        finally:
            conn.close()

# Usage
if __name__ == "__main__":
    config_manager = PostgreSQLConfigManager({
        'host': 'localhost',
        'database': 'production',
        'user': 'config_user',
        'password': 'config_password'
    })

    # Load and apply configuration
    config_data = config_manager.load_config_from_file('config/production.yaml')
    config_manager.apply_configuration(config_data, 'production')

    # Validate configuration
    validation_results = config_manager.validate_configuration('production')
    print(json.dumps(validation_results, indent=2))

Configuration Templates

Template Management

-- Create function to create configuration template
CREATE OR REPLACE FUNCTION create_configuration_template(
    p_template_name VARCHAR(100),
    p_environment VARCHAR(20),
    p_settings JSONB,
    p_description TEXT DEFAULT NULL
)
RETURNS VOID AS $$
BEGIN
    INSERT INTO configuration_templates (
        template_name, environment, settings, description
    ) VALUES (
        p_template_name, p_environment, p_settings, p_description
    ) ON CONFLICT (template_name, environment) 
    DO UPDATE SET 
        settings = EXCLUDED.settings,
        description = EXCLUDED.description;
END;
$$ LANGUAGE plpgsql;

-- Create function to get configuration template
CREATE OR REPLACE FUNCTION get_configuration_template(
    p_template_name VARCHAR(100),
    p_environment VARCHAR(20)
)
RETURNS JSONB AS $$
DECLARE
    template_settings JSONB;
BEGIN
    SELECT settings INTO template_settings
    FROM configuration_templates
    WHERE template_name = p_template_name AND environment = p_environment;

    RETURN template_settings;
END;
$$ LANGUAGE plpgsql;

Configuration Validation

-- Create function to validate configuration values
CREATE OR REPLACE FUNCTION validate_configuration_value(
    p_setting_name VARCHAR(100),
    p_setting_value TEXT,
    p_setting_type VARCHAR(20)
)
RETURNS BOOLEAN AS $$
BEGIN
    -- Validate based on setting type
    CASE p_setting_type
        WHEN 'integer' THEN
            RETURN p_setting_value ~ '^\d+$';
        WHEN 'boolean' THEN
            RETURN p_setting_value IN ('true', 'false', 'on', 'off', 'yes', 'no');
        WHEN 'size' THEN
            RETURN p_setting_value ~ '^\d+[KMGT]?B?$';
        WHEN 'time' THEN
            RETURN p_setting_value ~ '^\d+[smhd]?$';
        ELSE
            RETURN TRUE; -- String type, always valid
    END CASE;
END;
$$ LANGUAGE plpgsql;

-- Create function to validate all configuration
CREATE OR REPLACE FUNCTION validate_all_configuration(p_environment VARCHAR(20))
RETURNS TABLE (
    setting_name VARCHAR(100),
    setting_value TEXT,
    setting_type VARCHAR(20),
    is_valid BOOLEAN,
    error_message TEXT
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        cs.setting_name,
        cs.setting_value,
        cs.setting_type,
        validate_configuration_value(cs.setting_name, cs.setting_value, cs.setting_type) as is_valid,
        CASE 
            WHEN validate_configuration_value(cs.setting_name, cs.setting_value, cs.setting_type) THEN NULL
            ELSE 'Invalid value for ' || cs.setting_type || ' type'
        END as error_message
    FROM configuration_settings cs
    WHERE cs.environment = p_environment;
END;
$$ LANGUAGE plpgsql;

Configuration Monitoring

Configuration Change Tracking

-- Create function to get configuration changes
CREATE OR REPLACE FUNCTION get_configuration_changes(
    p_environment VARCHAR(20),
    p_start_date TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP - INTERVAL '7 days',
    p_end_date TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
)
RETURNS TABLE (
    setting_name VARCHAR(100),
    old_value TEXT,
    new_value TEXT,
    changed_by VARCHAR(100),
    change_reason TEXT,
    changed_at TIMESTAMPTZ
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        ch.setting_name,
        ch.old_value,
        ch.new_value,
        ch.changed_by,
        ch.change_reason,
        ch.changed_at
    FROM configuration_history ch
    WHERE ch.changed_at BETWEEN p_start_date AND p_end_date
    ORDER BY ch.changed_at DESC;
END;
$$ LANGUAGE plpgsql;

-- Create function to get configuration drift
CREATE OR REPLACE FUNCTION detect_configuration_drift(
    p_environment VARCHAR(20),
    p_template_name VARCHAR(100)
)
RETURNS TABLE (
    setting_name VARCHAR(100),
    current_value TEXT,
    template_value TEXT,
    is_drifted BOOLEAN
) AS $$
DECLARE
    template_settings JSONB;
BEGIN
    -- Get template settings
    SELECT settings INTO template_settings
    FROM configuration_templates
    WHERE template_name = p_template_name AND environment = p_environment;

    -- Compare current settings with template
    RETURN QUERY
    SELECT 
        cs.setting_name,
        cs.setting_value as current_value,
        template_settings ->> cs.setting_name as template_value,
        (cs.setting_value != (template_settings ->> cs.setting_name)) as is_drifted
    FROM configuration_settings cs
    WHERE cs.environment = p_environment
    AND template_settings ? cs.setting_name;
END;
$$ LANGUAGE plpgsql;

Configuration Monitoring

# monitoring/config_monitor.py
import psycopg2
import json
from datetime import datetime, timedelta
import logging

class ConfigurationMonitor:
    def __init__(self, connection_params):
        self.conn_params = connection_params
        self.setup_logging()

    def setup_logging(self):
        """Setup logging."""
        logging.basicConfig(level=logging.INFO)
        self.logger = logging.getLogger(__name__)

    def get_configuration_changes(self, environment, days=7):
        """Get configuration changes for the last N days."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                cur.execute("""
                    SELECT * FROM get_configuration_changes(%s, %s, %s)
                """, (environment, datetime.now() - timedelta(days=days), datetime.now()))

                changes = cur.fetchall()
                return changes

        except Exception as e:
            self.logger.error(f"Error getting configuration changes: {e}")
            return []
        finally:
            conn.close()

    def detect_configuration_drift(self, environment, template_name):
        """Detect configuration drift from template."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                cur.execute("""
                    SELECT * FROM detect_configuration_drift(%s, %s)
                """, (environment, template_name))

                drift = cur.fetchall()
                return drift

        except Exception as e:
            self.logger.error(f"Error detecting configuration drift: {e}")
            return []
        finally:
            conn.close()

    def get_configuration_summary(self, environment):
        """Get configuration summary for environment."""
        conn = psycopg2.connect(**self.conn_params)

        try:
            with conn.cursor() as cur:
                cur.execute("""
                    SELECT 
                        COUNT(*) as total_settings,
                        COUNT(*) FILTER (WHERE is_sensitive = true) as sensitive_settings,
                        COUNT(*) FILTER (WHERE updated_at > CURRENT_DATE - INTERVAL '7 days') as recent_changes
                    FROM configuration_settings
                    WHERE environment = %s
                """, (environment,))

                summary = cur.fetchone()
                return summary

        except Exception as e:
            self.logger.error(f"Error getting configuration summary: {e}")
            return None
        finally:
            conn.close()

    def generate_configuration_report(self, environment):
        """Generate comprehensive configuration report."""
        changes = self.get_configuration_changes(environment, 7)
        drift = self.detect_configuration_drift(environment, 'production_template')
        summary = self.get_configuration_summary(environment)

        report = {
            'report_timestamp': datetime.now().isoformat(),
            'environment': environment,
            'summary': summary,
            'recent_changes': changes,
            'configuration_drift': drift
        }

        return report

# Usage
if __name__ == "__main__":
    monitor = ConfigurationMonitor({
        'host': 'localhost',
        'database': 'production',
        'user': 'monitor_user',
        'password': 'monitor_password'
    })

    report = monitor.generate_configuration_report('production')
    print(json.dumps(report, indent=2))

TL;DR Runbook

Quick Start

-- 1. Create configuration tables
CREATE TABLE configuration_settings (
    id SERIAL PRIMARY KEY,
    setting_name VARCHAR(100) UNIQUE NOT NULL,
    setting_value TEXT NOT NULL,
    environment VARCHAR(20) NOT NULL
);

-- 2. Create configuration functions
CREATE OR REPLACE FUNCTION get_config_value(
    p_setting_name VARCHAR(100),
    p_environment VARCHAR(20)
) RETURNS TEXT AS $$
-- Function implementation
$$ LANGUAGE plpgsql;

-- 3. Set configuration values
SELECT set_config_value('max_connections', '200', 'integer', 'production');

-- 4. Get configuration values
SELECT get_config_value('max_connections', 'production');

Essential Patterns

# Complete PostgreSQL configuration management setup
def setup_postgresql_configuration_management():
    # 1. Configuration schema design
    # 2. Environment-specific configuration
    # 3. Configuration automation
    # 4. Configuration templates
    # 5. Configuration validation
    # 6. Configuration monitoring
    # 7. Change tracking
    # 8. Drift detection

    print("PostgreSQL configuration management setup complete!")

This guide provides the complete machinery for PostgreSQL configuration management excellence. Each pattern includes implementation examples, configuration strategies, and real-world usage patterns for enterprise PostgreSQL configuration systems.