Laravel DB::olap() for Large PlanetScale Queries | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel 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)
- [About](https://www.msaied.com#about)

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

Menu
----

Close 

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

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

 1. [Home](https://www.msaied.com)
2. /
3. [Articles](https://www.msaied.com/articles)
4. /
5. [Laravel](https://www.msaied.com/articles?category=laravel)
6. /
7. Run Large PlanetScale Queries in Laravel With DB::olap()

   [Laravel](https://www.msaied.com/articles?category=laravel) 

 Run Large PlanetScale Queries in Laravel With DB::olap()
=========================================================

 Laravel 13.36 adds DB::olap(), a clean way to switch a PlanetScale MySQL connection to Vitess OLAP mode for the duration of a callback, bypassing the 100,000-row and 64 MiB OLTP limits.

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

ShareCopy linkCopied

 ![Run Large PlanetScale Queries in Laravel With DB::olap()](https://cdn.msaied.com/797/2903a8f469c2bc085780ed09b63d2dd9.png) 

  On this page +1. [The Problem: Vitess OLTP Limits on PlanetScale](#the-problem-vitess-oltp-limits-on-planetscale)
2. [The Old Way: Raw Statements and try/finally](#the-old-way-raw-statements-and-tryfinally)
3. [Using DB::olap()](#using-dbolap)
4. [Queued CSV Export Example](#queued-csv-export-example)
5. [Keep Lazy Queries Inside the Callback](#keep-lazy-queries-inside-the-callback)
6. [Detecting Vitess with isVitess()](#detecting-vitess-with-isvitess)
7. [When Should You Use OLAP Mode?](#when-should-you-use-olap-mode)
8. [Key Takeaways](#key-takeaways)

 The Problem: Vitess OLTP Limits on PlanetScale
----------------------------------------------

PlanetScale runs MySQL on top of Vitess, which enforces strict limits on its default OLTP workload: a single query may return, update, or delete at most **100,000 rows**, and the result set may not exceed **64 MiB**. A reporting query or a CSV export over a large table can hit those limits even though the identical query runs fine on a local MySQL server.

Vitess exposes a second workload — **OLAP** — that removes those constraints. Laravel 13.36 ships `DB::olap()`, a first-class way to use it.

The Old Way: Raw Statements and try/finally
-------------------------------------------

Before Laravel 13.36, switching to OLAP mode required a raw statement and careful cleanup:

```php
try {
    DB::statement("set workload = 'olap'");
    $rows = Order::query()->where('created_at', '>=', now()->subYears(3))->get();
} finally {
    DB::statement("set workload = 'oltp'");
}

```

This approach has two sharp edges. First, the `SET` statement runs on the write connection, so a `SELECT` routed to a read replica still executes in OLTP mode. Second, a missing `finally` block leaves the connection in OLAP mode for the next queued job or the next Octane request.

Using DB::olap()
----------------

Contributed by Jack Bayliss in [PR #61932](https://github.com/laravel/framework/pull/61932), `DB::olap()` wraps the workload switch in a `finally` block internally and sets the workload on **both** the write and read connections:

```php
$revenue = DB::olap(fn () => Order::query()
    ->selectRaw("customer_id, date_format(created_at, '%Y-%m') as month, sum(total) as revenue")
    ->where('created_at', '>=', now()->subYears(3))
    ->groupBy('customer_id', 'month')
    ->get()
);

```

The method returns whatever the callback returns. When the underlying database is not Vitess, `olap()` simply calls the callback and does nothing else — so the same code works against a local MySQL server, in CI, and on PlanetScale.

To target a non-default connection, pass a closure that accepts the connection object:

```php
DB::connection('analytics')->olap(function ($connection) {
    return $connection->table('page_views')
        ->selectRaw('path, count(*) as views')
        ->groupBy('path')
        ->get();
});

```

> **Note:** `olap()` is defined on `MySqlConnection`. Calling it when the default connection is PostgreSQL or SQLite throws a `BadMethodCallException`.

Queued CSV Export Example
-------------------------

A queued job that streams a large export is a natural fit. Keep all querying inside the callback so the connection returns to OLTP mode before the next job runs:

```php
DB::olap(function () use ($stream) {
    Order::query()
        ->select('id', 'customer_id', 'total', 'created_at')
        ->lazyById(5000)
        ->each(fn (Order $order) => fputcsv($stream, [
            $order->id,
            $order->customer_id,
            $order->total,
            $order->created_at,
        ]));
});

```

Each chunk fetched by `lazyById()` runs while the workload is OLAP. Once the callback returns, the worker connection is back in OLTP mode.

### Keep Lazy Queries Inside the Callback

`cursor()`, `lazy()`, and `lazyById()` return a `LazyCollection` that defers its query until iteration. If you return one from the callback and iterate it outside, the query runs after `olap()` has already switched back to OLTP:

```php
// BAD: query runs after olap() has switched back to OLTP
$orders = DB::olap(fn () => Order::query()->cursor());
foreach ($orders as $order) { /* ... */ }

```

Always iterate lazy results **inside** the callback, or use `get()` to materialise them before returning.

Detecting Vitess with isVitess()
--------------------------------

The same PR adds `isVitess()` to `MySqlConnection`. It returns `true` when the server version string contains `Vitess` or `PlanetScale`:

```php
if (DB::connection()->isVitess()) {
    // PlanetScale-specific behaviour
}

```

`olap()` uses this check internally to decide whether to issue the `SET workload` statements.

When Should You Use OLAP Mode?
------------------------------

PlanetScale's own documentation recommends keeping regular application queries in OLTP mode — the row limits exist to protect shared database resources. Good candidates for `DB::olap()` are:

- **Occasional large exports** (CSV, JSON dumps)
- **Data backfills** that touch more than 100,000 rows
- **Admin or analytics reports** that cannot be paginated

For ongoing reporting workloads, PlanetScale suggests copying data to a dedicated analytics database.

Key Takeaways
-------------

- `DB::olap()` is available from **Laravel 13.36** on `MySqlConnection`.
- It sets the OLAP workload on both write and read connections and always restores OLTP in a `finally` block.
- On non-Vitess databases the callback runs unchanged — no extra conditionals needed.
- Lazy collections (`cursor()`, `lazy()`, `lazyById()`) must be iterated **inside** the callback.
- `isVitess()` lets you branch on PlanetScale-specific behaviour without hardcoding version strings.

---

*Source: [Run Large PlanetScale Queries in Laravel With DB::olap() — Laravel News](https://laravel-news.com/laravel-planetscale-olap)*

- [Laravel](https://www.msaied.com/articles?search=Laravel)
- [PlanetScale](https://www.msaied.com/articles?search=PlanetScale)
- [Vitess](https://www.msaied.com/articles?search=Vitess)
- [MySQL](https://www.msaied.com/articles?search=MySQL)
- [Database](https://www.msaied.com/articles?search=Database)
- [Laravel 13](https://www.msaied.com/articles?search=Laravel%2013)

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

  What are the OLTP limits on PlanetScale that DB::olap() helps bypass?PlanetScale's default Vitess OLTP workload limits each query to at most 100,000 rows returned, updated, or deleted, and caps the result set at 64 MiB. DB::olap() switches the connection to the OLAP workload, which removes those limits for the duration of the callback.

   Can I use DB::olap() with a local MySQL server or in CI?Yes. When the underlying database is not Vitess, DB::olap() simply executes the callback without issuing any SET workload statements, so the same code runs unchanged on local MySQL, in CI pipelines, and on PlanetScale.

   Why must lazy collections like cursor() or lazyById() be iterated inside the DB::olap() callback?Lazy collections defer their database query until you iterate them. If you return one from the callback and iterate it outside, the query executes after DB::olap() has already switched the connection back to OLTP mode, meaning it runs under the 100,000-row limit again.

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

[Mohamed Said](https://www.msaied.com#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#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 articleEloquent Query Optimization: Defeating N+1 Problems at the Source](https://www.msaied.com/articles/eloquent-query-optimization-defeating-n1-problems-at-the-source) [Next articleEloquent Custom Casts: Encrypting, Compressing, and Composing Value Objects at the Model Layer](https://www.msaied.com/articles/eloquent-custom-casts-encrypting-compressing-and-composing-value-objects-at-the-model-layer)  

   On this page
-------------

1. [The Problem: Vitess OLTP Limits on PlanetScale](#the-problem-vitess-oltp-limits-on-planetscale)
2. [The Old Way: Raw Statements and try/finally](#the-old-way-raw-statements-and-tryfinally)
3. [Using DB::olap()](#using-dbolap)
4. [Queued CSV Export Example](#queued-csv-export-example)
5. [Keep Lazy Queries Inside the Callback](#keep-lazy-queries-inside-the-callback)
6. [Detecting Vitess with isVitess()](#detecting-vitess-with-isvitess)
7. [When Should You Use OLAP Mode?](#when-should-you-use-olap-mode)
8. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

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

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

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

 [ ![](https://cdn.msaied.com/796/13df3306c87eeeccf69b380e7a07d8da.png)  · 3 min read### Eloquent Custom Casts: Encrypting, Compressing, and Composing Value Objects at the Model Layer

19 Oct 2026 ](https://www.msaied.com/articles/eloquent-custom-casts-encrypting-compressing-and-composing-value-objects-at-the-model-layer) [ ![](https://cdn.msaied.com/795/9d9dd20c259ecc6356a91021a872695c.png)  · 3 min read### Eloquent Query Optimization: Defeating N+1 Problems at the Source

18 Oct 2026 ](https://www.msaied.com/articles/eloquent-query-optimization-defeating-n1-problems-at-the-source) [ ![](https://cdn.msaied.com/794/ceed213d17627e0cfd9535e4ae909775.png)  · 3 min read### Filament v4 Custom Table Columns: Building Reusable, Typed Column Components

18 Oct 2026 ](https://www.msaied.com/articles/filament-v4-custom-table-columns-building-reusable-typed-column-components) 

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

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

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