Laravel eloquent performance
Skill kwhorne/elyra-skills/skills/laravel-eloquent-performance
Find and fix Eloquent performance problems - N+1 queries, eager loading strategy, chunking, indexes, and query review for Laravel apps. Use when a Laravel page or job is slow, when reviewing Eloquent code for query efficiency, when the user mentions N+1 problems, or before shipping list views and reports that touch large tables.From its SKILL.md
npx -y skills add kwhorne/elyra-skills --skill laravel-eloquent-performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
4.5 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it
Laravel Eloquent Performance
Eloquent makes queries invisible — which is how one Blade loop quietly becomes 300 of them. Make queries visible first, then fix the worst offender, then prevent recurrence.
When to use
- A Laravel page, endpoint, or job is slow
- Reviewing code that loops over models or renders collections
- "We have an N+1 problem" / preparing list views, exports, dashboards
- Before shipping anything that touches large tables
Principles
- Measure before touching. Count queries and duration; don't guess from code shape.
- The database does sets; PHP does rows. Filtering, counting, and aggregating in a
->filter()/->count()after->get()is the cardinal sin. - Load what the view needs, nothing more. Eager load relations the page renders; select the columns it shows.
- Prevention beats heroics. One strict-mode line catches future N+1s in development forever.
Process
1. Make queries visible
// AppServiceProvider::boot() — development
Model::shouldBeStrict(! app()->isProduction()); // throws on lazy loading, silent attribute access
DB::listen(fn ($q) => logger()->debug($q->sql, ['ms' => $q->time]));
Or use Debugbar/Telescope/->dd() on the query log for the specific request. Record: query count + total ms as the baseline.
2. Fix N+1 (the usual suspect)
// Symptom: query per row in a loop
$posts = Post::with(['author', 'comments.user'])->get(); // eager load
$posts = Post::withCount('comments')->get(); // counts without loading
$query->withAvg('reviews', 'rating'); // aggregates likewise
- Nested relations:
with('comments.user'), constrained:with(['comments' => fn ($q) => $q->latest()->limit(5)]) - In Blade components rendered per-row: the N+1 hides in the component — eager load in the parent query
3. Stop over-fetching
->select(['id', 'title', 'author_id'])— include FKs needed bywith()- Big text/blob columns excluded from list queries
exists()notcount() > 0;value('col')notfirst()->col
4. Move work to the database
| PHP smell | Database fix |
|---|---|
->get()->filter(...) | ->where(...) |
->get()->count() | ->count() |
->get()->sum('x') | ->sum('x') |
->get()->groupBy(...) | ->groupBy() + aggregate, or a dedicated query |
5. Handle large datasets
- Iteration:
chunkById()(notchunk()when mutating rows) orlazyById()/cursor() - Exports/jobs: never
all()on an unbounded table - Pagination:
paginate()for UI;cursorPaginate()for infinite scroll / large offsets
6. Check indexes
php artisan db:table <table> # see existing indexes
- Every column in
where/orderBy/joins on big tables: candidate - Composite index order: equality columns first, then range/sort
- Verify with
DB::select('EXPLAIN ...')— look for full table scans
7. Re-measure and lock in
- Compare query count + ms against baseline; report both
- Keep
Model::shouldBeStrict()in non-production permanently - Add a CI-friendly assertion where it matters: e.g. test that a page renders in ≤ N queries
Output format
## Eloquent performance: <page/endpoint/job>
**Baseline:** N queries / X ms → **After:** M queries / Y ms
### Fixes
1. <file:line> — <problem> → <fix>
### Indexes added/proposed
- <table>.<cols> — for <query>
### Prevention
- strict mode: on | query-count test: added | …
Anti-patterns
- ❌ Sprinkling
with()everywhere "just in case" — eager loading unrendered relations is its own waste - ❌ Caching as the first response to a slow query instead of fixing the query
- ❌
chunk()while updating the column you chunk by (rows get skipped — usechunkById()) - ❌ Fixing the N+1 in the controller while the Blade component re-introduces it
- ❌ Adding indexes without checking they're used (
EXPLAIN) - ❌ Declaring victory without before/after numbers
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.