Streaming replication sends WAL records directly from primary to standby over a TCP connection, providing near-real-time replication.

Primary Server Configuration

sql
-- postgresql.conf on primary
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on

-- Recommended for reliability
synchronous_commit = on
wal_keep_size = 1GB

Replication Slots

Replication slots prevent the primary from removing WAL files that standby still needs:

sql
-- Create a physical replication slot
SELECT pg_create_physical_replication_slot('standby_slot');

-- View existing slots
SELECT slot_name, slot_type, active, restart_lsn
FROM pg_replication_slots;

-- Drop unused slot (WARNING: can cause WAL bloat if forgotten)
SELECT pg_drop_replication_slot('old_slot');

Standby Configuration

Create standby.signal in data directory and configure:

sql
-- postgresql.auto.conf or postgresql.conf on standby
primary_conninfo = 'host=primary port=5432 user=replicator password=secret'
primary_slot_name = 'standby_slot'
hot_standby = on

Monitoring Replication

sql
-- On primary: view connected standbys
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;

-- On standby: view replication status
SELECT pg_is_in_recovery();  -- Should return true
SELECT pg_last_wal_receive_lsn();
SELECT pg_last_wal_replay_lsn();

-- Calculate lag in seconds
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()));

Code Examples

typescript
-- On PRIMARY: Create replication user and slot
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secure_password';
SELECT pg_create_physical_replication_slot('standby1_slot');

-- On STANDBY: After pg_basebackup -R, verify connection
-- Check postgresql.auto.conf contains:
-- primary_conninfo = 'host=primary port=5432 user=replicator password=...'
-- primary_slot_name = 'standby1_slot'
✓ Completed