/

Glossary

Replication Lag: Causes and Fixes

Intergalactic Data Labs

—

—

10 min read

Table of contents

Summarize

Replication lag is the delay between a transaction committing on the primary and the same change becoming visible on a replica. It builds in four stages, in the network, on the replica’s disk, in the apply process, and for logical replication in the decoder. As of November 2026 Postgres and MySQL both expose each stage, and every stage has its own cause and its own fix.

Stage

Postgres measure

MySQL measure

Usual cause

Send and network

write_lag

Receiver thread position

Bandwidth, distance, timeouts

Flush on the replica

flush_lag

Relay log write

Slow replica disk

Apply

replay_lag

Seconds_Behind_Source

One apply thread, locks, big transactions

Decode, logical only

pg_stat_subscription

Not applicable

Large transactions decoded at commit

How do you measure replication lag?

Measure it on the primary in bytes and on the replica in seconds, and watch both, because each number lies in a different situation. The Postgres statistics reference puts it plainly. “For an asynchronous standby, the replay_lag column approximates the delay before recent transactions became visible to queries.”

System

Where to look

What it tells you

Postgres primary

pg_stat_replication

Per standby, bytes and time behind at each stage

Postgres replica

pg_last_xact_replay_timestamp()

Seconds since the last replayed commit

Postgres publisher

pg_replication_slots

WAL a slot still holds, wal_status, safe_wal_size

Postgres subscriber

pg_stat_subscription

Last LSN received and when

MySQL replica

SHOW REPLICA STATUS

Seconds_Behind_Source, receiver and applier positions

MySQL replica

performance_schema

Original commit and apply timestamps per worker

The primary-side query pairs the lag intervals with a byte count, since pg_wal_lsn_diff returns the difference in bytes between two log positions.

SELECT application_name, state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_bytes,
       write_lag, flush_lag, replay_lag
FROM

SELECT application_name, state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_bytes,
       write_lag, flush_lag, replay_lag
FROM

SELECT application_name, state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_bytes,
       write_lag, flush_lag, replay_lag
FROM

The replica-side figure has a trap. Cloud SQL computes it as now minus pg_last_xact_replay_timestamp(), and nothing resets it on an idle primary, which is why RDS for PostgreSQL reports up to five minutes of lag while no user transactions run.

MySQL’s number carries a different caveat, stated in the manual. “In essence, this field measures the time difference in seconds between the replication SQL (applier) thread and the replication I/O (receiver) thread.” A slow network leaves it at 0 while the receiver trails the source, so read the commit timestamps in performance_schema instead.

SELECT WORKER_ID,
       LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,
       LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP
FROM

SELECT WORKER_ID,
       LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,
       LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP
FROM

SELECT WORKER_ID,
       LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,
       LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP
FROM

Why a single apply process is the usual bottleneck

The primary commits with every client in parallel, but a physical replica replays that work in one process, so the apply stage falls behind first. The Postgres glossary defines the startup process as “An auxiliary process that replays WAL during crash recovery and in a physical replica.” The community parallel recovery page adds that records are replayed in written order in a single thread while many backends generate them.

MySQL went multi-threaded by default. A replica runs four applier workers plus a coordinator, with replica_parallel_type at LOGICAL_CLOCK and replica_preserve_commit_order on, per the replica options. In MySQL 8.4 the source always writes write-set dependencies so the workers know which transactions are independent.

Postgres logical replication is single-threaded per subscription too. The monitoring page states “Normally, there is a single apply process running for an enabled subscription.” Parallel apply covers only large streamed transactions, the default in Postgres 18.

Why do large transactions spike replication lag?

Because both engines hand a transaction to the replica only at commit, so an hour-long batch arrives as an hour of work in one burst. The logical decoding docs say changes “are only passed to the output plugin at commit.” They add that “Depending on the size of the transaction and network bandwidth, the transfer time may significantly increase the apply lag.”

