Laravel Read/Write Splitting &amp; Sticky Reads | 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. Read/Write Splitting and Sticky Reads in Laravel: A Production Guide

 Read/Write Splitting and Sticky Reads in Laravel: A Production Guide
=====================================================================

 Configure Laravel's read/write connection splitting correctly, avoid stale-read bugs with sticky sessions, and tune connection pooling so your replicas actually reduce primary load.

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

ShareCopy linkCopied

 ![Read/Write Splitting and Sticky Reads in Laravel: A Production Guide](https://cdn.msaied.com/788/efb3e90bd9347b097a81544cadd32b67.png) 

  On this page +1. [Why Read/Write Splitting Breaks in Subtle Ways](#why-readwrite-splitting-breaks-in-subtle-ways)
2. [The Baseline Configuration](#the-baseline-configuration)
3. [The sticky Option — What It Actually Does](#the-codestickycode-option-what-it-actually-does)
4. [Forcing a Connection Explicitly](#forcing-a-connection-explicitly)
5. [Connection Pooling Considerations](#connection-pooling-considerations)
6. [Testing Replica Routing](#testing-replica-routing)
7. [Key Takeaways](#key-takeaways)

 Why Read/Write Splitting Breaks in Subtle Ways
----------------------------------------------

Adding a read replica is one of the highest-leverage database moves you can make — until it silently serves stale data to a user who just submitted a form. Laravel ships with first-class support for read/write splitting, but the defaults hide several production traps. This article walks through the configuration, the `sticky` option, connection pooling concerns, and the patterns that keep replicas reliable.

---

The Baseline Configuration
--------------------------

Laravel's `config/database.php` accepts a `read` / `write` key inside any connection definition. Both keys accept an array of hosts, and Laravel round-robins across them.

```php
'pgsql' => [
    'driver' => 'pgsql',
    'read' => [
        'host' => [
            env('DB_READ_HOST_1', '10.0.1.11'),
            env('DB_READ_HOST_2', '10.0.1.12'),
        ],
    ],
    'write' => [
        'host' => env('DB_WRITE_HOST', '10.0.1.10'),
    ],
    'sticky' => true,
    'database' => env('DB_DATABASE', 'app'),
    'username' => env('DB_USERNAME', 'app'),
    'password' => env('DB_PASSWORD'),
    'charset' => 'utf8',
    'prefix' => '',
    'schema' => 'public',
],

```

Every `SELECT` goes to a read host; every `INSERT`, `UPDATE`, `DELETE`, and DDL goes to the write host. That's it for the happy path.

---

The `sticky` Option — What It Actually Does
-------------------------------------------

When `sticky` is `true`, Laravel records whether the write connection was used **during the current request**. If it was, all subsequent reads in that same request are also routed to the write host.

This prevents the classic post-write stale-read: user saves a record, the page reloads, the read replica hasn't caught up yet, and the user sees the old value.

```php
// Without sticky: this SELECT might hit a replica that hasn't replicated yet.
User::create(['name' => 'Alice']);
$user = User::find(1); // stale read possible

// With sticky: the SELECT is forced to the write host for the rest of this request.

```

The flag is stored on the `Connection` instance via `$recordsModified`. You can inspect or reset it:

```php
DB::connection('pgsql')->recordsHaveBeenModified(); // bool
DB::connection('pgsql')->forgetRecordModificationState(); // reset if needed

```

Reset it only when you're certain replication lag is acceptable — for example, inside a queued job that runs seconds after the originating request.

---

Forcing a Connection Explicitly
-------------------------------

Sometimes you need fine-grained control regardless of `sticky`:

```php
// Always read from replica
$stats = DB::connection('pgsql')
    ->table('events')
    ->useReadPdo()
    ->count();

// Always write host
$fresh = User::on('pgsql::write')->find($id);

```

`useReadPdo()` is available on the query builder. For Eloquent, the `on()` method accepts a connection name, but Laravel doesn't expose a `::write` alias by default — you'd define a second named connection pointing at the primary, or use `DB::connection()->getPdo()` directly for one-off cases.

---

Connection Pooling Considerations
---------------------------------

Laravel opens a new PDO connection per worker process (or per Octane request cycle if you're not careful). With PgBouncer or ProxySQL in front:

- **PgBouncer in transaction mode** breaks `SET LOCAL`, advisory locks, and prepared statements. Use session mode or disable prepared statements in Laravel:

```php
'options' => [
    PDO::ATTR_EMULATE_PREPARES => true,
],

```

- **ProxySQL** can route read/write at the proxy layer, making Laravel's own splitting redundant. Pick one layer to own the routing — dual routing causes confusion.
- Under **Octane**, the PDO connection persists across requests. Ensure your pooler's `server_idle_timeout` is longer than Octane's worker lifetime, or you'll hit `SQLSTATE[HY000]: General error: 2006 MySQL server has gone away`.

---

Testing Replica Routing
-----------------------

Verify routing in a feature test by swapping the connection resolver:

```php
it('routes selects to the read connection', function () {
    $readQueries = [];

    DB::listen(function ($query) use (&$readQueries) {
        if (str_starts_with(strtoupper(trim($query->sql)), 'SELECT')) {
            $readQueries[] = $query->sql;
        }
    });

    User::all();

    expect($readQueries)->not->toBeEmpty();
});

```

For a true integration test, spin up two separate SQLite files as read/write targets and assert row counts differ after an insert without `sticky`.

---

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

- Enable `sticky` on any connection where users immediately read their own writes.
- `forgetRecordModificationState()` is your escape hatch inside jobs where lag is acceptable.
- Choose **one** layer (application or proxy) to own read/write routing — never both.
- In transaction-mode PgBouncer, disable server-side prepared statements via `PDO::ATTR_EMULATE_PREPARES`.
- Under Octane, keep pooler idle timeouts longer than worker lifetimes to avoid dropped connections.

- [laravel](https://msaied.com/articles?search=laravel)
- [database](https://msaied.com/articles?search=database)
- [performance](https://msaied.com/articles?search=performance)
- [postgresql](https://msaied.com/articles?search=postgresql)
- [mysql](https://msaied.com/articles?search=mysql)

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

  Does Laravel's `sticky` option affect queue workers?No. Each job runs in its own dispatch cycle, so `recordsModified` starts as false for every job. If a job writes and then reads within the same execution, sticky will kick in for that job's lifetime — but there's no cross-job state.

   Can I use read/write splitting with Laravel transactions?Yes. Laravel automatically routes all queries inside a `DB::transaction()` block to the write connection, regardless of the sticky setting. The transaction wraps a single PDO connection, so replicas are never involved.

   How do I add a third replica without redeploying?Put the replica host list behind an environment variable as a comma-separated string, parse it in `config/database.php`, and reload your workers. No code change is needed — Laravel round-robins across all hosts in the `read.host` array automatically.

   ![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 articleFluent Rule Builders for All Laravel Validation Rules](https://msaied.com/articles/fluent-rule-builders-for-all-laravel-validation-rules)  

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

1. [Why Read/Write Splitting Breaks in Subtle Ways](#why-readwrite-splitting-breaks-in-subtle-ways)
2. [The Baseline Configuration](#the-baseline-configuration)
3. [The sticky Option — What It Actually Does](#the-codestickycode-option-what-it-actually-does)
4. [Forcing a Connection Explicitly](#forcing-a-connection-explicitly)
5. [Connection Pooling Considerations](#connection-pooling-considerations)
6. [Testing Replica Routing](#testing-replica-routing)
7. [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/787/3dbb2870a99a7e4609f817339f391a5e.png) Laravel · 4 min read### Fluent Rule Builders for All Laravel Validation Rules

16 Oct 2026 ](https://msaied.com/articles/fluent-rule-builders-for-all-laravel-validation-rules) [ ![](https://cdn.msaied.com/782/215fd94331f880a70b7bfaf16528790d.png) Composer Pacakge · 4 min read### Laravel URL AI Transformer: Extract Structured Data From URLs

14 Oct 2026 ](https://msaied.com/articles/laravel-url-ai-transformer-extract-structured-data-from-urls) [ ![](https://cdn.msaied.com/784/000a77bb8acd1c5452f67943b3bbc48b.png) Laravel · 4 min read### Add Email to Any Laravel Cloud Environment with the Resend Integration

14 Oct 2026 ](https://msaied.com/articles/add-email-to-any-laravel-cloud-environment-with-the-resend-integration) 

  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)
