Guide

Synchronous Replication and Slots

Part 2 of 5By Amith Kumar7 min read
Part 2 of 5PostgreSQL Replication and HA: A Hands-On Guide
  1. 1Streaming Replication: A Live Standby
  2. 2Synchronous Replication and Slots
  3. 3Failover and Promotion
  4. 4Connection Routing with PgBouncer
  5. 5Backups and Point-in-Time Recovery
Verified 22 Jul 2026 · Ubuntu 24.04 LTS · PostgreSQL 16.14

The safety dial most people never touch

PostgreSQL synchronous replication lets you trade a little commit latency for a hard promise: a transaction is not confirmed to the client until the standby also has it. This chapter creates a replication slot so the primary holds WAL for the standby, then flips a single commit from asynchronous to synchronous and watches the difference on a live server.

Key takeaways
  • A physical replication slot makes the primary retain WAL a standby still needs, so it can resume after a brief disconnect.
  • The slot's restart_lsn is the oldest position the standby still requires, and it advances as the standby confirms progress.
  • synchronous_standby_names names the standby the primary will wait for before confirming a commit.
  • A synchronous commit blocks until the standby answers, so if the standby is gone the write waits.

Asynchronous replication is the default because it is fast: the primary confirms your write the moment it is durable locally, and the standby catches up a beat later. The risk is a narrow window. If the primary dies in that beat, a confirmed transaction may not have reached the standby yet. Synchronous replication closes that window, at the cost of waiting for the standby on every commit.

Prerequisites

Before you start
  • The primary and standby pair from the first chapter of this series, streaming on an Ubuntu 24.04 LTS host.
  • The standby cloned with a slot named standby_slot, as the pg_basebackup -S step created.
  • Sudo and psql access to both the primary on 5432 and the standby on 5433.
  • A willingness to stop the standby briefly to see a synchronous commit wait.

A slot so the primary holds WAL for the standby

Without a slot, the primary recycles old WAL on its own schedule. If a standby falls behind or drops off, the segment it needs can be gone, and it has to be rebuilt. A slot fixes that by recording how far the standby has consumed, so the primary keeps everything newer. Read the slot the clone created.

sudo -u postgres psql -p 5432 -x -c "SELECT slot_name, slot_type, active, \
  restart_lsn, wal_status FROM pg_replication_slots;"
A physical replication slot named standby_slot shown active with a reserved WAL status and a restart_lsn marking the oldest log position the standby still needs

The slot is physical, active is t, and wal_status is reserved, meaning the primary is holding WAL for it. The restart_lsn is the oldest log position the standby still needs. Generate more writes on the primary and run a checkpoint, and restart_lsn moves forward, here from 0/4002C90 to 0/4210558. That advance is the standby telling the primary it is safe to release the older WAL.

PostgreSQL synchronous replication, on demand

By default the standby is asynchronous. To make a commit wait for it, name the standby in synchronous_standby_names and reload. The name is the standby's application_name, which is standby here.

sudo -u postgres psql -p 5432 \
  -c "ALTER SYSTEM SET synchronous_standby_names = 'standby';"
sudo -u postgres psql -p 5432 -c "SELECT pg_reload_conf();"
After naming the standby in synchronous_standby_names and reloading, pg_stat_replication reports the standby's sync_state changed from async to sync

Read pg_stat_replication again and sync_state has changed from async to sync. From this moment, any commit governed by synchronous_commit = on waits for the standby to confirm it has the WAL before the client is told the write succeeded. The default level, on, waits for the standby to flush WAL to disk. Stronger levels wait for it to be applied; weaker ones do not wait at all.

Watch a synchronous commit wait

The guarantee is only real if the commit actually blocks when the standby cannot answer. Prove it. Stop the standby, then run an insert on the primary and watch it hang, because the sync standby it is waiting for is gone.

sudo pg_ctlcluster 16 standby stop
sudo -u postgres psql -p 5432 -d shopdb -c "INSERT INTO customers (email, \
  full_name, city) VALUES ('[email protected]','Sync Demo','Pune');"
A synchronous commit blocking with the standby stopped, then the warning that it is canceling the wait for synchronous replication because the row is committed only locally

The insert stops and waits. Cancel the wait and PostgreSQL prints exactly what happened: canceling wait for synchronous replication, with the detail that the transaction has committed locally but might not be on the standby. That warning is the whole lesson. A single synchronous standby is a single point of failure for writes, so if it dies, commits stall. Start the standby again and the held transaction streams across as the standby reconnects.

Warning Naming one standby in synchronous_standby_names means writes stop if that standby is down. For real synchronous durability without that fragility, run at least two standbys and require a quorum, for example ANY 1 (s1, s2), so any one can be lost without blocking commits.
Checkpoint

With the standby stopped, the insert on 5432 should hang until you cancel it; start the standby again and the same commit returns at once. If it never blocks, sync_state is still async, so recheck that the name in synchronous_standby_names matches the standby's application_name exactly.

Going further: when to reach for synchronous commit

Most workloads should stay asynchronous. It is faster, and a well-monitored standby that trails by milliseconds is fine for reads and for failover. Turn on synchronous commit when losing even one confirmed transaction is unacceptable, such as a payment ledger, and only when you have enough standbys that waiting for one does not put your writes at the mercy of a single machine. You can even set it per transaction, keeping most writes fast and making the few that matter wait.

Troubleshooting slots and sync

sync_state stays async after the reload. The name in synchronous_standby_names does not match the standby's application_name. Check the standby's primary_conninfo and set application_name there to the exact name you listed.

restart_lsn never advances and disk fills. A slot whose standby is gone pins WAL forever, and the primary's disk grows without bound. Drop an obsolete slot with pg_drop_replication_slot, and set max_slot_wal_keep_size so a stuck slot cannot fill the disk.

Commits hang with no obvious reason. A synchronous standby is down or unreachable. Confirm the standby is streaming in pg_stat_replication, or temporarily clear synchronous_standby_names to unblock writes while you fix it.

Frequently asked questions

Does synchronous replication mean zero data loss?

It means a confirmed commit is on at least one standby before the client hears success, so a primary failure cannot lose an acknowledged transaction. It does not protect against a bad statement or the loss of both nodes at once, so backups still matter.

What is the difference between the synchronous_commit levels?

The setting controls how far a commit waits: off does not wait even locally, local waits for the primary's own disk, on waits for the standby to flush WAL, and remote_apply waits for the standby to apply it so a read there sees the row.

Why does my disk fill up after adding a slot?

A slot makes the primary keep every WAL segment the standby has not consumed. If the standby stops, that WAL piles up with nowhere to go. Set max_slot_wal_keep_size so an abandoned slot is capped rather than filling the disk.

Can I make only some transactions synchronous?

Yes. synchronous_commit can be set per session or per transaction, so you keep most writes fast and asynchronous and make only the few that must not be lost wait for the standby. That is often the right balance.

What you have, and what comes next

You have a replication slot holding WAL for the standby with a restart_lsn you watched advance, and you have seen a synchronous commit block when its standby was gone. You understand the dial now, and why most servers leave it on the fast setting.

A standby is only useful in a crisis if you can turn it into a primary. The next chapter is failover: promoting the standby, watching the timeline change, and dealing with the old primary so you never end up with two of them: Failover and Promotion.


Run HA Postgres on slices

Run highly-available Postgres on slices

A standby and failover need more than one box. Spin up a couple of slices, set up streaming replication and PITR from this guide, and survive a node dying.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Was this guide helpful?
Share

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot