Articles

What Is Replication Lag? Causes and Fixes

Replication lag is the delay between a write on the primary database and its arrival on a replica. What causes it, how to measure it, and how to reduce it.

Chisato Chisato · · 4 min read
Abstract illustration representing database servers

Replication lag is the delay between a write committing on a primary database and that same write becoming visible on a replica. It’s measured in time — usually milliseconds under normal load, but capable of stretching to minutes when something goes wrong — and it’s the practical cost every team pays for read replicas and horizontally scaled reads. Zero lag would mean the replica is a perfect real-time mirror; in practice, some lag always exists, and the real engineering question is how much you can tolerate and how you find out when you’ve exceeded it.

Why lag exists at all

A replica doesn’t receive your application’s writes directly. It receives a stream of changes from the primary — typically the same write-ahead log entries the primary uses for its own crash recovery — and replays them in order. That replay path has several stages, and each one adds latency: the primary has to ship the log records over the network, the replica has to receive and buffer them, and then it has to apply them to its own copy of the data before a query against the replica will see the change.

Under light load, this entire pipeline runs in milliseconds. Under heavy load, any one of those stages can become a bottleneck. See what database replication is for the broader mechanics this article builds on.

The usual causes

A handful of patterns account for most lag spikes in production:

  • Long-running transactions on the primary. A transaction that holds a lock or writes a large batch delays the log records the replica needs, and single-threaded replay on the replica queues up behind it.
  • Single-threaded apply. Many replication systems historically replayed changes on a single thread to preserve ordering. A burst of writes that the primary handled in parallel across many connections gets serialized into one queue on the replica side, and that queue can’t drain any faster than one change at a time.
  • Large writes and bulk operations. A single UPDATE touching millions of rows, or a bulk import, generates a correspondingly large amount of log data that has to travel and replay before anything after it is visible.
  • Network saturation or distance. Cross-region replicas add real speed-of-light latency, and any network congestion between primary and replica compounds it.
  • Replica resource contention. If the replica is also serving read traffic, CPU or I/O contention between incoming reads and the replication apply process can slow the apply side down, which is somewhat counterintuitive — the replica falls further behind precisely because it’s busy being useful.
  • Schema changes. An index rebuild or a large ALTER TABLE can block or slow the apply thread just like it would slow writes on the primary.

How to measure it

Most database engines expose a lag metric directly — commonly a “seconds behind” figure computed by comparing timestamps between the last applied transaction and the current time, or by comparing log position offsets between primary and replica. Treat this number as an approximation, not a precise clock: it depends on clock synchronization between hosts and on how the specific replication implementation calculates it.

The more reliable pattern for production monitoring is alerting on trend and threshold rather than a single reading — a replica that briefly spikes to two seconds of lag during a nightly batch job is different from one that has been steadily climbing for an hour.

Why it matters to your application

Lag becomes a problem the moment your application assumes a replica is current. The classic failure mode: a user submits a form, the write commits on the primary, the application immediately redirects to a page that reads from a replica, and the replica hasn’t caught up yet — the user sees their own change vanish. This is the exact scenario covered in read-your-writes consistency, and it’s why many teams route “read my own write” queries back to the primary, or to a replica confirmed to be caught up, rather than to whichever replica happens to answer next.

Lag is also the concrete mechanism behind eventual consistency as a practical property rather than an abstract one — “eventually” is exactly as long as the current replication lag happens to be.

Reducing lag

There’s no single fix, because lag comes from several independent bottlenecks, but the common levers are:

  • Parallel or logical replication apply. Newer replication implementations apply independent transactions concurrently instead of serializing everything through one thread, which removes the single biggest source of lag under write bursts.
  • Smaller, more frequent writes. Breaking a huge batch update into chunks lets the replica apply and catch up incrementally instead of processing one enormous transaction.
  • Placing replicas closer to the primary, network-wise, when cross-region latency is the dominant factor — accepting that this trades off the disaster-recovery benefit of geographic separation.
  • Dedicating replicas by purpose. A replica used purely for analytics or reporting can tolerate more lag and take more resource contention than one serving latency-sensitive reads; separating the two workloads keeps read traffic from starving the apply process on your low-latency replica.
  • Choosing synchronous replication for specific writes that can’t tolerate any lag at all — at the cost of added write latency, since the primary now waits for a replica to acknowledge before it reports the write as committed. See synchronous vs asynchronous replication for that trade-off in detail.

The takeaway

Replication lag is the delay between a committed write and its visibility on a replica, and it’s produced by real, identifiable bottlenecks — transaction size, apply concurrency, network distance, and replica load — rather than randomness. Measure it continuously rather than assuming it’s zero, route consistency-sensitive reads around it, and match your replication mode (synchronous or asynchronous) and replica placement to how much staleness each part of your application can actually tolerate.

The Lycoris Team The Lycoris Team · · 4 min read

PgBouncer and Postgres Pooling Modes Explained

PgBouncer sits between clients and Postgres, reusing a small pool of real connections. Session, transaction, and statement modes trade features for scale.

#Databases #PostgreSQL #Performance
Chisato Chisato · · 4 min read

Postgres Logical Replication Explained

Logical replication streams row-level changes between Postgres databases instead of copying raw disk blocks — enabling selective sync, upgrades, and CDC.

#Databases #Cloud #DevOps