Read/Write Splitting &amp; Sticky Reads in Laravel | 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. [  Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel ](#readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel)
2. [  Configuring a Read Replica ](#configuring-a-read-replica)
3. [  What sticky Actually Does ](#what-codestickycode-actually-does)
4. [  Forcing a Connection Explicitly ](#forcing-a-connection-explicitly)
5. [  Layering PgBouncer or ProxySQL ](#layering-pgbouncer-or-proxysql)
6. [  Transactions Always Use the Write Connection ](#transactions-always-use-the-write-connection)
7. [  Monitoring Replication Lag ](#monitoring-replication-lag)
8. [  Takeaways ](#takeaways)

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

  #laravel   #database   #performance   #postgresql   #mysql  

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

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

       Table of contents

1. [  01   Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel  ](#readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel)
2. [  02   Configuring a Read Replica  ](#configuring-a-read-replica)
3. [  03   What sticky Actually Does  ](#what-codestickycode-actually-does)
4. [  04   Forcing a Connection Explicitly  ](#forcing-a-connection-explicitly)
5. [  05   Layering PgBouncer or ProxySQL  ](#layering-pgbouncer-or-proxysql)
6. [  06   Transactions Always Use the Write Connection  ](#transactions-always-use-the-write-connection)
7. [  07   Monitoring Replication Lag  ](#monitoring-replication-lag)
8. [  08   Takeaways  ](#takeaways)

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

Laravel's database layer has first-class support for read/write splitting, but the defaults hide several sharp edges that only appear under production load. This article walks through the configuration mechanics, the sticky-read guarantee, and how to safely introduce a connection pooler without breaking transactions or session state.

### Configuring a Read Replica

The `read` / `write` keys in `config/database.php` let you declare separate hosts while inheriting the rest of the connection config:

```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_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',
],

```

Laravel randomly picks one host from the `read` array per request. All `SELECT` statements that are not inside an explicit transaction route to the read connection; everything else goes to write.

### What `sticky` Actually Does

When `sticky` is `true`, any write performed during the current request causes all subsequent reads **in the same request lifecycle** to be redirected to the write connection. This prevents the classic "I just inserted a row and immediately can't find it" bug caused by replication lag.

The flag is tracked on the `ConnectionInterface` instance held by the `DatabaseManager`. It resets between requests because the manager is resolved fresh from the container on each HTTP request — but **not** in long-lived Octane workers. Under Octane you must reset it manually or accept that a write in request N keeps reads on the primary for the remainder of that worker's life.

```php
// Octane: reset sticky state between requests
app('db')->purge(); // drops both connections and resets sticky flag

```

A lighter alternative is to call `DB::reconnect()` only when you know a write occurred, but `purge()` is safer.

### Forcing a Connection Explicitly

Sometimes you need to bypass the automatic routing — for example, reading a freshly-committed row in a background job that runs milliseconds after the HTTP response:

```php
$user = User::on('mysql::write')->find($id);

```

The `on()` method accepts the connection name. The `::write` suffix is a Laravel convention that resolves to the write PDO instance of the named connection.

### Layering PgBouncer or ProxySQL

Connection poolers sit between your app servers and the database. The key concern is **transaction-mode pooling**: PgBouncer in transaction mode reuses a server connection after each transaction, which means session-level state (prepared statements, `SET LOCAL`, advisory locks) does not survive across queries.

**Disable prepared statements** when using PgBouncer in transaction mode:

```php
// config/database.php — PostgreSQL
'pgsql' => [
    ...
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true,
    ],
],

```

For MySQL behind ProxySQL, set `wait_timeout` on the ProxySQL hostgroup to a value lower than MySQL's own `wait_timeout` to avoid "MySQL server has gone away" errors on idle connections.

### Transactions Always Use the Write Connection

Laravel wraps `DB::transaction()` and `DB::beginTransaction()` in the write connection automatically. Reads inside the transaction callback also go to write, regardless of the `sticky` flag. This is correct behaviour — you need read-your-own-writes consistency inside a transaction.

```php
DB::transaction(function () {
    $order = Order::create([...]);      // write
    $items = $order->items()->get();    // also write — correct
});

```

### Monitoring Replication Lag

No amount of sticky-read configuration helps if your replica is 30 seconds behind. Expose replication lag as a metric (via `SHOW SLAVE STATUS` or `pg_stat_replication`) and alert on it. Consider routing reads to the primary automatically when lag exceeds a threshold using a custom `Connection` resolver.

### Takeaways

- `sticky => true` prevents read-after-write anomalies within a single request but resets per request — not per Octane worker.
- Use `Model::on('mysql::write')` to force primary reads in jobs or event listeners that run close to a write.
- Disable PDO prepared statements (`ATTR_EMULATE_PREPARES`) when PgBouncer runs in transaction mode.
- Transactions always use the write connection; reads inside them do too.
- Monitor replication lag independently — application-level sticky reads cannot compensate for a lagging replica.

 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-2&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-2) 

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

  3 questions  

     Q01  Does `sticky =&gt; true` work correctly under Laravel Octane?        No, not automatically. Octane reuses worker processes across requests, so the sticky flag set by a write in one request persists for the worker's lifetime. Call `app('db')-&gt;purge()` in an Octane request lifecycle hook to reset it between requests. 

      Q02  Why do I get 'prepared statement does not exist' errors with PgBouncer?        PgBouncer in transaction mode does not preserve session state between transactions, so named prepared statements created in one transaction are gone by the next. Set `PDO::ATTR_EMULATE_PREPARES =&gt; true` in your PostgreSQL connection options to use client-side emulation instead. 

      Q03  Can I use different credentials for read and write connections?        Yes. Any key placed inside the `read` or `write` array overrides the top-level value for that connection type. You can specify separate `username`, `password`, or `port` values per role. 

  Continue reading

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

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

 [ ![Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API](https://cdn.msaied.com/297/6eb3a7aaf7148fd21116eea870bd004e.png) filament laravel filament-v4 

### Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

Filament v4 replaces scattered form and infolist definitions with a single Schema API. Learn how unified schem...

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

 26 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-1) [ ![Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware](https://cdn.msaied.com/296/afbac95c7f4aac1cee83eb2c87541369.png) laravel pipeline clean-architecture 

### Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware

The Pipeline pattern in Laravel is far more powerful than middleware alone. Learn how to build typed, composab...

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

 26 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-pipeline-pattern-building-custom-pipelines-beyond-middleware-1) [ ![Laravel Service Container: Contextual Binding, Tagging, and Method Injection](https://cdn.msaied.com/294/e5b9d047bd33c3f8b80069ef6a178884.png) laravel service-container dependency-injection 

### Laravel Service Container: Contextual Binding, Tagging, and Method Injection

Go beyond basic binding. Learn how contextual binding resolves different implementations per consumer, how tag...

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

 26 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-service-container-contextual-binding-tagging-and-method-injection-1) 

   [  ![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)
