Laravel Read/Write Splitting &amp; Sticky Reads | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

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

Menu
----

Close 

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

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

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel

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

 Learn how Laravel's database layer handles read/write splitting, when sticky reads save you from replication lag bugs, and how to pair connection pooling with PgBouncer or ProxySQL for real production throughput.

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

ShareCopy linkCopied

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

  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)

 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()`.

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

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

  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.

   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.

   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.

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

[Mohamed Said](https://www.msaied.com/public#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://www.msaied.com/public#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 articlePause All Laravel Queues During a Deploy with queue:pause --all](https://www.msaied.com/public/articles/pause-all-laravel-queues-during-a-deploy-with-queuepause-all) [Next articleEvent Sourcing in Laravel: Aggregates, Projectors, and Reactors Without the Ceremony](https://www.msaied.com/public/articles/event-sourcing-in-laravel-aggregates-projectors-and-reactors-without-the-ceremony)  

   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)

 ###  Have a technical challenge?

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

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

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

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

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

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

 [Discuss your project ↗](https://www.msaied.com/public#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://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/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://www.msaied.com/public/sitemap.xml)
