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

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

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

  [ home ](https://msaied.com)    [ articles ](https://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. [  How Laravel Splits Reads and Writes ](#how-laravel-splits-reads-and-writes)
3. [  The sticky Option and Why It Matters ](#the-codestickycode-option-and-why-it-matters)
4. [  Forcing a Connection Explicitly ](#forcing-a-connection-explicitly)
5. [  Connection Pooling: PgBouncer and ProxySQL ](#connection-pooling-pgbouncer-and-proxysql)
6. [  Detecting Replica Lag in Application Code ](#detecting-replica-lag-in-application-code)
7. [  Takeaways ](#takeaways)

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

  #laravel   #database   #performance   #postgresql   #mysql  

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

     2 Aug 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   How Laravel Splits Reads and Writes  ](#how-laravel-splits-reads-and-writes)
3. [  03   The sticky Option and Why It Matters  ](#the-codestickycode-option-and-why-it-matters)
4. [  04   Forcing a Connection Explicitly  ](#forcing-a-connection-explicitly)
5. [  05   Connection Pooling: PgBouncer and ProxySQL  ](#connection-pooling-pgbouncer-and-proxysql)
6. [  06   Detecting Replica Lag in Application Code  ](#detecting-replica-lag-in-application-code)
7. [  07   Takeaways  ](#takeaways)

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

Scaling a Laravel application's database layer is rarely about raw query optimisation alone. At some point you add a read replica, and suddenly a whole class of subtle bugs appears: a user creates a record, gets redirected, and sees an empty list because the replica hasn't caught up yet. Understanding how Laravel's database manager handles this — and where it can silently betray you — is essential before you put a replica in front of real traffic.

### How Laravel Splits Reads and Writes

Laravel's `DatabaseManager` accepts a `read` / `write` key inside any connection config. Each key accepts an array of hosts, and Laravel picks one at random per request.

```php
// config/database.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'),
    'password' => env('DB_PASSWORD'),
    'charset' => 'utf8',
    'prefix' => '',
    'schema' => 'public',
],

```

Every `SELECT` goes to a read host; every `INSERT`, `UPDATE`, `DELETE`, and DDL statement goes to the write host. The split happens inside `Illuminate\Database\Connection::select()` and `statement()` — not at the Eloquent layer, so raw queries respect it too.

### The `sticky` Option and Why It Matters

When `sticky` is `true`, Laravel records that a write occurred during the current request. Any subsequent read in the **same request lifecycle** is then routed to the write connection instead of a replica.

```php
// Internally, Connection tracks this flag:
protected $recordsModified = false;

// After any write:
$this->recordsModified = true;

// On the next select, if sticky and recordsModified:
return $this->getWritePdo();

```

This prevents the classic post-write stale-read bug within a single HTTP request. It does **not** help across requests (e.g., a redirect after a form POST) — for that you need application-level logic or a short-circuit cache.

### Forcing a Connection Explicitly

Sometimes you need deterministic routing regardless of `sticky`:

```php
// Force read replica
$users = DB::connection('pgsql')->table('users')
    ->useReadPdo()
    ->get();

// Force primary
$user = User::on('pgsql')->find($id); // always write host

// Or via the query builder:
DB::table('orders')->useWritePdo()->where('id', $id)->first();

```

`useReadPdo()` and `useWritePdo()` are available on the query builder and let you override sticky behaviour for a specific query.

### Connection Pooling: PgBouncer and ProxySQL

Laravel opens a new PDO connection per worker process (or per request under FPM). At scale this exhausts `max_connections` on PostgreSQL quickly. PgBouncer in **transaction pooling** mode is the standard fix for Postgres.

**Critical caveat:** transaction pooling breaks any feature that relies on session-level state — `SET LOCAL`, advisory locks, `pg_temp` tables, and `PREPARE` statements. Laravel's `DB::transaction()` is safe because PgBouncer keeps the same backend connection for the duration of a transaction block. Named prepared statements are **not** safe; disable them:

```php
// config/database.php — disable server-side prepared statements
'pgsql' => [
    // ...
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true,
    ],
],

```

For MySQL, ProxySQL can split reads and writes at the proxy layer based on query rules, which means you can point Laravel at a single host and let ProxySQL handle routing. This simplifies config but adds an infrastructure dependency.

### Detecting Replica Lag in Application Code

If your workload is write-heavy and replica lag is unpredictable, consider a health-check approach:

```php
// A simple lag guard using a heartbeat table
public function replicaIsAcceptable(): bool
{
    $lag = DB::connection('pgsql')
        ->table('replication_heartbeat')
        ->useReadPdo()
        ->value('lag_seconds');

    return $lag !== null && $lag < 2;
}

```

Update `replication_heartbeat` from a scheduled job every second on the primary. If lag exceeds your threshold, route critical reads to the write host for that request.

### Takeaways

- Laravel's `read`/`write` config splits connections automatically; `sticky` prevents stale reads within a single request but not across requests.
- Use `useReadPdo()` / `useWritePdo()` for explicit per-query routing.
- PgBouncer transaction pooling requires `PDO::ATTR_EMULATE_PREPARES => true` to avoid named prepared statement errors.
- ProxySQL can absorb the routing logic entirely, but adds a network hop and operational complexity.
- Monitor replica lag actively; don't assume `sticky` is a complete solution for post-redirect reads.

 Found this useful?

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

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

  3 questions  

     Q01  Does Laravel's `sticky` option protect against stale reads after a redirect?        No. `sticky` only routes reads to the write host within the same request lifecycle. After a redirect, a new request starts with a clean state and may hit a replica. You need a short-circuit cache, a heartbeat check, or explicit `useWritePdo()` calls for post-redirect reads on critical data. 

      Q02  Why do I get 'prepared statement already exists' errors with PgBouncer?        PgBouncer in transaction pooling mode reuses backend connections across clients, so named prepared statements created by one client can conflict with another. Set `PDO::ATTR_EMULATE_PREPARES =&gt; true` in your connection options to make PDO use client-side parameter interpolation instead of server-side prepared statements. 

      Q03  Can I use multiple read replicas with different weights in Laravel?        Laravel picks a read host uniformly at random from the `read.host` array. There is no built-in weighting. For weighted routing you need a proxy layer (ProxySQL, HAProxy) in front of Laravel, or a custom `DatabaseManager` binding that overrides `getReadPdo()` with your own selection logic. 

  Continue reading

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

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

 [ ![PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents](https://cdn.msaied.com/505/151a0bba66cc27064e090e69e55d7c92.png) PhpStorm JetBrains PHP 8.5 

### PhpStorm 2026.2 Released: Laravel Tool Window, PHP 8.5 Pipe Operator, and AI Agents

PhpStorm 2026.2 ships a dedicated Laravel tool window with Artisan, error logs, and Laravel Cloud tabs, plus P...

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

 3 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/phpstorm-20262-released-laravel-tool-window-php-85-pipe-operator-and-ai-agents) [ ![Laravel Doctor: Diagnose Your Laravel App With One Artisan Command](https://cdn.msaied.com/504/d72224689abc7b396bce187535008272.png) Laravel Artisan Health Checks 

### Laravel Doctor: Diagnose Your Laravel App With One Artisan Command

Laravel Doctor is a first-party package announced at Laracon US 2026 that adds an `artisan doctor` command to...

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

 3 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-doctor-diagnose-your-laravel-app-with-one-artisan-command) [ ![Livewire v4.3.5 Released: Fix for SFC Detection with PHP Attribute Array Arguments](https://cdn.msaied.com/503/9678ed8dbf5d7a6f4f19ca7694cf241b.png) Livewire Laravel PHP 

### Livewire v4.3.5 Released: Fix for SFC Detection with PHP Attribute Array Arguments

Livewire v4.3.5 ships a targeted bug fix for Single File Component (SFC) detection when PHP attributes contain...

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

 3 Aug 2026     2 min read  

  Read    

 ](https://msaied.com/articles/livewire-v435-released-fix-for-sfc-detection-with-php-attribute-array-arguments) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
