{"post":{"slug":"replication-lag-explained-2026","title":"Replication Lag: When the Replica Has Not Caught Up","excerpt":"Routing reads to a replica scales your read capacity and introduces a window where the replica does not yet have data the primary already committed.","category":"code","date":"January 6, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span> user updates their profile and is redirected to the profile page, which shows the old values. They update again. Same result. They conclude the feature is broken and contact support.\n\nNothing is broken. The write went to the primary, the read went to a replica, and the replica had not yet applied the change.\n\n## What changed in 2026\n\n- **Per-query routing became standard.** Choosing primary or replica per query, rather than globally, became the expected pattern.\n- **Time-based lag monitoring displaced byte-based.** Measuring how far behind in seconds became the norm.\n- **Framework support improved.** More ORMs gained first-class read/write splitting with routing hints.\n- **Session-consistency mechanisms spread.** Tracking a write position and routing subsequent reads accordingly became available in more systems.\n\n## Where lag comes from\n\nThe primary commits a change and streams it to replicas, which apply it. Several things can delay that.\n\n**Replay is largely single-threaded** in many systems, while writes on the primary are concurrent. A burst of parallel writes can be produced faster than one replay process applies them.\n\n**Network transfer** takes time, particularly across regions.\n\n**Conflicting queries on the replica** can pause replay. A long-running read on the replica may conflict with a change that would invalidate what it is reading, and depending on configuration the replica either delays replay or cancels the query.\n\n**Large transactions** replay as a unit. A bulk operation on the primary appears on the replica only when its whole transaction is applied.\n\nThat third cause is the one that surprises people: a long analytical query on the replica can *cause* the lag it then suffers from.\n\n## Measure in time\n\nByte-based lag — how many bytes behind the replica is — is nearly useless operationally. A hundred megabytes behind means nothing without knowing the write rate.\n\nTime-based lag answers the question that matters: how stale is data on this replica right now. Most systems expose it directly.\n\n| Lag | Typical acceptability |\n| --- | --- |\n| Milliseconds | Fine for nearly everything |\n| A second or two | Fine for most reads |\n| Tens of seconds | Analytics only |\n| Minutes | Investigate |\n| Growing steadily | Replica cannot keep up |\n\nThe distinction between a lag that is high and stable versus one that is *growing* matters most. Stable lag means the replica keeps up with a delay. Growing lag means it cannot, and it will keep falling behind until something changes.\n\n## Route by requirement\n\nThe workable pattern is deciding per query rather than globally.\n\n**To the primary:** anything read immediately after a related write, anything used to make a decision with consequences, and anything where a user would notice staleness.\n\n**To a replica:** analytics, reporting, list views, search, and anything where a second of staleness is invisible.\n\nRead-your-own-writes is the specific case that needs handling. Three approaches: route reads to the primary for a short window after a write from that session; track the write position and route to a replica only once it has caught up past that position; or route all reads within a request to the primary if the request performed a write.\n\nThe second is the most precise and requires system support. The first is crude and works everywhere.\n\n## Common mistakes\n\n- **Routing all reads to replicas.** Guarantees read-after-write failures.\n- **Byte-based lag monitoring.** Says nothing about staleness.\n- **Alerting on a lag threshold only.** Growth matters more than level.\n- **Long analytical queries on a replica serving user traffic.** Causes the lag it suffers from.\n- **No handling for read-your-own-writes.** The case users notice.\n- **Assuming failover is instant.** A promoted replica may be missing recent writes.\n- **Not testing with realistic lag.** Local development has none.\n\n## FAQ\n\n**How much lag is acceptable?**\nDepends entirely on the query. Sub-second is fine for nearly everything; analytics tolerates far more. The useful question is per query, not per system.\n\n**Can I eliminate lag?**\nSynchronous replication removes it at the cost of write latency, since the primary waits for the replica — see [synchronous replication](/blog/synchronous-replication-2026). Most systems accept some lag rather than pay that on every write.\n\n**What happens on failover?**\nAn asynchronously-replicated replica promoted after a primary failure may be missing the most recent writes. That data loss window is the cost of asynchronous replication and should be an explicit decision.\n\n**Does logical replication lag differently?**\nIt has its own characteristics and constraints — see [logical replication](/blog/logical-replication-explained-2026).\n\n## Where to go next\n\nFor the zero-lag alternative and its cost, read [synchronous replication](/blog/synchronous-replication-2026). For the flexible replication type, [logical replication](/blog/logical-replication-explained-2026), and for the underlying mechanism, [read replicas explained](/blog/read-replicas-explained-2026).","hue":316,"metaTitle":"Replication Lag: When the Replica Has Not Caught Up","metaDescription":"Why read replicas serve stale data, how to measure lag meaningfully, and how to route queries that cannot tolerate it.","keywords":["replication lag","read after write consistency","replica staleness","read routing","replication delay monitoring","stale reads","database scaling","database operations 2026"],"tldr":["**Read-after-write is the failure users notice** — they update something and see the old value.","**Measure lag in time, not bytes** — byte lag says nothing about how stale the data is.","**Route reads that must be current to the primary** — per-query routing beats a global choice.","**A single long transaction on the replica can stall replay** — conflicts pause application of changes.","**Skip:** routing all reads to replicas without deciding which ones tolerate staleness."],"readTime":"4 min read","wordCount":776},"related":[{"slug":"read-replicas-explained-2026","title":"Read Replicas Explained: Scaling Reads in 2026","excerpt":"Most applications are read-heavy long before they are write-heavy. Read replicas copy data to extra nodes so queries scale out without the complexity of sharding or touching the write path at all.","category":"code","date":"August 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":314},{"slug":"quorum-reads-explained-2026","title":"Quorum Reads: Buying Consistency With Latency","excerpt":"Reading from one replica is fast and may return stale data. Requiring several replicas to agree costs latency and gives you a guarantee about what you read.","category":"code","date":"January 19, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":300},{"slug":"replication-explained-2026","title":"Replication explained in 2026: how databases stay in sync across nodes","excerpt":"Database replication keeps copies of your data on multiple servers for availability and read scaling. Here is the practical 2026 guide to getting it right.","category":"code","date":"April 7, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":321}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"portfolio-turnover-cost-2026","title":"Portfolio Turnover: The Cost That Does Not Appear in the Fee","excerpt":"A fund's expense ratio is disclosed prominently. Its trading costs and the tax bill it generates for you are not, and for an active fund they can exceed the stated fee.","category":"finance","date":"January 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":290},"older":{"slug":"varchar-vs-text-2026","title":"Varchar vs Text: A Length Limit Is a Constraint, Not a Speedup","excerpt":"Declaring a column as varchar(255) does not make it smaller or faster in most databases. It adds a constraint, and constraints should be chosen deliberately.","category":"code","date":"January 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":301}}