Laravel Read Replicas: Verify Replica Sync with Commit IDs | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://msaied.com) - [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [About](https://msaied.com#about)

           [  Contact](https://msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://msaied.com)
- [ProjectsCase studies](https://msaied.com/projects)
- [ArticlesEngineering notes](https://msaied.com/articles)
- [CertificatesCredentials](https://msaied.com/certificates)
- [AboutHow I work](https://msaied.com#about)
- [ContactGet in touch](https://msaied.com#contact)

  [Start a conversation](https://msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://msaied.com)
2. /
3. [Articles](https://msaied.com/articles)
4. /
5. [Laravel](https://msaied.com/articles?category=laravel)
6. /
7. Laravel Read Replicas: Check if a Replica Has Caught Up with Commit IDs

   [Laravel](https://msaied.com/articles?category=laravel) [Tips &amp; Tricks](https://msaied.com/articles?category=tips-tricks) 

 Laravel Read Replicas: Check if a Replica Has Caught Up with Commit IDs
========================================================================

 Laravel 13.40 adds getLastCommitId(), hasCommitId(), and mergeCommitIds() so you can verify a read replica has applied a specific write before reading from it, replacing fixed time-window guesses.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://msaied.com#person) Published 11 Nov 2026 · Updated 13 Nov 2026 · 4 min read

ShareCopy linkCopied

 ![Laravel Read Replicas: Check if a Replica Has Caught Up with Commit IDs](https://cdn.msaied.com/822/5e40cb946aeac937ab3dc0017777ec8b.png) 

  On this page +1. [The Problem with Fixed Replication Lag Windows](#the-problem-with-fixed-replication-lag-windows)
2. [The Three New Methods](#the-three-new-methods)
3. [Database Requirements](#database-requirements)
4. [Read Your Own Writes with Session Middleware](#read-your-own-writes-with-session-middleware)
5. [Waiting in Queued Jobs](#waiting-in-queued-jobs)
6. [Key Takeaways](#key-takeaways)

 The Problem with Fixed Replication Lag Windows
----------------------------------------------

In a Laravel application using a read/write database setup, writes go to the primary server and reads come from one or more replicas. Replicas copy changes from the primary asynchronously, which means they can fall behind. A user who submits a form and is redirected to a confirmation page may see stale data if that page reads from a replica that hasn't yet received the write.

Laravel 13.38 introduced the `PreventStaleDatabaseReads` middleware, which routes reads to the primary for a fixed two-second window after a write. Two seconds is an estimate — if replication lag exceeds it, users still see old data; if lag is shorter, reads hit the primary unnecessarily.

**Laravel 13.40** replaces the guesswork with three new connection methods contributed by [Tobias Petry](https://github.com/tpetry): `getLastCommitId()`, `hasCommitId()`, and `mergeCommitIds()`.

The Three New Methods
---------------------

```php
use Illuminate\Support\Facades\DB;

// After a write, capture the primary's replication position
$commitId = DB::getLastCommitId();

// Check once whether the replica has applied that position
DB::hasCommitId($commitId);

// Wait up to 2 seconds for the replica to catch up
DB::hasCommitId($commitId, wait: 2);

// Combine positions from multiple writes into one
$combined = DB::mergeCommitIds([$firstId, $secondId]);

```

`getLastCommitId()` always queries the write connection and returns the primary's current replication position as a string. The format is database-specific:

| Database | Format example | |---|---| | MySQL (GTID) | `3e11fa47-71ca-11e1-9e33-c80aa9429562:1-77` | | MariaDB | `0-1-1052` | | PostgreSQL | `0/16B3748` |

`hasCommitId()` returns `true` when the queried server has applied everything up to that position. With `wait: 0` (default) it checks once and returns immediately. A higher value tells the database to block until the replica catches up or the timeout expires, returning `false` on timeout.

`mergeCommitIds()` accepts an array of commit IDs and returns a single ID covering all of them. It throws a `RuntimeException` on an empty array.

Database Requirements
---------------------

MySQL requires `log_bin`, `gtid_mode = ON`, and `enforce_gtid_consistency = ON`. MariaDB requires only `log_bin`. PostgreSQL needs no special settings, but the `wait` argument requires PostgreSQL 19 or later. SQLite and SQL Server are not supported.

Read Your Own Writes with Session Middleware
--------------------------------------------

A practical pattern stores the commit ID in the session after each write. On the next request, the middleware checks whether the replica has caught up. If it has, reads proceed from the replica and the session key is cleared. If not, reads are forced to the primary:

```php
public function handle(Request $request, Closure $next): Response
{
    $pending = $request->session()->get('db_commit_id');

    if ($pending !== null) {
        if (DB::hasCommitId($pending)) {
            $request->session()->forget('db_commit_id');
            $pending = null;
        } else {
            DB::useWriteConnectionWhenReading();
        }
    }

    $response = $next($request);

    if (DB::hasModifiedRecords()) {
        $commitId = DB::getLastCommitId();
        $request->session()->put('db_commit_id', $pending === null
            ? $commitId
            : DB::mergeCommitIds([$pending, $commitId]));
    }

    return $response;
}

```

Append this middleware to the `web` group in `bootstrap/app.php` so it runs inside the session middleware.

Waiting in Queued Jobs
----------------------

A queued job dispatched immediately after a write can start before the replica has the row. Pass the commit ID to the job and wait a short time in `handle()`:

```php
$order = DB::transaction(fn () => Order::create([...]));
SendOrderConfirmation::dispatch($order->id, DB::getLastCommitId());

```

Call `getLastCommitId()` **after** the transaction commits — inside the transaction the primary hasn't committed yet. In the job, use `usingWritePdo()` as the fallback rather than `useWriteConnectionWhenReading()`, because queue workers reuse connections across jobs and `usingWritePdo()` restores the previous setting when its callback returns.

Key Takeaways
-------------

- `getLastCommitId()` captures the primary's exact replication position after a write.
- `hasCommitId()` checks whether a specific replica has applied that position, optionally waiting.
- `mergeCommitIds()` collapses multiple commit IDs into one, useful when a session spans several writes.
- The session middleware pattern routes reads to the primary only when the replica is genuinely behind, not on a fixed timer.
- MySQL requires GTIDs enabled; the `wait` argument on PostgreSQL requires version 19+.
- Pass commit IDs (not models) to queued jobs to avoid premature replica reads before `handle()` runs.

---

Source: [Laravel Read Replicas: Check if a Replica Has Caught Up — Laravel News](https://laravel-news.com/laravel-read-replica-commit-ids)

- [Laravel](https://msaied.com/articles?search=Laravel)
- [Read Replicas](https://msaied.com/articles?search=Read%20Replicas)
- [Database](https://msaied.com/articles?search=Database)
- [Replication](https://msaied.com/articles?search=Replication)
- [Laravel 13.40](https://msaied.com/articles?search=Laravel%2013.40)

 Frequently asked questions 
---------------------------

  What is the difference between hasCommitId() with and without the wait argument?Without the wait argument (or with wait: 0), hasCommitId() queries the replica once and returns immediately — true if caught up, false if not. With a positive integer like wait: 2, it asks the database to block for up to that many seconds until the replica reaches the given position, returning false if the timeout expires before it does.

   Why should getLastCommitId() be called after a transaction, not inside it?Inside a transaction the primary has not yet committed the changes, so the replication position returned would not include those writes. Calling getLastCommitId() after the transaction ensures the returned ID covers everything that was just committed.

   Why use usingWritePdo() instead of useWriteConnectionWhenReading() as a fallback in queued jobs?Queue workers reuse the same database connection across multiple jobs. useWriteConnectionWhenReading() persists for the lifetime of the connection, so later jobs in the same worker process would also read from the primary. usingWritePdo() accepts a callback and restores the previous connection setting when the callback returns, keeping subsequent jobs unaffected.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://msaied.com#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleInsert Middleware Before or After Existing Laravel Middleware (Laravel 13.40)](https://msaied.com/articles/insert-middleware-before-or-after-existing-laravel-middleware-laravel-1340)  

   On this page
-------------

1. [The Problem with Fixed Replication Lag Windows](#the-problem-with-fixed-replication-lag-windows)
2. [The Three New Methods](#the-three-new-methods)
3. [Database Requirements](#database-requirements)
4. [Read Your Own Writes with Session Middleware](#read-your-own-writes-with-session-middleware)
5. [Waiting in Queued Jobs](#waiting-in-queued-jobs)
6. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/821/88459f9998da0239229cdbcb6f7daa6c.png) Laravel · 3 min read### Insert Middleware Before or After Existing Laravel Middleware (Laravel 13.40)

6 Nov 2026 ](https://msaied.com/articles/insert-middleware-before-or-after-existing-laravel-middleware-laravel-1340) [ ![](https://cdn.msaied.com/818/b17de0c62dc689581d4135b9cb797240.png) Laravel · 4 min read### Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys

5 Nov 2026 ](https://msaied.com/articles/laravel-herd-2-coding-agents-workspaces-and-cloud-deploys) [ ![](https://cdn.msaied.com/820/d87884969c96b6d38c264c4213db8d12.png) Laravel · 4 min read### Strict Request Input Casting in Laravel 13.40

5 Nov 2026 ](https://msaied.com/articles/strict-request-input-casting-in-laravel-1340) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://msaied.com)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://msaied.com/sitemap.xml)
