Research notes / Replication contracts
Replication acknowledgements: received, durable, applied
Published 12 October 2026 / Primary-source review
A replica can receive a change before it becomes durable, and persist it before queries can observe it. A successful acknowledgement protects only the boundary and replica set required by the actual configuration.
Start with the completion contract
SNIA Swordfish distinguishes the maintained-copy relationship, called a mirror, from its update mode. The existence of another copy does not specify how writes complete or promise instantaneous failover. [8] Ask which event releases the caller, which replicas participate and which failures the storage stack can survive.
PostgreSQL 18 is a concrete example, not a universal replication vocabulary. The following table assumes normal durable local WAL handling and configured synchronous standbys. It concerns the transaction commit record, not a requirement to flush every modified data page.
| Setting | Local WAL durable? | Required remote progress |
|---|---|---|
off | Not required before success | None |
local | Yes | None |
remote_write | Yes | Commit record written to standby filesystem |
on | Yes | Commit record flushed to durable standby storage |
remote_apply | Yes | Commit record durable and transaction replayed |
A standby filesystem write can survive a database-process crash without surviving an operating-system crash. Durable WAL does not yet imply that a standby query sees the transaction; remote_apply adds replay. These guarantees depend on the underlying durability configuration and storage behaving as promised. [1]
Replay does not refresh an older read snapshot
Operational inference: after a remote_apply acknowledgement, route the read to a standby whose replay was actually required and use a sufficiently new read snapshot. PostgreSQL Repeatable Read retains the snapshot established by the first non-transaction-control statement. An already-running transaction can therefore keep seeing an earlier version even after the standby has applied the update. Replay progress and snapshot isolation answer different questions. [1] [10]
The replica set matters as much as the setting
An empty synchronous_standby_names means no remote wait, even with synchronous_commit=on. FIRST n (...) selects by priority; ANY n (...) accepts the required count of acknowledgements among the named candidates. Neither means every replica, and the names are standby application names, not necessarily hostnames. [2]
Streaming replication defaults to asynchronous operation. A promoted standby can lack acknowledged transactions; required synchronous standbys can instead delay completion when unavailable. PostgreSQL 18 cascading replication remains asynchronous. Protection of a directly connected standby therefore does not automatically extend to its downstream replicas. [3]
Operational inference: a claim of zero recovery-point loss needs an explicit failure scenario, surviving acknowledged replica and promotion rule. Replica count alone is insufficient. Preventing an old primary from continuing to write is a separate fencing problem.
The same label can hide a different boundary
DRBD replicates block-device writes. Its protocol A completes after local disk completion and placement in the local TCP send buffer; B also waits for peer receipt; C waits for confirmed local and remote disk writes. LINBIT calls B memory-synchronous or semi-synchronous. [6, section 2.3]
Do not equate DRBD B with PostgreSQL remote_write: peer receipt and a filesystem write are distinct events. Nor does DRBD C establish SQL query visibility. This is a comparison of documented boundaries, not a performance ranking.
Asynchronous does not mean unable to block: DRBD protocol A can stall the writer when its send buffer fills. Removing a remote acknowledgement from completion does not remove finite buffering or bandwidth limits. [6, section 2.18]
Chain replication puts the reply boundary at the tail
Van Renesse and Schneider's OSDI 2004 protocol sends updates to a head, forwards changes through an ordered chain, and serves queries and replies at its tail. This is not majority voting for each data update; a separate master reconfigures membership. The model assumes fail-stop servers and reliable FIFO links. Its prototype uses Paxos for the master. [11, sections 3 and 3.1]
Do not read that proof as a PostgreSQL cascade guarantee: the protocols differ. The paper's simulated throughput is not a modern hardware benchmark, and it does not promise service across arbitrary network partitions. [11, sections 5 and 6] See the chain replication record.
Lag needs a stage, unit and observation context
PostgreSQL's pg_stat_replication reports directly connected standbys. Its sent, written, flushed and replayed LSN positions distinguish progress stages. The lag intervals include recent processing and notification delay; they are not a catch-up-time estimate. [4]
A caught-up idle standby can retain its last measured delay briefly, then report NULL. Missing logical-replication tracking can also produce NULL. Converting every missing value to zero discards important uncertainty. [4]
An LSN difference measures bytes in PostgreSQL's WAL address space, not transactions or seconds. The hexadecimal representation does not change that unit. Compare compatible log histories and label the two endpoints. [5] [9]
| Observation | Meaning | Avoid inferring |
|---|---|---|
| Current WAL write position minus replay position | Byte distance between those stages | Number of lost transactions or seconds to recover |
replay_lag | Recent replay and notification delay | A forecast of catch-up time |
| Time since last replayed transaction | Age of the latest replayed commit/abort timestamp | Proof of backlog during an idle period |
Function names also need care: pg_current_wal_lsn() identifies the write position, whereas the insert and flush functions identify other stages. pg_last_wal_receive_lsn() already describes data received and synced to disk. The last replayed transaction timestamp can remain unchanged while the system is idle, so its age alone is not evidence of queued work. [7]
Record evidence before declaring a failover safe
Hesela's diagnostic checklist is an editorial synthesis, not an automated failover procedure:
- Record the commit policy and required replica membership for the affected transactions.
- Capture the candidate's write, flush and replay positions with timestamps and log-history context.
- Separate acknowledged transactions from requests whose outcome the client never learned.
- Check promotion eligibility and fencing independently of replica freshness.
A client can miss the success response for a transaction that the primary later recovers as committed. A missing acknowledgement is not proof of rollback. Treat that as an uncertain completion problem, not permission to retry blindly. [3]
Method and limits
This is a documentation and paper review, not a replication experiment. We compared PostgreSQL 18, the DRBD 9 guide, Swordfish 1.2.8 and the original OSDI 2004 chain-replication paper. No failover was executed, and no latency, loss probability or recovery time was measured. These reference versions are not a claim about the newest available release. Verify actual settings, storage flush behavior and failure domains before applying the conclusions.
Machine-readable definitions: synchronous replication, asynchronous replication, replication lag, log sequence number and storage mirroring.
Primary sources
- PostgreSQL 18: WAL settings, synchronous_commit
- PostgreSQL 18: synchronous_standby_names
- PostgreSQL 18: warm standby and synchronous replication
- PostgreSQL 18: pg_stat_replication and lag semantics
- PostgreSQL 18: pg_lsn type
- LINBIT DRBD 9 guide: section 2.3, replication modes
- PostgreSQL 18: WAL and recovery information functions
- SNIA Swordfish 1.2.8: ReplicaType Mirror
- PostgreSQL 18: timeline history and recovery branches
- PostgreSQL 18: Repeatable Read snapshot semantics
- Robbert van Renesse and Fred B. Schneider: Chain Replication for Supporting High Throughput and Availability, OSDI 2004, pp. 91-104
Accessed 12 October 2026. Conclusions are scoped to the cited documentation.