Streaming Replication Explained
Streaming replication is the mechanism that keeps a second PostgreSQL instance continuously in sync with a primary by shipping the same write-ahead log the primary already writes for crash recovery.
It belongs to the family of physical replication, which copies the database at the byte level rather than reasoning about individual rows or statements.
Understanding it matters because almost every high-availability, read-scaling, and backup strategy in PostgreSQL starts from this one primitive, and the how-to pages in this section assume you already have this mental model in place.
Summary
- Core Idea: Streaming replication continuously ships write-ahead log (WAL) records from a primary to one or more standbys, which replay them to stay byte-identical to the primary.
- Why It Matters: It is the foundation for failover, zero-downtime backups, and read scaling, and almost nothing else in PostgreSQL's HA story works without it.
- Key Concepts: WAL, WAL sender, WAL receiver, replication slot, synchronous commit, cascading replica.
- When to Use: Any workload that needs a promotable hot standby, offloaded read traffic, or a low-RPO backup source.
- Limitations / Trade-offs: It replicates the whole cluster byte-for-byte, so you cannot filter tables or bridge major versions the way logical replication can.
- Related Topics: synchronous vs asynchronous commit, replication slots, cascading replicas, high availability and failover.
Foundations
Every PostgreSQL write first lands in the write-ahead log, a durable, append-only record of every change made to the database before that change is applied to the actual data files.
This design exists for crash recovery, since replaying WAL after a crash rebuilds exactly the state the database was in before it went down.
Streaming replication reuses that same log for a second purpose: instead of only replaying WAL locally after a crash, a WAL sender process on the primary streams it over the network to a WAL receiver process on a standby, continuously and in near real time.
The standby's recovery process replays that WAL exactly as it would after its own crash, which is why a streaming standby is often described as being in a permanent, ongoing state of crash recovery.
Because the standby is replaying the primary's actual WAL rather than re-executing SQL statements, it ends up as a byte-for-byte physical copy, including indexes, table bloat, and even the same page layout.
A useful analogy is a courier who reads a primary's diary aloud in real time to a second scribe, who copies every word verbatim rather than summarizing the events described.
That verbatim nature is both the feature and the constraint: a standby cannot select which tables to receive, cannot run a different major version, and cannot accept writes of its own until it stops following the primary entirely.
Stopping and switching roles is called promotion, the moment a standby exits recovery, starts accepting writes, and becomes a primary in its own right.
Mechanics & Interactions
The primary does not simply fire WAL at a standby and forget about it, since PostgreSQL supports several synchronous commit levels that change when a client's transaction is allowed to report success.
At the loosest end, asynchronous replication lets the primary commit and acknowledge the client before any standby has confirmed receipt, which minimizes commit latency but accepts that a crash at just the wrong moment can lose the most recent transactions.
At the strictest end, synchronous commit levels make the primary wait for one or more standbys to acknowledge the WAL before the client's commit returns, trading latency for a durability guarantee that survives a single-node failure.
The synchronous_standby_names setting controls which standbys participate and how many must acknowledge, and PostgreSQL supports quorum-style groups so a commit can wait for any two of three eligible standbys rather than one specific named node.
-- Require acknowledgment from any 2 of 3 named standbys before commit returns
ALTER SYSTEM SET synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)';That single setting is the lever that turns the same underlying WAL-shipping mechanism into either a low-latency async pipeline or a strongly durable synchronous cluster, and choosing between them is really choosing how much commit latency you are willing to pay for how much data-loss protection.
A second mechanic worth internalizing is replication slot retention, since a slot tells the primary to keep WAL around on disk until the corresponding standby confirms it has received and applied it.
Without a slot, a primary is free to recycle old WAL segments once it no longer needs them for its own crash recovery, which means a standby that falls too far behind can find the WAL it needs has already been deleted.
With a slot, the primary instead retains WAL indefinitely for that standby, which protects against exactly that failure mode but introduces a new one: a standby that goes offline and never comes back can quietly fill the primary's disk.
Standbys are not limited to a single hop from the primary either, because a standby can itself act as a source for further standbys in a cascading topology, relaying WAL it has already received rather than requiring every standby to connect directly to the primary.
Cascading exists primarily to protect the primary from connection and CPU overhead as standby count grows, not to reduce replication lag, since each additional hop in a cascade typically adds a small amount of lag rather than removing it.
Advanced Considerations & Applications
At scale, the operational question shifts from "does replication work" to "what happens when it falls behind or breaks," and the answer depends heavily on which synchronous commit level you chose and how your orchestration layer reacts to it.
A synchronous standby that goes offline does not just stop receiving updates, it can block every write on the primary until it reconnects or is removed from synchronous_standby_names, which is why production synchronous setups almost always use a quorum of multiple standbys rather than a single named one.
Delayed replicas, standbys configured to replay WAL after a deliberate time lag, offer a safety net against logical mistakes like an accidental mass delete, since the delay gives operators a window to intervene before the damaging statement replicates through.
Cross-region topologies add network latency into the same trade-off space, since a synchronous standby across a WAN link can add tens or hundreds of milliseconds to every commit, which pushes many teams toward asynchronous cross-region replicas paired with synchronous same-region ones.
Orchestration tools such as Patroni 3.x sit on top of this entire mechanism, using PostgreSQL's own replication status views to decide which standby is eligible for promotion and to automate the failover that streaming replication alone does not provide.
| Synchronous level | Durability guarantee | Commit latency cost | Best fit |
|---|---|---|---|
off (async) | Standby may lag arbitrarily; recent commits can be lost on primary crash | None | Read replicas, cross-region DR where latency dominates |
local | Primary's own WAL flushed to disk; no standby guarantee | Minimal | Local durability without replication guarantees |
remote_write | Standby has received WAL into OS buffers, not yet fsynced | Moderate | Balance of safety and latency for most sync setups |
on | Standby has flushed WAL to disk | Higher | Tier-0 data where losing a committed transaction is unacceptable |
remote_apply | Standby has replayed WAL, visible to standby reads | Highest | Read-after-write consistency on the standby itself |
The deepest architectural lesson is that streaming replication gives you a dial, not a single answer, and the right synchronous level is a business decision about acceptable data loss dressed up as a database setting.
Common Misconceptions
- "Asynchronous replication means I will definitely lose data eventually." Data loss only happens if the primary crashes before an unacknowledged transaction's WAL reaches a standby, which for a healthy, low-lag standby is a narrow window rather than a certainty.
- "A synchronous standby guarantees zero data loss no matter what." It guarantees zero loss only for the specific standbys named in
synchronous_standby_names, so a misconfigured quorum can silently leave you with weaker guarantees than assumed. - "More cascading levels always increase lag proportionally." Each hop adds some lag, but a well-provisioned cascade with fast intra-region links often adds only milliseconds per level, far less than a single congested cross-region link would.
- "Replication slots are strictly a safety feature with no downside." A slot for an offline standby retains WAL forever, which has filled primary disks and taken down production clusters when nobody was monitoring inactive slots.
- "Streaming replication and logical replication are interchangeable." Streaming replication clones the entire cluster byte-for-byte, while logical replication moves row-level changes for selected tables, and the two solve genuinely different problems.
FAQs
What exactly gets sent over the network in streaming replication?
Raw WAL records, the same low-level log entries PostgreSQL writes for its own crash recovery, are streamed byte-for-byte to the standby rather than SQL statements or row diffs.
Why is a streaming standby described as being in permanent recovery?
The standby's recovery process replays incoming WAL exactly as it would after a crash, and it never exits that replay loop until it is promoted.
Does streaming replication let me pick which tables get replicated?
No, it replicates the entire physical cluster, and table-level selectivity is a capability of logical replication instead.
What is the practical difference between synchronous and asynchronous commit here?
Synchronous commit makes the primary wait for a standby's acknowledgment before returning success to the client, while asynchronous commit returns immediately and lets replication catch up afterward.
Can a synchronous standby block writes on the primary?
Yes, if it is unreachable and still listed in synchronous_standby_names, every commit waiting on its acknowledgment stalls until it reconnects or is removed from the quorum.
What does a replication slot actually protect against?
It stops the primary from recycling WAL segments a lagging standby still needs, at the cost of unbounded WAL retention if that standby never reconnects.
Why would I use a cascading replica instead of connecting every standby to the primary?
Cascading spreads the WAL-sending workload across multiple nodes, reducing connection and CPU pressure on the primary as the number of standbys grows.
Is promotion the same thing as failover?
Promotion is the mechanical step where a standby exits recovery and starts accepting writes, while failover is the broader process, often automated by tools like Patroni, that decides when and which standby to promote.
How current is a standby's data at any given moment?
Exactly as current as the WAL it has received and replayed, which is why replay lag, not wall-clock time, is the right way to reason about standby freshness.
What is a delayed replica used for?
It intentionally replays WAL after a configured delay, creating a window to catch and intervene before a destructive statement like an accidental mass delete reaches it.
Does cross-region streaming replication always need to be asynchronous?
Not always, but the added network latency of a synchronous cross-region standby is significant enough that most teams reserve synchronous commit for same-region standbys and use asynchronous for distant ones.
What role does an orchestrator like Patroni play on top of streaming replication?
Patroni watches PostgreSQL's own replication status to decide which standby is healthy and current enough to promote, then automates the promotion that streaming replication itself does not initiate on its own.
Related
- Streaming Replication Basics - hands-on setup for a single async standby
- Synchronous vs Asynchronous - choosing a commit level in practice
- Replication Slots - configuring slot-based WAL retention
- Cascading Replicas - multi-hop standby topologies
- HA & Failover Key Points - how replication feeds automated failover
Stack versions: This page was written for PostgreSQL 18.4 (stable major 18, maintenance line 17 also supported) and Patroni 3.x.