MySQL behaves the same way at the binlog. Updates inside a transaction are cached until COMMIT, at which point “mysqld writes the entire transaction to the binary log before the COMMIT is executed,” per the binary log docs. Cloud SQL’s advice for MySQL is to break large transactions into smaller ones.

A VACUUM FULL or a column type change is the hidden version of this, since each rewrites the whole table as one unit. Postgres 14 gave logical subscribers a relief valve, streaming an in-progress transaction once it passes logical_decoding_work_mem, 64 MB by default.

How replica queries and hardware slow replay

A busy or undersized replica slows its own replay by competing for disk and CPU and by holding snapshots that conflict with cleanup, which is why RDS says a read replica should match the source’s compute. The hot standby docs explain that a vacuum cleanup record conflicts with standby queries that can still see the rows, so the standby must delay the WAL or cancel the query.

The knob is max_standby_streaming_delay, 30 seconds by default, per the replication settings. Setting it to -1 waits forever, and AWS warns that this grows lag indefinitely and piles up WAL on the source. The alternative, hot_standby_feedback, stops cleanup on the primary and “can cause database bloat on the primary for some workloads.”

Missing primary keys turn one statement into a full scan per row. MySQL’s sql_require_primary_key docs describe an update that scans the table once on the source and once per modified row on the replica, and Postgres logical replication needs a replica identity for the same reason.

Which fix matches which cause?

Match the fix to the lagging stage, because parallelism does nothing for a saturated network and a bigger replica does nothing for a one-transaction burst.

Signal

Likely cause

Fix

write_lag high, flush and replay low

Network or distance

Enable wal_compression, move the replica closer, raise wal_sender_timeout

flush_lag high

Slow replica disk

Faster storage, size the replica like the primary

replay_lag high and steady

Apply rate below write rate

Add parallelism, cut rewrites, remove read traffic

replay_lag spikes then recovers

One large transaction

Batch the job, stream in-progress transactions

Query cancels in pg_stat_database_conflicts

Standby queries vs cleanup

Separate analytics replica or hot_standby_feedback

Seconds_Behind_Source stuck at 0

Receiver behind on a slow link

Read performance_schema timestamps instead

Lock waits on a MySQL replica

Too many parallel workers

Match replica_parallel_workers to replica cores

Full scans per replicated row

No primary key

Add keys, set sql_require_primary_key

Cloud SQL’s MySQL guidance sets replica_parallel_workers to the replica’s vCPU count and warns that a very high value causes lock waits and deadlocks, per its lag page. Distance sets a floor that no tuning removes, since Azure measures cross-region latency from milliseconds to seconds.

Where replication lag goes wrong

Four failures turn a lag number into an outage, and two come from the slot meant to prevent lag.

requested WAL segment has already been removed

The primary recycled a segment the standby had not read, and the walsender raises this error from walsender.c. The warm standby chapter says such a standby “will need to be reinitialized from a new base backup.” A slot or a nonzero wal_keep_size prevents it.

The slot fills the disk

A slot holds every segment its consumer has not confirmed, and the same chapter is direct. “Beware that replication slots can cause the server to retain so many WAL segments that they fill up the space allocated for pg_wal.” The cap is max_slot_wal_keep_size, unlimited by default per the replication settings, and Postgres 18 adds idle_replication_slot_timeout for slots nobody uses.

SELECT slot_name, active, wal_status, safe_wal_size,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes
FROM

SELECT slot_name, active, wal_status, safe_wal_size,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes
FROM

SELECT slot_name, active, wal_status, safe_wal_size,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes
FROM

The replica never catches up

When apply throughput sits below write throughput, lag grows without bound. Azure’s Postgres docs say that “with persistent heavy write-intensive primary workloads, the replication lag can continue to grow” and suggest recreating the replica once the burst ends. Cutting the write rate, or the work per write, is the only durable fix.

MySQL error 1236

The replica asks for a binlog position the source has purged, and the source answers with ER_SOURCE_FATAL_ERROR_READING_BINLOG, per the error reference. Retention is binlog_expire_logs_seconds, 30 days by default, and a replica that lags past it has to be rebuilt.

