Replication Strategies

On this page

Replication places the same logical data in multiple locations. Replicas can handle failover, provide local reads, and share load, but simply keeping multiple copies of data does not automatically yield strong consistency: you must define who orders writes, how much success must be waited for, what reads are allowed to see, and who has the right to continue writing after a failure.

Separate four independent dimensions first

DimensionQuestion to Answer
Topology & Entry PointIs there one leader, multiple writers, or a coordinator contacting multiple replicas?
Acknowledgment ConditionsWait for local persistence, remote receipt, remote disk flush, or application completion?
Read SemanticsAllow stale values, require read-your-writes, or require linearizability?
Failure & FailoverWhich faults are tolerable, how to preserve acknowledged writes, and how to isolate the old leader?

"Synchronous replication" primarily describes the acknowledgment wait condition, while "single-leader" primarily describes the write organization method. You cannot infer the entire system's guarantees based on just one of these terms.

Single Leader: Centralized Ordering within a Replication Group

Clients hand writes to the leader, which establishes a log order and replicates changes to followers. Replicas then apply the log according to the protocol to form a queryable state. The leader here is usually the leader of a shard or replication group, not necessarily the only writing node in the entire cluster; different shards can have leaders on different machines.

Received, Persisted, Applied: Three Different Progress Stages of Replicas Log Received May still be in memory Persisted Recoverable after restart Applied to State Snapshots also affect visibility Which stage to wait for, and which replicas to wait for, determines the meaning of write success. Typical replication stages; the protocol defines commit and visibility guarantees.

Synchronous vs. Asynchronous: At Which Boundary Does the Success Response Occur?

Asynchronous replication allows the leader to return success without waiting for remote replicas. If the leader is subsequently permanently lost, and the successor has not yet received these updates, writes that clients have already received as successful may be lost. If it is only a brief outage and the local persistent log remains, the data may be recovered later; the fault model must be distinguished.

Synchronous replication requires satisfying specified remote acknowledgment conditions before success, but "synchronous" does not uniformly mean "at least one replica flushed to disk." Protocols may wait for a specified number, specified members, or quorum, and may also wait for different progress stages. Write latency includes log processing, network round-trips, queuing, and storage overhead; it cannot be summarized merely by the maximum of two fsync times.

For example, PostgreSQL distinguishes between waiting for remote receipt, persistence, and application; only by selecting remote_apply is the wait advanced to the standby's application stage. Specific behavior should be combined with configuration, rather than assuming any standby is readable just because "synchronous" is seen. PostgreSQL Replication Documentation

For the Same Committed Record, Three Replicas May Reach Different Stages

Using simplified integers to represent log positions, the committed record to be acknowledged is at 108. The current standby progress is as follows: write means written to the OS file layer, flush means persisted, apply means replayed:

ReplicawriteflushapplyStatus for Position 108
A110108104Persisted, not yet applied
B110106106Written to file layer, not yet persisted to 108
C107107107Not yet written to 108
progress = {
    "A": {"write": 110, "flush": 108, "apply": 104},
    "B": {"write": 110, "flush": 106, "apply": 106},
    "C": {"write": 107, "flush": 107, "apply": 107},
}

def reached(stage, position):
    return {node for node, stages in progress.items() if stages[stage] >= position}

assert reached("write", 108) == {"A", "B"}
assert reached("flush", 108) == {"A"}
assert reached("apply", 108) == set()
progress["A"]["apply"] = 108
assert reached("apply", 108) == {"A"}
assert "B" not in reached("apply", 108)

In PostgreSQL, when valid synchronous standbys are configured, remote_write waits for the remote write to the file system, not just the network packet being received; on waits for the corresponding standby to persist, and remote_apply advances to the application stage. remote_write offers different protection against standby database process crashes versus standby OS crashes; the latter may still lose data that has not yet been persisted. synchronous_commit Definition

The model only lists which replicas have reached each stage, without implementing FIRST/ANY standby selection or complete commit conditions. Whether to return depends on which standbys are eligible to count, how many acknowledgments are needed, and whether local persistence is complete. Even if A's application acknowledgment is sufficient to meet the current configuration, you cannot route immediate reads to B, which is still lagging.

Failover Is Not Renaming Any Follower to Leader

Safe failover must preserve the committed history required by the protocol, select eligible candidates, and prevent the old leader from continuing successful writes after recovery. If there are replicas that have acknowledged data, but a lagging replica is chosen to take over, simply "having used synchronous replication" does not prove no data loss.

Consensus protocols combine election eligibility, log matching, and commit rules. Standard master-slave replication requires checking whether its failover mechanism provides corresponding guarantees. Suspecting the old leader is unreachable and gaining new write authority are two different issues; see Failure Detection for details.

Why Replica Reads Might Break Read-Your-Writes

If a user modifies their nickname and immediately refreshes the page, and the new read is assigned to a replica that has not yet applied that log entry, they may see the old nickname. You can route relevant reads through an entry point with sufficient freshness guarantees, or return a version token in the write response, with the reader waiting until its own progress reaches at least that token.

Tokens must have clear scope and ordering relationships. A log position in one shard cannot be arbitrarily compared with another; cross-leader terms also require valid history identifiers. A fixed "read master five seconds after write" can only serve as an empirical strategy and cannot prove read-your-writes in all latency scenarios.

Freshness Tokens Must Constrain Position, History, and Read Snapshots Simultaneously

