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 manager handles read/write connections, where sticky reads prevent stale data after writes, and how to layer PgBouncer or ProxySQL without breaking your application.

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

ShareCopy linkCopied

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

  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
---------------------------------------------------------------------

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.

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

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

  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.

   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.

   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.

   ![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 articleLaravel Service Container: Contextual Binding, Tagging, and Method Injection](https://www.msaied.com/public/articles/laravel-service-container-contextual-binding-tagging-and-method-injection-3) [Next articleLaravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware](https://www.msaied.com/public/articles/laravel-pipeline-pattern-building-custom-pipelines-beyond-middleware-2)  

   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)

 ###  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)
