Laravel Read/Write Splitting &amp; Sticky Reads | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel        On this page       1. [  Why Read/Write Splitting Breaks More Apps Than It Fixes ](#why-readwrite-splitting-breaks-more-apps-than-it-fixes)
2. [  Configuring Read/Write Connections ](#configuring-readwrite-connections)
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: PgBouncer and ProxySQL ](#connection-pooling-pgbouncer-and-proxysql)
6. [  Takeaways ](#takeaways)

  ![Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel](https://cdn.msaied.com/543/97ef3abac42d00989679f44916e2efd5.png)

  #laravel   #database   #postgresql   #mysql   #performance  

 Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel 
=======================================================================

     13 Aug 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Read/Write Splitting Breaks More Apps Than It Fixes  ](#why-readwrite-splitting-breaks-more-apps-than-it-fixes)
2. [  02   Configuring Read/Write Connections  ](#configuring-readwrite-connections)
3. [  03   The sticky Option: What It Actually Does  ](#the-codestickycode-option-what-it-actually-does)
4. [  04   Forcing a Connection Explicitly  ](#forcing-a-connection-explicitly)
5. [  05   Connection Pooling: PgBouncer and ProxySQL  ](#connection-pooling-pgbouncer-and-proxysql)
6. [  06   Takeaways  ](#takeaways)

 Why Read/Write Splitting Breaks More Apps Than It Fixes
-------------------------------------------------------

Adding a read replica feels like a free performance win. In practice, replication lag — even 50 ms — causes subtle, hard-to-reproduce bugs: a user creates a record, gets redirected, and the next page query hits the replica before the write has propagated. Laravel ships with first-class support for read/write connections, but the defaults are more dangerous than most engineers realise.

---

Configuring Read/Write Connections
----------------------------------

Laravel's `config/database.php` accepts `read` and `write` keys inside any connection. The driver merges them with the top-level config, so you only override what differs:

```php
'mysql' => [
    'driver' => 'mysql',
    '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' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
],

```

When multiple hosts are listed under `read`, Laravel picks one at random per request — a simple but effective load-distribution strategy.

---

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

With `'sticky' => true`, Laravel tracks whether a write query has been executed during the current request lifecycle. If it has, **all subsequent reads for that request are routed to the write connection**, bypassing the replica entirely.

This is implemented in `Illuminate\Database\Connection` via a simple boolean flag:

```php
// Simplified from the framework source
if ($this->recordsHaveBeenModified() && $this->getConfig('sticky')) {
    return $this->getWritePdo();
}
return $this->getReadPdo();

```

The flag is reset at the start of each request via the `DatabaseServiceProvider`, so there is no cross-request leakage in FPM. Under **Laravel Octane**, however, the connection object is reused across requests — you must call `DB::resetRecordsModified()` in an `octane:request` listener or use a middleware:

```php
// app/Http/Middleware/ResetDbStickyFlag.php
public function handle(Request $request, Closure $next): Response
{
    DB::connection()->resetRecordsModified();
    return $next($request);
}

```

Register it early in the global middleware stack.

---

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

Sometimes you need deterministic routing regardless of sticky state — for example, an admin dashboard that must always read fresh data:

```php
$users = DB::connection('mysql::write')
    ->table('users')
    ->where('active', true)
    ->get();

```

Or with Eloquent:

```php
User::on('mysql::write')->where('active', true)->get();
// Alternatively, use the useWritePdo() scope:
User::query()->useWritePdo()->where('active', true)->get();

```

`useWritePdo()` is available on the query builder directly and is the cleanest option for one-off overrides.

---

Connection Pooling: PgBouncer and ProxySQL
------------------------------------------

Laravel opens a new PDO connection per worker process. Under FPM with 50 workers × 4 app servers, you can exhaust PostgreSQL's `max_connections` (default 100) instantly.

**PgBouncer** (PostgreSQL) in `transaction` pooling mode is the standard solution. Each query borrows a server connection for its duration, then returns it to the pool. Your Laravel `DB_HOST` points to PgBouncer, not Postgres directly.

Key caveats with PgBouncer transaction mode:

- `SET` statements and advisory locks are **not safe** — they do not persist across queries.
- Prepared statements require `server_reset_query` or disabling them in Laravel: set `'options' => [PDO::ATTR_EMULATE_PREPARES => true]` in your connection config.

**ProxySQL** serves the same role for MySQL/MariaDB and additionally supports query routing rules — you can route `SELECT` statements to replicas and writes to the primary at the proxy layer, removing that concern from application config entirely.

---

Takeaways
---------

- Enable `sticky` on every read/write split config — replication lag bugs are silent and costly.
- Under Octane, reset the modified flag explicitly; FPM handles it automatically.
- Use `useWritePdo()` for admin or post-write reads that must be consistent.
- Point Laravel at PgBouncer/ProxySQL rather than the database directly to avoid connection exhaustion.
- Disable PDO prepared statements when using PgBouncer in transaction pooling mode.
- Test replica routing in CI by asserting which connection a query targets using `DB::listen()`.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Freadwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-6&text=Read%2FWrite+Splitting%2C+Connection+Pooling%2C+and+Sticky+Reads+in+Laravel) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Freadwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-6) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does Laravel's sticky option work across multiple requests?        No. Under PHP-FPM the sticky flag is reset at the start of each request. Under Octane, connections are reused, so you must reset it manually with DB::resetRecordsModified() in a middleware or Octane request listener. 

      Q02  Can I use PgBouncer in transaction pooling mode with Laravel's default PDO settings?        Not safely. Transaction pooling does not preserve prepared statement state between queries. Set PDO::ATTR_EMULATE_PREPARES to true in your connection options, or switch PgBouncer to session pooling if you need native prepared statements. 

      Q03  When should I route reads at the proxy layer (ProxySQL) vs. in Laravel config?        Proxy-layer routing is better when you have multiple applications sharing the same database cluster, or when you want to change routing rules without deploying application code. Laravel-level config is simpler for single-app setups and gives you fine-grained per-query control. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Octane Worker Lifecycle, State Leakage, and Memory Management in Production](https://cdn.msaied.com/554/8cc265358b47e59601a66d1e247eba9a.png) laravel octane performance 

### Octane Worker Lifecycle, State Leakage, and Memory Management in Production

Laravel Octane keeps workers alive across requests, which means static state, resolved singletons, and stale d...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/octane-worker-lifecycle-state-leakage-and-memory-management-in-production-2) [ ![Job Batching with Laravel Horizon: Reliable Async Workflows at Scale](https://cdn.msaied.com/553/b794b736bfd84f3cbcc6218319916544.png) laravel queues horizon 

### Job Batching with Laravel Horizon: Reliable Async Workflows at Scale

Learn how to combine Laravel's job batching API with Horizon's queue supervision to build fault-tolerant async...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 15 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/job-batching-with-laravel-horizon-reliable-async-workflows-at-scale) [ ![Contextual Eloquent Casts: Custom Cast Classes, Value Objects, and Inbound-Only Transforms](https://cdn.msaied.com/552/a7825c0c6f53d934f84fce522573eafb.png) laravel eloquent value-objects 

### Contextual Eloquent Casts: Custom Cast Classes, Value Objects, and Inbound-Only Transforms

Go beyond primitive casts. Learn how to build custom Eloquent cast classes that hydrate value objects, handle...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 15 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/contextual-eloquent-casts-custom-cast-classes-value-objects-and-inbound-only-transforms) 

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

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