Assume a write returns (orders, history-1, 108): it belongs to a specific log history of the orders shard, and reads must include at least position 108. The inventory shard is already at 999, so it cannot serve orders reads; position 200 in another incompatible history cannot be directly compared with 108.

from dataclasses import dataclass

@dataclass(frozen=True)
class Token:
    shard: str
    history: str
    position: int

def can_read(applied, required, snapshot_position=None):
    if (applied.shard, applied.history) != (required.shard, required.history):
        return False
    visible = applied.position
    if snapshot_position is not None:
        visible = min(visible, snapshot_position)
    return visible >= required.position

required = Token("orders", "history-1", 108)
assert not can_read(Token("orders", "history-1", 106), required)
assert can_read(Token("orders", "history-1", 108), required)
assert not can_read(Token("inventory", "history-1", 999), required)
assert not can_read(Token("orders", "other-history", 200), required)
assert not can_read(Token("orders", "history-1", 110), required, 100)
Read your writes: both replica and read view must be fresh Write Token: 108 orders / history-1 Replica Applied: 110 Same Shard, Same History Old Snapshot: 100 Still cannot see 108 Comparable Positions ≠ Old Transaction Snapshots Auto-Refresh The model only judges the freshness of specified dependencies, not a complete strong consistency read protocol.

The last assertion is an easily overlooked boundary: the replica has applied up to 110, but the current transaction is fixed to a historical snapshot at 100; waiting for apply will not automatically refresh this snapshot. If the read must reflect the just-written data, a sufficiently fresh read view must be established under the allowed isolation semantics.

This is an abstract model of "read view corresponds to a log prefix," not the actual MVCC snapshot format of PostgreSQL. Real systems need to implement reliable mapping from tokens to read conditions; if different histories can be proven to have an inheritance relationship by the protocol, conversion and verification can be performed rather than rejecting all of them as in the example. Clients cannot fabricate tokens themselves as authorization credentials.

Waiting for a known write to be applied only provides the freshness required by this dependency, not reading the latest writes of all clients. To commit to linearizable reads, you still need current leadership or a corresponding read protocol; read-your-writes and global real-time ordering are different guarantees.

Received, durable and applied are different frontiers

First fix which standby is being awaited, then compare its stages. This model selects one synchronous standby and assumes local conditions hold; write, flush and apply remain ordered. Switching the wait criterion from flush to apply changes the success condition; it does not perform replay for the standby.

Preparing the visual
Received, durable and applied are different frontiers

apply≤flush≤write. Remote file writes, durability and application acknowledgements provide different guarantees; success does not make every replica immediately readable.

Multi-Leader: Allowing Independent Changes at Multiple Locations

Independent writes across regions can reduce local write latency and continue processing requests during certain network partitions. The cost is that different locations may modify the same object without seeing each other's updates. For example, A and B simultaneously modify a contact's address; upon resyncing, both versions must be preserved, selected, or merged.

This does not mean conflicts inevitably arise just because "two nodes can write": fixing write sites by tenant or restricting operations to mergeable types can reduce conflicts. However, migrating write authority across sites, offline editing, and concurrent creation still require explicit rules. If uniqueness must be coordinated across sites before success, you cannot treat all writes as completely independent local operations.

Leaderless: Coordinating Multiple Replicas, Not Absence of Protocol

Dynamo-style replication allows the request coordinator to contact multiple replicas for a key. The coordinator does not necessarily hold a replica for that key; only replicas that truly satisfy the acknowledgment conditions count towards W; you cannot mechanically "receive W-1 replies plus yourself."

Let the fixed replica set size be N, a write gets W replica acknowledgments, and a read waits for R replicas. When R+W>N, the two sets must intersect. But intersection only indicates that the read may have touched information left by a previously acknowledged write; how to select versions, handle concurrency, incomplete writes, and read repairs still belongs to the protocol.

For example, with N=3, W=2, R=2, write 1 only reaches A before the write request times out; the first read of A and B sees 1, but the second read of B and C sees 0. If the protocol only "selects the largest version this time and returns," without completing necessary propagation or read-repair, the read value may regress. Quorum numbers alone do not prove linearizability.

Some leaderless register algorithms can indeed achieve linearizability using quorums plus versioning and read-repair. Therefore, a leaderless topology does not mean only weak consistency is available; the complete algorithm should be evaluated.

Staging and Repair During Failures

Hinted handoff is used to stage updates that should have been sent to a specific replica, to be delivered later upon its recovery. It relies on hint persistence, retention periods, and subsequent repair; it is not a guarantee that any replica can be recovered from permanent loss.

Sloppy quorum allows temporary substitute nodes to participate in receiving, potentially improving writability. However, once the candidate sets for read and write differ, the same N-based intersection proof cannot be directly applied. See Distributed Read-Write Paths for detailed paths.

Write Down Business Requirements First When Choosing

RequirementNeeds Further Checking
Writes cannot be lost due to faults within the allowed range after successPersistence, acknowledgment set, failover eligibility, and fault domains
Read your own modifications immediately after writingRouting, application progress, and session tokens
Ability to modify even when multiple sites are disconnectedConcurrent conflict semantics and constraints that cannot be verified locally
Scaling total write throughputShard and leader distribution, not just the number of replicas

Continue reading: Conflict Resolution and Distributed Transactions.

References

Keywords: replication, leader, follower, quorum, synchronous replication, read-your-writes, failover