AgentStack
SKILL verified MIT Self-run

Laravel Eloquent Performance

skill-kwhorne-elyra-skills-laravel-eloquent-performance · by kwhorne

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.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-kwhorne-elyra-skills-laravel-eloquent-performance

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Laravel Eloquent Performance? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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 by with()
  • Big text/blob columns excluded from list queries
  • exists() not count() > 0; value('col') not first()->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() (not chunk() when mutating rows) or lazyById() / 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             # 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: 

**Baseline:** N queries / X ms → **After:** M queries / Y ms

### Fixes
1.  —  → 

### Indexes added/proposed
- . — for 

### 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 — use chunkById())
  • ❌ 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

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.