How managed databases report replication lag

Every managed service exposes a lag metric, but the units differ, and three of them rest on the replica-side timestamp that climbs on an idle primary.

Service

Metric

Unit

Note

Amazon RDS

ReplicaLag

Seconds

-1 means replication is not running

Amazon Aurora

AuroraReplicaLag

Milliseconds

Usually under 100 ms per the Aurora docs

Cloud SQL

replica_lag, network_lag

Seconds

network_lag caps at 25 seconds

Azure PostgreSQL

physical_replication_delay_in_seconds

Seconds

Bytes version lives on the primary only

Azure MySQL

replication_lag

Seconds

Built on Seconds_Behind_Master

Debezium

MilliSecondsBehindSource

Milliseconds

Includes clock skew between hosts

Debezium sits in the table because a change data capture connector is a logical replica with the same slot risks, so its docs call for heartbeats on quiet databases.

Related guides

The Postgres logical replication guide and the MySQL binlog guide cover the decode stage from the source side. Change streams vs triggers vs polling compares the delay each capture method adds. The WAL archiving guide explains the segments a slot retains, and the zero-downtime migration walkthrough is where reaching zero lag decides the cutover.

Frequently asked questions

What is replication lag?

Replication lag is the delay between a transaction committing on the primary database and the same change becoming visible on a replica. It is measured in seconds from the commit timestamp or in bytes of log not yet applied. Every asynchronous replica carries some lag, and the question is whether it is growing.

What causes replication lag?

A replica falls behind when any stage of the pipeline is slower than the primary’s write rate. The usual causes are a single apply process facing many concurrent writers, one large transaction delivered at commit, read queries on the replica blocking replay, a replica with weaker hardware than the primary, and network distance.

How do I check replication lag in PostgreSQL?

On the primary, query pg_stat_replication for write_lag, flush_lag, and replay_lag, and compute bytes with pg_wal_lsn_diff between pg_current_wal_lsn and replay_lsn. On the replica, subtract pg_last_xact_replay_timestamp from now, remembering that this figure climbs on an idle primary because no new commit arrives to reset it.

What does Seconds_Behind_Source mean in MySQL?

It is the gap in seconds between the transaction the applier thread is working on and the current time on the replica, using the commit timestamp the source wrote into the event. The manual warns it measures applier against receiver, so on a slow network it reads 0 while the receiver is still behind.

How much replication lag is normal?

Milliseconds in the same availability zone, seconds across regions, and anything that keeps growing is a problem. Amazon says Aurora replicas usually sit under 100 milliseconds behind the writer, and Azure’s guidance for MySQL read replicas is a few seconds to a couple of minutes. Alert on your own baseline rather than a universal number.

Does multi-threaded replication reduce lag?

Yes, when transactions are independent of each other. Current MySQL replicas run four applier workers by default, with dependency tracking from the source’s write sets. Postgres physical replicas replay in one process, and logical subscribers gained parallel apply for large in-progress transactions in Postgres 16, the default streaming mode in Postgres 18.

Can replication lag cause data loss?

With asynchronous replication, yes, if the primary fails before the replica receives the last commits. Azure’s MySQL docs say the lag at the moment you promote a replica is how much data is lost. Synchronous commit at the on or remote_apply level removes that window at the price of slower commits.

Why does replication lag keep growing instead of recovering?

Because the replica’s apply rate is below the primary’s write rate, so the backlog can only grow. Azure’s Postgres docs state that under persistent write-heavy workloads the lag can continue to grow. Cut the write rate, add apply parallelism, remove read traffic from the replica, or rebuild the replica once the burst ends.

More articles

Stay up to date with what we’re building

Stay up to date with what we’re building

Stay up to date with what we’re building

Questions

Answered

FAQ

What does Galaxy do?

What is Filament?

What is enterprise context management?

What does working with Galaxy look like?

How do you handle security and compliance?

Why does Galaxy build in the open?

Own your knowledge stack

Own your knowledge stack

Copyright © 2026 Galaxy. All rights reserved.