Search Documentation

Search across all documentation pages

Getting started

Limits and performance

The default limits that keep every page fast on tables with millions of rows, and the speed budgets Lathe is held to.

Lathe is built to stay fast on a table with 10 million rows. It does that with limits: a list never counts every row, never shows every row, and never runs a large bulk action inside one request. Each limit is a key in config/lathe.php, and a Resource can set its own value in its definition.

Default limits

Every limit is enforced on the server, not only in the browser.

Limit Default Config key Per Resource
Row count Counts at most 10,001 rows and shows "10,000+". Pages past row 10,000 answer 422. limits.count_cap countStrategy()
Pagination type Numbered pages pagination.type paginationType()
Page size 25, with 25, 50 and 100 to choose from. At most 100. There is no "show all". pagination.per_page, pagination.per_page_options, limits.max_per_page perPage()
Relation picker 25 results, searched on the server limits.picker_results pickerResults()
Search Matches the start of a column (term%); "contains" is opt-in per column. At most 100 characters and 8 words. limits.search_term_chars, limits.search_term_words searchable(), searchTermChars(), searchTermWords()
Filter options More than 100 options load as you type limits.filter_remote_threshold filterRemoteThreshold()
Bulk actions 500 rows or fewer run at once; more run as a queued batch with progress and cancel limits.bulk_inline_max bulkInlineMax()
Ids in one request 1,000 limits.id_list_max idListMax()
Widget values Cached 60 seconds, flushed when the table is written limits.metrics_ttl cacheFor() on the widget
Global search 5 results per Resource global_search.results globalSearchResults()
Saved tabs 10 per user and Resource saved_tabs.max_per_resource none
Uploads and imports 10 MB per file, 5 MB per image, 50 MB per import uploads.max_file_size_mb, uploads.max_image_size_mb, imports.max_file_size_mb maxSize() on the field
Export and import chunks 500 rows exports.chunk_size, imports.chunk_size none

countStrategy() takes a Lathe\Query\CountStrategy: Capped (the default), Exact, Estimated (the database's own estimate) or None (no total, only next and previous). Use Exact only on a table you know stays small.

use Lathe\Query\CountStrategy;
use Lathe\Resources\ResourceDefinition;

protected function configure(ResourceDefinition $definition): ResourceDefinition
{
    return $definition
        ->model(\App\Models\Order::class)
        ->countStrategy(CountStrategy::Capped, 50_000)
        ->bulkInlineMax(200);
}

Speed budgets

Lathe's CI fails a change that breaks one of these budgets. They are measured on a table of 10 million orders and with 500 Resources registered.

Budget Limit
List page, server time at the 95th percentile 50 ms, with the same number of queries for 10 rows as for 100
Detail and edit page, server time at the 95th percentile 30 ms
Lathe's cost per request with 500 Resources 5 ms over a plain Laravel route
JavaScript for the first page load 200 KB gzipped
Rendering a table of 100 rows and 20 columns 50 ms in desktop Chrome
Interaction to next paint on a mid-range phone 200 ms
Any of the above compared with the last release at most 20% worse

The public benchmark against Filament runs the same six list, search, filter, detail and edit requests on both panels with the same data. Its results and how to run it again are at github.com/lathephp/benchmarks.

Keeping your own Resources fast

  • Sort and search on indexed columns. A search that "contains" a term cannot use an index; lathe:doctor warns about it on large tables.
  • Keep relation columns on the index to belongsTo fields; Lathe loads them in one query per relation.
  • Run a queue worker in production, so large bulk actions, exports and imports run in the background (see Deployment).

What you see

Open a list on a table with more than 10,000 rows: the footer reads "Showing 1 to 25 of 10,000+", and the page answers as fast as it does on a small table.