# Performance forensics

Applies when: an application got slow, and the honest answer to "why" is that nobody knows.

## Count first, theorise second

The instinct is to read the code and form a theory. Resist it. Every slow-application
disaster I have been called into had someone acting on a plausible theory that was wrong.

Count the queries on the slow request. Not "roughly" — exactly. On one legacy rebuild a
single page was issuing **3,982 queries per request**. Nobody guessed that. Nobody could
have. It was found by counting, and once counted the fix was obvious.

Get a number before you touch anything. The number is also your baseline: without it you
cannot prove the work paid for itself, and "it feels faster" is not a result.

## The N+1 you find is rarely the N+1 that matters

Everyone knows about `with()`. Fewer people know that:

- **Eager loading can make it worse.** `with()` on a hasMany that returns 50k rows loads
  50k rows into memory to render ten of them. Check what the relation actually returns.
- **The N+1 is often in the view**, not the controller. An accessor that lazily loads a
  relation inside a `@foreach` is invisible until you count.
- **Nested N+1 compounds.** A loop over 60 rows, each triggering a relation that itself
  triggers a relation, is 3,600 queries from ten lines of clean-looking Blade.

## Order of payoff

Work in this order. It is roughly cheapest-fix-first, and most projects never need step 4:

1. **Query count** — N+1s, lazy loads in views, accessors that hit the database.
2. **Indexes** — the missing one is usually composite, and usually matches your most
   common `where` + `order by` together, in that column order.
3. **Cache** — but only what is expensive to compute and safe to be stale. Caching to hide
   a query problem means you now have two problems.
4. **Architecture** — read replicas, queues, denormalisation. Genuinely necessary sometimes,
   and almost never the first answer despite being the most fun.

## Infrastructure spend is usually a query problem

When the bill climbs, the instinct is a bigger server. Sometimes that is right. More often
the database is doing thousands of times more work than the feature requires, and you are
paying to run inefficiency faster.

Check the queries before you check the pricing page.

## The queue backlog trap

A queue that grows faster than it drains is not fixed by adding workers. Adding workers to
a throughput problem buys you a more expensive version of the same backlog.

Find what each job is actually doing — usually a query problem wearing a queue costume.

## What to hand over

If you cannot say "this page went from X to Y, measured over Z", you have not finished. A
performance engagement that ends in adjectives ended without evidence.

---
MIT licensed. Written by Smit Desai — <https://laravel.org.in>
