TechSetupGuides
Beginnerpostgresqlpostgresdatabasesqlrelationalrdbmsopen-sourcejsongisanalytics

PostgreSQL - Advanced Relational Database

Install, configure, and use PostgreSQL, the world's most advanced open-source relational database — covering installation, advanced features, performance tuning, and production deployments.

  1. Step 1

    Overview

    PostgreSQL (also known as Postgres) is a powerful, open-source object-relational database system that uses and extends the SQL language. It has earned a reputation for reliability, feature robustness, and performance.

    Key capabilities:

    • ACID Compliance: Full transactional integrity with MVCC
    • Extensibility: User-defined types, functions, operators, and indexing methods
    • Standards Compliance: Implements most SQL:2016 features
    • JSON Support: Native JSON/JSONB for NoSQL use cases
    • Full-Text Search: Built-in PostgreSQL search engine
    • Geospatial: PostGIS extension for GIS applications
    • Advanced Indexing: B-tree, Hash, GiST, SP-GiST, BRIN, GIN
    • Concurrency: MVCC allows readers never to block writers

    Why PostgreSQL:

    • Open Source: Free with no vendor lock-in
    • Reliability: Battle-tested at massive scale
    • Features: Most feature-rich open-source database
    • Performance: Excellent for complex queries and analytics
    • Community: Active development and large community
    Official site: https://www.postgresql.org
    GitHub: https://github.com/postgres/postgres (14K+ stars)
    Documentation: https://www.postgresql.org/docs/
    
    Key features:
    - ACID transactions with MVCC
    - Extensible (user-defined types, functions, operators)
    - Advanced indexing (B-tree, GIN, GiST, BRIN, etc.)
    - Full JSON/JSONB support
    - Foreign data wrappers for distributed queries
    - Point-in-time recovery (PITR)
    - Tablespaces for storage management
  2. Step 2

    Quick Installation Options

    Multiple installation methods available depending on your platform and use case:

    # Option 1: Docker (recommended for development)
    docker run -d \
      --name postgres \
      -e POSTGRES_PASSWORD=mysecretpassword \
      -e POSTGRES_DB=mydatabase \
      -p 5432:5432 \
      postgres:16
    
    # Option 2: Homebrew (macOS)
    brew install postgresql@16
    brew services start postgresql@16
    
    # Option 3: APT (Debian/Ubuntu)
    sudo apt update
    sudo apt install postgresql postgresql-contrib
    sudo systemctl start postgresql
    
    # Option 4: YUM (RHEL/CentOS/Fedora)
    sudo yum install postgresql-server postgresql-contrib
    sudo postgresql-setup --initdb
    sudo systemctl start postgresql
    
    # Option 5: Windows (Chocolatey)
    choco install postgresql
    
    # Verify installation
    psql --version
    psql -U postgres -c "SELECT version();"
    
    # Connect to database
    psql -U postgres
    # or with password:
    psql -U postgres -W
  3. Step 3

    Build from Source (Ubuntu 22.04/24.04)

    Building PostgreSQL from source gives you the latest features and custom compilation options. This is recommended for production deployments or when you need specific features.

    # Install build dependencies
    sudo apt update
    sudo apt install -y \
      build-essential \
      libreadline-dev \
      zlib1g-dev \
      libssl-dev \
      libxml2-dev \
      libjson-c-dev \
      libkrb5-dev \
      wget \
      curl
    
    # Download PostgreSQL source (version 16 latest stable)
    wed https://ftp.postgresql.org/pub/source/v16.3/postgresql-16.3.tar.gz
    tar xzf postgresql-16.3.tar.gz
    cd postgresql-16.3
    
    # Configure build
    # Default install location: /usr/local/pgsql
    ./configure --prefix=/usr/local/pgsql \
                --with-openssl \
                --with-libxml \
                --with-libjson \
                --with-python \
                --with-perl \
                --with-tcl
    
    # Build (use -j for parallel compilation)
    make -j$(nproc)
    
    # Run regression tests
    make install-world
    
    # Install
    sudo make install
    
    # Create directories for data and configuration
    sudo mkdir -p /usr/local/pgsql/data
    sudo chown $USER /usr/local/pgsql/data
    
    # Initialize database cluster
    /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data
    
    # Start PostgreSQL
    /usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data start
    
    # Add to PATH
    export PATH=/usr/local/pgsql/bin:$PATH
    
    # Verify installation
    psql --version
    psql -c "SELECT version();"
  4. Step 4

    Initial Configuration

    PostgreSQL uses two main configuration files:

    postgresql.conf - Server runtime configuration

    • Memory settings (shared_buffers, work_mem)
    • Connection settings (max_connections, listen_addresses)
    • Logging (log_destination, logging_collector)
    • WAL settings for replication

    pg_hba.conf - Client authentication configuration

    • Defines which clients can connect
    • Authentication methods (trust, password, md5, scram-sha-256, cert)

    Key settings to customize:

    • listen_addresses: Where to accept connections
    • port: TCP port (default: 5432)
    • shared_buffers: Memory for caching (25% of RAM recommended)
    • max_connections: Maximum simultaneous connections
    • log_statement: Query logging for debugging
    # postgresql.conf - Essential settings
    
    # Connections
    listen_addresses = '*'          # Accept connections from anywhere (use 'localhost' for local only)
    port = 5432                     # Default port
    max_connections = 100           # Increase for production
    
    # Memory Tuning (adjust based on available RAM)
    shared_buffers = 256MB          # 25% of RAM, minimum 128MB
    effective_cache_size = 2GB      # 50-75% of RAM
    work_mem = 16MB                # Per-operation memory
    maintenance_work_mem = 256MB    # For VACUUM, CREATE INDEX
    
    # WAL Settings
    wal_level = replica            # 'minimal', 'replica', or 'logical'
    max_wal_size = 2GB             # Before autovacuum triggers
    min_wal_size = 1GB
    
    # Logging (enable for production)
    logging_collector = on
    log_directory = 'log'
    log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
    log_statement = 'ddl'          # Log all DDL or 'mod' for DML too
    log_min_duration_statement = 1000  # Log queries taking > 1s
    
    # Autovacuum (keep enabled!)
    autovacuum = on
    autovacuum_max_workers = 3
    autovacuum_naptime = 1min
    
    # pg_hba.conf - Client authentication (simplified)
    # TYPE  DATABASE        USER            ADDRESS                 METHOD
    local   all             all                                     peer
    host    all             all             127.0.0.1/32            scram-sha-256
    host    all             all             ::1/128                 scram-sha-256
    host    all             all             0.0.0.0/0               scram-sha-256
  5. Step 5

    Basic Usage & SQL

    PostgreSQL supports standard SQL with many extensions. Use psql as the command-line client:

    Basic operations:

    • CREATE, INSERT, UPDATE, DELETE
    • Complex queries with JOINs, subqueries, CTEs
    • Stored procedures and functions (PL/pgSQL)
    • Triggers and constraints
    • Window functions and aggregates

    psql meta-commands:

    • \l - List databases
    • \c dbname - Connect to database
    • \dt - List tables
    • \d table - Describe table
    • \dx - List extensions
    • \q - Quit
    -- Start psql
    psql -U postgres
    
    -- Create a database
    CREATE DATABASE myapp;
    
    -- Connect to it
    \c myapp
    
    -- Create a table
    CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        username VARCHAR(50) UNIQUE NOT NULL,
        email VARCHAR(100) UNIQUE NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        is_active BOOLEAN DEFAULT true
    );
    
    -- Insert data
    INSERT INTO users (username, email) VALUES
        ('alice', 'alice@example.com'),
        ('bob', 'bob@example.com'),
        ('charlie', 'charlie@example.com');
    
    -- Query data
    SELECT * FROM users;
    SELECT username, email FROM users WHERE is_active = true;
    
    -- Update data
    UPDATE users SET is_active = false WHERE username = 'bob';
    
    -- Delete data
    DELETE FROM users WHERE username = 'charlie';
    
    -- Complex query with aggregation
    SELECT 
        COUNT(*) as total_users,
        SUM(CASE WHEN is_active THEN 1 ELSE 0 END) as active_users
    FROM users;
    
    -- Use CTEs (Common Table Expressions)
    WITH active_users AS (
        SELECT * FROM users WHERE is_active = true
    )
    SELECT * FROM active_users;
    
    -- Window functions
    SELECT 
        username,
        email,
        ROW_NUMBER() OVER (ORDER BY created_at) as row_num,
        RANK() OVER (ORDER BY created_at DESC) as rank
    FROM users;
    
    -- Exit psql
    \q
  6. Step 6

    JSON & JSONB Support

    PostgreSQL has excellent JSON support with two types:

    JSON: Stores exact text representation, preserves formatting JSONB: Binary JSON, optimized for storage and querying, recommended for most use cases

    JSONB supports indexing and operators for efficient querying, making PostgreSQL a viable NoSQL alternative.

    -- Create table with JSONB column
    CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        attributes JSONB,
        metadata JSONB
    );
    
    -- Insert JSON data
    INSERT INTO products (name, attributes, metadata) VALUES
        ('Laptop', 
         '{"cpu": "i7", "ram": 16, "storage": "512GB SSD"}',
         '{"color": "silver", "warranty": "2 years", "tags": ["electronics", "portable"]}');
    
    -- Query JSONB data
    SELECT name, attributes->>'cpu' as cpu FROM products;
    SELECT name, attributes->'ram' as ram FROM products;
    
    -- Query nested JSON
    SELECT * FROM products 
    WHERE attributes @> '{"cpu": "i7"}';
    
    -- Check if JSON contains key
    SELECT * FROM products 
    WHERE attributes ? 'cpu';
    
    -- Array operations
    SELECT metadata->'tags'->>0 as first_tag FROM products;
    
    -- Update JSON field
    UPDATE products 
    SET attributes = attributes || '{"battery": "8 hours"}'
    WHERE id = 1;
    
    -- Create index on JSON field for faster queries
    CREATE INDEX idx_products_cpu ON products ((attributes->>'cpu'));
    CREATE INDEX idx_products_tags ON products USING GIN (metadata->'tags');
    
    -- Query with JSON aggregation
    SELECT json_agg(row_to_json(p.*))
    FROM (
        SELECT id, name, attributes->>'cpu' as cpu
        FROM products
    ) p;
  7. Step 7

    Full-Text Search

    PostgreSQL has built-in full-text search capabilities. Create search indexes using tsvector and tsquery:

    Key components:

    • tsvector - Document representation for searching
    • tsquery - Search query representation
    • to_tsvector() - Convert text to tsvector
    • to_tsquery() - Convert query to tsquery
    • @@ - Match operator

    Features:

    • Stemming and normalization
    • Ranking by relevance
    • Multiple language support
    -- Create table with full-text search
    CREATE TABLE articles (
        id SERIAL PRIMARY KEY,
        title VARCHAR(200),
        content TEXT,
        search_vector tsvector
    );
    
    -- Create search vector from text
    UPDATE articles 
    SET search_vector = to_tsvector('english', title || ' ' || content);
    
    -- Create GIN index for fast search
    CREATE INDEX idx_articles_search ON articles USING GIN(search_vector);
    
    -- Search for documents
    SELECT id, title 
    FROM articles 
    WHERE search_vector @@ to_tsquery('english', 'database & postgresql');
    
    -- Ranked search (most relevant first)
    SELECT id, title, 
           ts_rank(search_vector, to_tsquery('english', 'database optimization')) as rank
    FROM articles 
    WHERE search_vector @@ to_tsquery('english', 'database optimization')
    ORDER BY rank DESC;
    
    -- Phrase search
    SELECT * FROM articles 
    WHERE search_vector @@ to_tsquery('english', ''"database optimization"'');
    
    -- Search with stemming (finds "optimize", "optimized", etc.)
    SELECT * FROM articles 
    WHERE search_vector @@ to_tsquery('english', 'optimization');
    
    -- Update trigger for automatic search vector maintenance
    CREATE FUNCTION update_search_vector() RETURNS trigger AS $$
    BEGIN
        NEW.search_vector := to_tsvector('english', COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.content, ''));
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE TRIGGER articles_search_vector_update
    BEFORE INSERT OR UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION update_search_vector();
    
    -- Fuzzy search with plainto_tsquery (more user-friendly)
    SELECT * FROM articles 
    WHERE search_vector @@ plainto_tsquery('english', 'database AND postgresql');
    
    -- Highlighting search results
    SELECT id, title,
           highlight(search_vector, to_tsquery('english', 'database')) as snippet
    FROM articles
    WHERE search_vector @@ to_tsquery('english', 'database');
  8. Step 8

    Advanced Indexing

    PostgreSQL supports multiple indexing methods for different use cases:

    B-tree: Default, for equality and range queries GIN: For JSONB, arrays, full-text search GiST: For geometric data, full-text, and custom operators BRIN: Block Range Index, excellent for very large tables Hash: For equality-only lookups

    Choose the right index type based on your query patterns.

    -- B-tree index (default, most common)
    CREATE INDEX idx_users_email ON users(email);
    CREATE INDEX idx_users_active ON users(is_active);
    
    -- Composite index for multi-column queries
    CREATE INDEX idx_users_active_email ON users(is_active, email);
    
    -- Partial index (only for subset of data)
    CREATE INDEX idx_users_active_only ON users(email) WHERE is_active = true;
    
    -- Expression index
    CREATE INDEX idx_users_lower_email ON users(LOWER(email));
    
    -- GIN index for JSONB columns
    CREATE INDEX idx_products_attributes ON products USING GIN(attributes);
    
    -- GIN index for arrays
    CREATE TABLE tags (
        id SERIAL PRIMARY KEY,
        name TEXT[]
    );
    CREATE INDEX idx_tags_name ON tags USING GIN(name);
    
    -- BRIN index for large tables with natural ordering
    CREATE TABLE sensor_readings (
        id SERIAL,
        timestamp TIMESTAMP,
        value DECIMAL
    );
    CREATE INDEX idx_sensor_timestamp ON sensor_readings USING BRIN (timestamp);
    
    -- Concurrent index creation (doesn't lock table)
    CREATE INDEX CONCURRENTLY idx_large_table ON large_table(column);
    
    -- Check index usage
    EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
    
    -- View indexes on table
    \d users
    
    -- Index statistics
    SELECT 
        schemaname,
        tablename,
        indexname,
        pg_size_pretty(pg_relation_size(indexname::regclass)) as index_size
    FROM pg_stat_user_indexes
    WHERE schemaname = 'public'
    ORDER BY pg_relation_size(indexname::regclass) DESC;
  9. Step 9

    Performance Tuning

    Optimize PostgreSQL for your workload:

    Memory allocation:

    • shared_buffers: 25% of RAM (min 128MB)
    • effective_cache_size: 50-75% of RAM
    • work_mem: Per-operation (sorts, hashes) - be conservative
    • maintenance_work_mem: For VACUUM, CREATE INDEX

    Connection management:

    • Use connection pooling (pgbouncer)
    • Limit max_connections appropriately
    • Configure idle timeouts

    Query optimization:

    • Use EXPLAIN ANALYZE to find slow queries
    • Add indexes for common query patterns
    • Avoid SELECT * in production
    • Use proper JOIN types
    # Check current configuration
    psql -c "SHOW shared_buffers;"
    psql -c "SHOW work_mem;"
    psql -c "SHOW effective_cache_size;"
    
    # Analyze slow queries
    psql -c "SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10;"
    
    # Check table statistics
    psql -c "SELECT relname, n_live_tup, n_dead_tup, last_vacuum, last_analyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;"
    
    # Run VACUUM to clean up dead tuples
    VACUUM ANALYZE;
    
    # Check index usage
    psql -c "SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch FROM pg_stat_user_indexes ORDER BY idx_scan ASC;"
    
    # Analyze specific query
    EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
    
    # Autovacuum configuration (postgresql.conf)
    # autovacuum = on
    # autovacuum_max_workers = 3
    # autovacuum_naptime = 1min
    # autovacuum_vacuum_threshold = 50
    # autovacuum_analyze_threshold = 50
    
    # Connection pooling with pgbouncer
    # sudo apt install pgbouncer
    # Edit /etc/pgbouncer/pgbouncer.ini
    # [databases]
    # postgres = host=127.0.0.1 port=5432
    # [pgbouncer]
    # listen_port = 6432
    # auth_type = md5
  10. Step 10

    Backup & Recovery

    Protect your data with regular backups:

    Backup methods:

    • pg_dump: Logical backup of single database
    • pg_dumpall: Backup all databases
    • pg_basebackup: Physical backup for replication
    • PITR: Point-in-time recovery using WAL

    Best practices:

    • Regular scheduled backups
    • Test restoration procedures
    • Offsite backup storage
    • WAL archiving for continuous backup
    # Backup single database (custom format, compressed)
    pg_dump -h localhost -U postgres -Fc -f backup.dump mydatabase
    
    # Backup all databases
    pg_dumpall -h localhost -U postgres -f all_databases.sql
    
    # Backup specific table
    pg_dump -h localhost -U postgres -t users -f users.sql mydatabase
    
    # Restore from custom format
    pg_restore -h localhost -U postgres -d mydatabase backup.dump
    
    # Restore from SQL dump
    psql -h localhost -U postgres mydatabase < backup.sql
    
    # Create backup with WAL for point-in-time recovery
    # Enable in postgresql.conf:
    archive_mode = on
    archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'
    
    # Schedule daily backups with cron
    crontab -e
    # 0 2 * * * pg_dump -U postgres mydatabase | gzip > /backups/mydatabase-$(date +\%Y\%m\%d).sql.gz
    
    # Verify backup
    pg_restore -l backup.dump
    
    # Backup size check
    ls -lh /backups/*.sql.gz
    
    # Point-in-time recovery
    # 1. Stop PostgreSQL
    # 2. Remove data directory contents
    # 3. Restore from base backup
    # 4. Configure recovery target in postgresql.conf
    # recovery_target_time = '2024-01-15 10:00:00'
    # 5. Start PostgreSQL
  11. Step 11

    Extensions

    PostgreSQL can be extended with powerful modules:

    Popular extensions:

    • PostGIS: Geographic information system
    • pg_stat_statements: Query performance statistics
    • uuid-ossp: UUID generation
    • pg_trgm: Text similarity and trigram indexing
    • pgcrypto: Cryptographic functions
    • hstore: Key-value pairs

    Enable extensions to add functionality without application changes.

    -- List available extensions
    \dx
    
    -- Install pg_stat_statements for query analysis
    CREATE EXTENSION pg_stat_statements;
    
    -- View query statistics
    SELECT query, calls, total_time, mean_time, rows
    FROM pg_stat_statements
    ORDER BY mean_time DESC
    LIMIT 10;
    
    -- Install UUID support
    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
    
    -- Create table with UUID primary key
    CREATE TABLE sessions (
        id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
        user_id INTEGER,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- Insert with UUID
    INSERT INTO sessions (user_id) VALUES (1);
    SELECT * FROM sessions;
    
    -- Install PostGIS for GIS (requires system package)
    -- sudo apt install postgresql-16-postgis-3
    CREATE EXTENSION postgis;
    
    -- Create table with geometry
    CREATE TABLE locations (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        geom GEOMETRY(Point, 4326)
    );
    
    -- Insert geographic data
    INSERT INTO locations (name, geom) VALUES
        ('New York', ST_GeomFromText('POINT(-74.0060 40.7128)', 4326));
    
    -- Query distance between points
    SELECT name, 
           ST_Distance(
               geom, 
               ST_GeomFromText('POINT(-73.9352 40.7306)', 4326)
           ) as distance_meters
    FROM locations;
    
    -- Install pg_trgm for text similarity
    CREATE EXTENSION pg_trgm;
    
    -- Similar string search
    SELECT 'postgresql' || '' as similarity,
           similarity('postgresql', 'postgres') as similarity_score;
    
    -- Create trigram index
    CREATE INDEX idx_users_username_trgm ON users USING GIN (username gin_trgm_ops);
    
    -- Fuzzy search
    SELECT * FROM users 
    WHERE username ILIKE '%john%';
    
    -- Install pgcrypto for encryption
    CREATE EXTENSION pgcrypto;
    
    -- Encrypt/decrypt data
    SELECT pgp_sym_encrypt('secret data', 'secret key');
    SELECT pgp_sym_decrypt(encrypted_column, 'secret key') FROM table;
    
    -- Install hstore for key-value
    CREATE EXTENSION hstore;
    
    -- Use hstore column
    CREATE TABLE config (
        id SERIAL PRIMARY KEY,
        settings hstore
    );
    
    INSERT INTO config (settings) VALUES ('key1=>value1, key2=>value2');
    SELECT settings->'key1' FROM config;
  12. Step 12

    Replication & High Availability

    Set up replication for high availability and read scaling:

    Streaming replication:

    • One primary (master) server
    • Multiple replica (standby) servers
    • Synchronous or asynchronous replication
    • Automatic failover with Patroni

    Benefits:

    • Read scalability (query replicas)
    • High availability (failover)
    • Disaster recovery
    • Reporting offload
    # Configure primary server (postgresql.conf)
    wal_level = replica
    max_wal_senders = 3
    wal_keep_size = 64MB
    hot_standby = on
    
    # Configure pg_hba.conf for replica connections
    host    replication     replicator      10.0.0.0/24       scram-sha-256
    
    # Create replication user
    CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secure_password';
    
    # Take base backup on primary
    pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/backup -Fp -Xs -P
    
    # Configure standby server (postgresql.conf)
    primary_conninfo = 'host=primary_host port=5432 user=replicator password=secure_password'
    hot_standby = on
    
    # Recovery configuration (standby.signal file)
    # Create empty file to indicate this is a standby
    touch /var/lib/postgresql/data/standby.signal
    
    # Start standby server
    pg_ctl -D /var/lib/postgresql/data start
    
    # Verify replication status (on primary)
    SELECT * FROM pg_stat_replication;
    
    # Check replica lag
    SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
    FROM pg_stat_replication;
    
    # Query on standby (read-only)
    psql -h standby_host -U postgres -c "SELECT * FROM users;"
    
    # Synchronous replication (stronger consistency)
    # In postgresql.conf:
    synchronous_standby_names = 'standby1'
    
    # Patroni for automatic failover (recommended for production)
    # Install Patroni on all nodes
    # pip install patroni
    # Configure with etcd or Consul for consensus
  13. Step 13

    Client Libraries & ORMs

    Connect to PostgreSQL from your applications:

    Official drivers:

    • Python: psycopg2, psycopg3
    • Node.js: node-postgres (pg)
    • Java: PostgreSQL JDBC Driver
    • Go: lib/pq, pgx
    • PHP: PDO, pgsql
    • Ruby: pg

    ORMs with PostgreSQL support:

    • Django ORM
    • SQLAlchemy (Python)
    • Prisma (TypeScript)
    • Hibernate (Java)
    • Rails ActiveRecord (Ruby)
    # Python with psycopg3 (latest)
    import psycopg
    
    conn = psycopg.connect(
        host="localhost",
        port=5432,
        database="mydb",
        user="postgres",
        password="mypassword"
    )
    
    with conn.cursor() as cur:
        cur.execute("INSERT INTO users (username, email) VALUES (%s, %s)",
                    ("alice", "alice@example.com"))
        cur.execute("SELECT * FROM users WHERE is_active = %s", (True,))
        rows = cur.fetchall()
        for row in rows:
            print(row)
    
    conn.commit()
    conn.close()
    
    # Python with SQLAlchemy ORM
    from sqlalchemy import create_engine, Column, Integer, String, Boolean
    from sqlalchemy.orm import declarative_base, sessionmaker
    
    engine = create_engine('postgresql://postgres:mypassword@localhost/mydb')
    Base = declarative_base()
    SessionLocal = sessionmaker(bind=engine)
    
    class User(Base):
        __tablename__ = 'users'
        id = Column(Integer, primary_key=True)
        username = Column(String(50), unique=True)
        email = Column(String(100), unique=True)
        is_active = Column(Boolean, default=True)
    
    # Create tables
    Base.metadata.create_all(engine)
    
    # Use session
    session = SessionLocal()
    user = User(username="bob", email="bob@example.com")
    session.add(user)
    session.commit()
    
    # Query
    users = session.query(User).filter(User.is_active == True).all()
    for u in users:
        print(u.username)
    
    # Node.js with pg
    const { Pool } = require('pg');
    
    const pool = new Pool({
        host: 'localhost',
        port: 5432,
        database: 'mydb',
        user: 'postgres',
        password: 'mypassword'
    });
    
    async function query() {
        const result = await pool.query('SELECT * FROM users WHERE is_active = $1', [true]);
        console.log(result.rows);
    }
    
    query().then(() => pool.end());
    
    # Go with pgx
    import "github.com/jackc/pgx/v5"
    
    conn, err := pgx.Connect(ctx, "postgres://postgres:mypassword@localhost:5432/mydb")
    if err != nil {
        log.Fatal("Failed to connect:", err)
    }
    defer conn.Close(ctx)
    
    var username string
    err = conn.QueryRow(ctx, "SELECT username FROM users WHERE id = $1", 1).Scan(&username)
    if err != nil {
        log.Fatal("Query failed:", err)
    }
    fmt.Println(username)
    
    # Java with JDBC
    Connection conn = DriverManager.getConnection(
        "jdbc:postgresql://localhost:5432/mydb",
        "postgres", "mypassword"
    );
    
    PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
    stmt.setInt(1, 1);
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
        String username = rs.getString("username");
        System.out.println(username);
    }
  14. Step 14

    Monitoring & Administration

    Monitor PostgreSQL health and performance:

    Key metrics:

    • Connection count and wait events
    • Query performance (slow queries)
    • Table/index sizes and bloat
    • WAL generation and replication lag
    • Cache hit ratio
    • Deadlock detection

    Built-in monitoring:

    • pg_stat_database: Database statistics
    • pg_stat_user_tables: Table statistics
    • pg_stat_user_indexes: Index statistics
    • pg_stat_activity: Current queries

    Tools:

    • pgAdmin (GUI)
    • pg_stat_statements (extension)
    • Prometheus + Grafana
    • Datadog, New Relic
    -- View current queries
    SELECT pid, usename, application_name, state, query, 
           state_change::text as duration
    FROM pg_stat_activity
    WHERE state != 'idle';
    
    -- Database size
    SELECT pg_size_pretty(pg_database_size('mydatabase')) as size;
    
    -- Table sizes
    SELECT 
        schemaname,
        tablename,
        pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size
    FROM pg_tables
    WHERE schemaname = 'public'
    ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
    
    -- Index sizes
    SELECT 
        indexname,
        pg_size_pretty(pg_relation_size(indexrelid)) as size
    FROM pg_stat_user_indexes
    ORDER BY pg_relation_size(indexrelid) DESC;
    
    -- Cache hit ratio (should be >95%)
    SELECT 
        datname,
        round(100.0 * sum(blks_hit) / nullif(sum(blks_hit + blks_read), 0), 2) as cache_hit_ratio
    FROM pg_statio_user_tables
    GROUP BY datname;
    
    -- Locks and blocking queries
    SELECT 
        blocked_locks.pid as blocked_pid,
        blocked_activity.usename as blocked_user,
        blocked_activity.query as blocked_query,
        blocking_locks.pid as blocking_pid,
        blocking_activity.usename as blocking_user,
        blocking_activity.query as blocking_query
    FROM pg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activity 
        ON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locks 
        ON blocking_locks.locktype = blocked_locks.locktype
        AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
        AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
        AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
        AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
        AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
        AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
        AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
        AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
        AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
        AND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activity 
        ON blocking_activity.pid = blocking_locks.pid
    WHERE NOT blocked_locks.GRANTED;
    
    -- Slow queries (requires pg_stat_statements)
    SELECT query, calls, total_time, mean_time, rows
    FROM pg_stat_statements
    WHERE mean_time > 100  -- queries taking >100ms
    ORDER BY mean_time DESC
    LIMIT 20;
    
    -- Connection count by database
    SELECT datname, numbackends as connections
    FROM pg_stat_database
    WHERE datname != 'template0' AND datname != 'template1';
    
    -- Terminate problematic query
    SELECT pg_terminate_backend(12345);
    
    -- Check for table bloat
    SELECT 
        schemaname,
        tablename,
        n_live_tup as live_rows,
        n_dead_tup as dead_rows,
        round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) as dead_percent
    FROM pg_stat_user_tables
    WHERE n_dead_tup > 1000
    ORDER BY dead_percent DESC;
  15. Step 15

    Resources & Next Steps

    Documentation:

    Community:

    Tools:

    Learning:

    Next guides:

    • PostgreSQL performance tuning for production
    • PostGIS for geospatial applications
    • Continuous archiving and point-in-time recovery
    • High availability with Patroni and etcd
    Official site: https://www.postgresql.org
    GitHub: https://github.com/postgres/postgres
    Documentation: https://www.postgresql.org/docs/
    Tutorial: https://www.postgresqltutorial.com/
    
    Downloads: https://www.postgresql.org/download/
    Extensions catalog: https://pgextensions.org/
    Status (versions): https://www.postgresql.org/support/versioning/

Feature requests

Sign in to suggest features or vote on existing ones.

No feature requests yet.

Discussion

0 people marked this as worked·Sign in to mark your own.

Sign in to join the discussion.

No comments yet.