Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

Database Replication Explained: Primaries, Replicas and the Lag That Bites

About Post

A user saves a form. The app redirects to the details page. The page says: "Record not found."

They refresh. There it is.

No bug in the controller, no failed query, nothing in the logs. Just a database replica that was a fraction of a second behind, and an app that asked it a question too early. If you've added read replicas to an application, you'll meet this ghost sooner or later. Let's look at how replication works, what Laravel does for you, and where the gaps are.

The setup in one picture

Replication means keeping copies of the same database on more than one server:

  • The primary accepts writes: every INSERT, UPDATE and DELETE.
  • One or more replicas receive a stream of those changes and apply them to their own copy. They serve reads.

In MySQL, the primary records every change in its binary log. Each replica connects, pulls new entries from that log into a local relay log, and replays them. It's like a head chef calling out every change to the recipe book while assistants update their own copies across the kitchen. Close behind, but never quite instantly.

Asynchronous means "eventually"

By default, MySQL replication is asynchronous. The primary commits your write and tells your app "done" without waiting for any replica. The replica applies the change shortly after. Usually that gap is tiny. Under load, or during a long-running write or a big migration, it can grow to seconds or much longer.

That gap is replication lag, and it's the source of almost every replica-related bug. You can see it on the replica itself:

SHOW REPLICA STATUS\G
-- look at Seconds_Behind_Source

There are stronger modes. Semi-synchronous replication makes the primary wait until at least one replica has received the change, which protects against data loss if the primary dies. Note the word "received": it doesn't promise the change has been applied and is readable yet. Stale reads can still happen.

Why bother with replicas?

  • Spreading read load. Most web apps read far more than they write. Replicas let you add read capacity without touching the primary.
  • Isolating heavy queries. Reports and exports can run on a replica so they don't slow down users' writes.
  • Failover. If the primary fails, a replica can be promoted to take its place. Managed services like Amazon RDS can handle much of this for you.

And one reason that is not on the list: backups. A replica copies your mistakes as faithfully as your data. Run DELETE without a WHERE on the primary, and the replicas obediently delete everything too. You still need real backups and point-in-time recovery.

Read and write connections in Laravel

Laravel lets you configure separate hosts for reads and writes on a single connection in config/database.php:

'mysql' => [
    'driver' => 'mysql',
    'read' => [
        'host' => [env('DB_READ_HOST_1'), env('DB_READ_HOST_2')],
    ],
    'write' => [
        'host' => [env('DB_WRITE_HOST')],
    ],
    'sticky' => true,
    'database' => env('DB_DATABASE'),
    'username' => env('DB_USERNAME'),
    'password' => env('DB_PASSWORD'),
    // ...charset, collation and the rest as usual
],

SELECT queries go to a read host (picked at random if there are several), and everything else goes to the write host. Your Eloquent code doesn't change at all.

What sticky fixes, and what it doesn't

With 'sticky' => true, once a write has happened during the current request, every later read in that same request goes to the primary. So "create the record, then load it back to return it as JSON" works fine.

But look at the story from the start of this article again. The save happens in one request. The redirect leads to a second request, which has done no writes, so it reads from a replica. Sticky doesn't help across requests. The same goes for:

  • Queued jobs dispatched right after a write. The worker is a different process and may read from a replica that hasn't seen the new row yet. If you dispatch inside a transaction, dispatch after commit too, but that alone doesn't solve lag.
  • Mobile apps that save and then immediately refetch a list.
  • Webhooks that arrive milliseconds after your own write, asking about the record you just created.

Fixes that actually work

Read from the primary when it matters. For reads that must see the latest data, say so explicitly:

$contract = Contract::onWriteConnection()->findOrFail($id);

Use this in jobs that process something just written, and on "show me what I just saved" pages.

Make your own writes stick for a moment. After a user writes, remember it briefly (for example, a timestamp in the session) and send that user's reads to the primary for the next few seconds. This gives "read your own writes" consistency to the person who'd notice, without sending everyone's reads to the primary.

Return the data you just wrote. If the create endpoint returns the full record, the client doesn't need to fetch it again straight away.

Watch the lag. Alert when Seconds_Behind_Source stays high. A replica that's minutes behind is serving confidently wrong answers.

The mental model: a replica is a slightly old photo of your database. Fine for browsing, reports and search. Not fine for "did my payment go through?" Decide per query which one you need.

Do you even need replicas?

Often, not yet. A single well-indexed MySQL server handles a lot more than people expect. Before adding replicas and the consistency problems that come with them, check the cheaper fixes: missing indexes, N+1 queries, caching hot reads, and a bigger instance. Replicas are great when reads truly outgrow one server or when you need failover. They're a poor fix for slow queries.

The Laravel docs on read and write connections cover the configuration options.

Have you been bitten by replication lag? I'm curious where it showed up for you: a redirect, a queued job, or somewhere stranger.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close