PostgreSQL Synchronous Replication Is a Commit Contract

Sep 2

Synchronous replication is often described as a checkbox for “zero data loss.” That description hides the decision that matters: what evidence must PostgreSQL collect before the application is allowed to call a transaction committed?

The invariant is:

A commit acknowledgement is a promise about which failures the transaction can survive, not proof that every replica can already serve it.

PostgreSQL exposes several acknowledgement boundaries. Choosing one changes latency, durability, read-after-write behavior, and availability during a standby failure.

Follow one WAL record

A transaction becomes durable through a sequence of events:

application
  -> primary inserts commit record into WAL
  -> primary flushes WAL
  -> standby receives WAL
  -> standby writes WAL to its operating system
  -> standby flushes WAL to durable storage
  -> standby replays WAL
  -> standby query can observe the transaction

These events are not interchangeable. synchronous_commit = on waits for local flush and for the selected synchronous standby to report durable WAL flush. remote_write waits for the standby operating system to accept the WAL but not necessarily persist it through an OS crash. remote_apply waits until the standby has replayed the transaction, which can support causal reads from that standby at the cost of replay delay in the commit path.

local and off weaken the remote or local acknowledgement boundary further. They may be sensible for rebuildable telemetry or derived events. They are rarely sensible for a payment ledger merely because an incident made synchronous commits slow.

Quorum is not geography

PostgreSQL can use priority-based or quorum-based synchronous standbys. A quorum configuration can look like:

synchronous_standby_names = 'ANY 1 (az_a, az_b, az_c)'

This allows a commit to proceed after any one listed synchronous candidate acknowledges it. It improves tolerance of one slow or unavailable standby, but the names alone say nothing about failure independence. Three virtual machines on the same storage system are not three durability domains.

The architecture review must map each acknowledgement to power, storage, network, availability-zone, and operator failure domains. If the primary and acknowledged standby share the failure you claim to tolerate, the topology does not satisfy the product promise.

Availability is part of the contract

If the configuration requires acknowledgements that cannot arrive, commits wait. PostgreSQL has not failed: it is preserving the configured contract. The operational question is whether the business prefers write unavailability or a weaker durability mode during that failure.

Do not let an improvised incident command decide this for the first time. Define a degradation policy:

ConditionDefault actionBusiness consequence
one quorum candidate lostcontinue with remaining candidatesreduced redundancy
all synchronous candidates loststop durable writesavailability loss
approved emergency downgradechange named policy explicitlyincreased data-loss exposure
standby replay slow under remote_applyinvestigate replay bottleneckcommit latency rises

An automatic downgrade from synchronous to asynchronous replication can preserve availability while silently violating the recovery objective. If allowed, make it a named mode with an owner, audit event, alert, expiry, and reconciliation procedure.

Per-transaction policy is powerful and dangerous

synchronous_commit can be set per transaction. This lets one cluster carry different durability classes:

BEGIN;
SET LOCAL synchronous_commit = 'remote_apply';
UPDATE account_balance
SET amount = amount - 500
WHERE account_id = 42;
COMMIT;

A ledger mutation may wait for remote durability, while an idempotent analytics event may accept asynchronous loss. The distinction belongs in a reviewed data classification—not scattered ORM settings.

Connection pools also make session settings dangerous. Prefer SET LOCAL inside a transaction so a relaxed policy cannot leak to the next borrower of a pooled connection.

Latency must be budgeted end to end

Synchronous commit latency includes primary WAL work, network transit, standby write or flush, reply transit, and sometimes replay. Tail latency matters more than the average because every qualifying commit depends on a remote critical path.

Measure:

  • commit latency by durability class;
  • WAL write, flush, and replay locations per standby;
  • write_lag, flush_lag, and replay_lag with workload context;
  • number and location of eligible synchronous candidates;
  • transactions waiting on synchronous replication;
  • WAL generation rate versus network and standby disk capacity;
  • frequency and duration of degraded durability modes.

Replication lag is not a single number. A standby may have received WAL but not flushed it, or flushed it but not replayed it. Alert on the boundary your contract uses.

Failover still has ambiguous outcomes

A client can lose its connection after PostgreSQL commits but before the acknowledgement reaches the application. Synchronous replication reduces data-loss exposure; it does not tell the caller whether an interrupted request committed.

Writes still need stable operation identifiers and reconciliation:

CREATE TABLE transfer (
  operation_id uuid PRIMARY KEY,
  from_account bigint NOT NULL,
  to_account bigint NOT NULL,
  amount numeric(18,2) NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now()
);

On retry, look up operation_id. Do not create a second transfer because the first response was lost. Durability and idempotency solve different failure boundaries.

The CTO decision

Write down the promise before tuning the database: which transaction classes may lose acknowledged data, which failure domains must be survived, whether read-after-write from a standby is required, and when writes should stop instead of weakening the promise.

Then choose synchronous_commit, quorum membership, topology, and degradation automation to implement that contract. “Synchronous” is not the outcome. An explicit, tested acknowledgement boundary is.

References

>