Skip to main content

Command Palette

Search for a command to run...

** PostgreSQL Replication: Master-Slave Architecture Guide

** Learn PostgreSQL replication strategies with practical TypeScript examples. Explore streaming replication, logical replication, and failover patterns.

Published
6 min read
** PostgreSQL Replication: Master-Slave Architecture Guide
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Building Resilient Data Systems: A Deep Dive into PostgreSQL Replication Strategies

Database downtime isn't just inconvenient—it's catastrophic. When your primary database server fails at 3 AM, every second counts. Your application grinds to a halt, users see error messages, and revenue evaporates. This nightmare scenario is exactly why database replication exists, and PostgreSQL offers some of the most robust replication mechanisms available in the open-source world.

Replication solves multiple critical problems simultaneously: it provides high availability through automatic failover, enables horizontal scaling for read-heavy workloads, and creates disaster recovery options. But implementing replication incorrectly can introduce subtle bugs, data inconsistencies, and false confidence in your backup strategy.

Understanding the Replication Problem Space

Before diving into implementation details, let's clarify what we're actually solving. A single database server represents a single point of failure. Hardware fails, networks partition, and software crashes. Beyond availability concerns, a single server also creates performance bottlenecks. When your application serves millions of read requests daily, one database instance simply cannot keep up.

PostgreSQL replication addresses these challenges by maintaining synchronized copies of your data across multiple servers. The primary server (often called the master) handles write operations, while replica servers (slaves or standby servers) maintain copies of the data. These replicas can serve read queries, reducing load on the primary server.

The complexity emerges in maintaining consistency. How do you ensure replicas stay synchronized? What happens when network issues prevent updates from reaching a replica? How quickly can a replica take over if the primary fails? These questions define the replication landscape.

PostgreSQL Replication Mechanisms

PostgreSQL provides two primary replication approaches: physical replication and logical replication. Physical replication, also called streaming replication, copies the exact binary changes from the Write-Ahead Log (WAL) to replica servers. This creates byte-for-byte identical copies of the entire database cluster.

Logical replication, introduced in PostgreSQL 10, replicates data changes at a higher level. Instead of copying raw disk blocks, it replicates actual data modifications—inserts, updates, and deletes. This approach offers more flexibility, allowing selective replication of specific tables or databases.

Implementing Streaming Replication

Let's build a practical streaming replication setup. First, configure the primary server by editing postgresql.conf:

// Configuration values for primary server
interface PrimaryConfig {
  wal_level: 'replica' | 'logical';
  max_wal_senders: number;
  wal_keep_size: string;
  hot_standby: boolean;
}

const primaryConfig: PrimaryConfig = {
  wal_level: 'replica',
  max_wal_senders: 10,
  wal_keep_size: '1GB',
  hot_standby: true
};

Create a replication user with appropriate permissions:

import { Pool } from 'pg';

async function createReplicationUser() {
  const pool = new Pool({
    host: 'primary-db.example.com',
    port: 5432,
    user: 'postgres',
    password: process.env.DB_PASSWORD,
    database: 'postgres'
  });

  try {
    await pool.query(`
      CREATE ROLE replicator WITH 
      REPLICATION 
      LOGIN 
      PASSWORD '${process.env.REPLICATION_PASSWORD}'
    `);

    console.log('Replication user created successfully');
  } catch (error) {
    console.error('Failed to create replication user:', error);
  } finally {
    await pool.end();
  }
}

Configure pg_hba.conf to allow replication connections:

host    replication    replicator    replica-ip/32    md5

Setting Up the Replica Server

On the replica server, use pg_basebackup to create an initial copy:

import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

async function initializeReplica() {
  const command = `
    pg_basebackup 
    -h primary-db.example.com 
    -D /var/lib/postgresql/data 
    -U replicator 
    -P 
    -v 
    -R 
    -X stream 
    -C -S replica_slot
  `;

  try {
    const { stdout, stderr } = await execAsync(command);
    console.log('Base backup completed:', stdout);
    return true;
  } catch (error) {
    console.error('Base backup failed:', error);
    return false;
  }
}

The -R flag automatically creates standby.signal and configures connection settings. The replica continuously streams WAL changes from the primary.

Implementing Logical Replication

Logical replication offers granular control. Here's how to replicate specific tables:

interface LogicalReplicationConfig {
  publicationName: string;
  tables: string[];
  subscriptionName: string;
}

async function setupLogicalReplication(
  config: LogicalReplicationConfig,
  primaryPool: Pool,
  replicaPool: Pool
) {
  // Create publication on primary
  await primaryPool.query(`
    CREATE PUBLICATION ${config.publicationName}
    FOR TABLE ${config.tables.join(', ')}
  `);

  // Create subscription on replica
  await replicaPool.query(`
    CREATE SUBSCRIPTION ${config.subscriptionName}
    CONNECTION 'host=primary-db.example.com port=5432 dbname=mydb user=replicator password=${process.env.REPLICATION_PASSWORD}'
    PUBLICATION ${config.publicationName}
  `);
}

// Usage example
const config: LogicalReplicationConfig = {
  publicationName: 'users_publication',
  tables: ['users', 'user_profiles', 'user_sessions'],
  subscriptionName: 'users_subscription'
};

Monitoring Replication Health

Continuous monitoring prevents silent failures:

interface ReplicationStatus {
  applicationName: string;
  state: string;
  sentLsn: string;
  writeLsn: string;
  flushLsn: string;
  replayLsn: string;
  lagBytes: number;
  lagSeconds: number;
}

async function checkReplicationStatus(
  pool: Pool
): Promise<ReplicationStatus[]> {
  const result = await pool.query(`
    SELECT 
      application_name,
      state,
      sent_lsn,
      write_lsn,
      flush_lsn,
      replay_lsn,
      pg_wal_lsn_diff(sent_lsn, replay_lsn) as lag_bytes,
      EXTRACT(EPOCH FROM (now() - replay_timestamp)) as lag_seconds
    FROM pg_stat_replication
  `);

  return result.rows;
}

// Alert on excessive lag
async function monitorReplicationLag(pool: Pool) {
  const statuses = await checkReplicationStatus(pool);

  for (const status of statuses) {
    if (status.lagSeconds > 30) {
      console.error(`High replication lag detected: ${status.lagSeconds}s`);
      // Trigger alert system
    }
  }
}

Common Pitfalls and How to Avoid Them

Replication Slot Exhaustion: Replication slots prevent WAL files from being deleted before replicas consume them. However, if a replica disconnects indefinitely, WAL files accumulate until disk space runs out. Always monitor slot usage and set max_slot_wal_keep_size.

Cascading Replication Complexity: While cascading replication (replicas replicating from other replicas) reduces primary server load, it increases lag and complexity. Use sparingly and monitor carefully.

Synchronous vs. Asynchronous Trade-offs: Synchronous replication guarantees zero data loss but impacts write performance. Asynchronous replication offers better performance but risks data loss during failover. Choose based on your consistency requirements.

Connection Pool Mismanagement: Applications must handle replica promotion gracefully. Connection pools should detect primary failures and redirect traffic appropriately.

Ignoring Replication Lag: Applications reading from replicas may see stale data. Implement lag monitoring and consider read-your-writes consistency patterns when necessary.

Best Practices Checklist

  • ✓ Configure monitoring for replication lag and slot usage
  • ✓ Implement automated failover with tools like Patroni or repmgr
  • ✓ Test failover procedures regularly in staging environments
  • ✓ Use connection poolers (PgBouncer) to manage replica connections
  • ✓ Set appropriate wal_keep_size and max_slot_wal_keep_size
  • ✓ Document your replication topology clearly
  • ✓ Implement health checks for all database nodes
  • ✓ Configure proper backup strategies independent of replication
  • ✓ Use SSL/TLS for replication connections in production
  • ✓ Monitor disk space on all servers, especially the primary

Frequently Asked Questions

Can I write to replica servers? No, streaming replication replicas are read-only. Attempting writes will fail. Logical replication subscribers can technically accept writes to non-replicated tables, but this creates complexity and potential conflicts.

How do I promote a replica to primary? Use pg_ctl promote or create a promote.signal file in the data directory. The replica stops replication and begins accepting writes. Update your application connection strings to point to the new primary.

What happens if the primary crashes before replicas receive all changes? With asynchronous replication, those changes are lost. Synchronous replication prevents this by waiting for replica acknowledgment before committing transactions, but impacts performance.

Can I replicate between different PostgreSQL versions? Streaming replication requires identical major versions. Logical replication supports different versions, making it useful for zero-downtime upgrades.

How much replication lag is acceptable? This depends on your application. Financial systems might require sub-second lag, while analytics workloads might tolerate minutes. Monitor lag and set alerts based on your specific requirements.

Should I use streaming or logical replication? Use streaming replication for full database high availability and disaster recovery. Choose logical replication when you need selective table replication, cross-version replication, or multi-master scenarios.

How do I handle schema changes with replication? Streaming replication automatically replicates all schema changes. Logical replication requires careful coordination—apply schema changes to both publisher and subscriber, ensuring compatibility during the transition.

PostgreSQL replication transforms fragile single-server deployments into resilient, scalable systems. By understanding the mechanisms, avoiding common pitfalls, and following best practices, you can build database infrastructure that survives failures and scales with your application's growth.