Most Laravel applications that struggle under load are not slow because of Laravel. They are slow because of a handful of decisions that were reasonable at a hundred users and are ruinous at a hundred thousand. Here is where the time actually goes, in the order we usually find it.
The N+1 query is still the biggest single cost
Eloquent makes it very easy to write a loop that issues one query per row without ever looking like it does. A listing page that renders fifty records, each touching a relationship in the view, quietly runs fifty one queries. At low traffic nobody notices. At scale it is the difference between a page that responds in 40 milliseconds and one that takes two seconds.
The fix is eager loading, and the way to find them is not reading code. Enable query logging in a staging environment and count queries per request. Anything rendering a list and issuing more than a handful of queries is worth opening. Laravel Debugbar or Telescope will show you this in an afternoon.
The subtler version is eager loading too much. Loading a relationship the view never touches costs a query and the memory to hydrate every model. Load what the page uses, not what it might use.
Hydrating models you are about to throw away
Eloquent builds a full object for every row, with attribute casting, relations and change tracking. That is exactly what you want for a record you are about to modify, and pure overhead for a report you are about to count.
For read-only aggregate work, the query builder returns plain objects and skips all of it. For large exports, chunking keeps memory flat instead of loading a hundred thousand models into an array and hitting the memory limit. These are not micro-optimisations. On a big result set the difference is an order of magnitude.
Cache the expensive thing, not the whole page
Full page caching is the blunt instrument, and it stops being usable the moment the page is personalised. What holds up better is caching the specific expensive fragment: the query that aggregates, the API call to a slow partner, the computed navigation tree.
Two rules make cache safe rather than dangerous. Give every key a deterministic name that includes the inputs, so two different results cannot collide. And decide expiry deliberately: a cache with no expiry is a bug that shows up weeks later as data nobody can explain.
Redis rather than the file driver, once you are on more than one server. File cache on a load balanced pair means two caches that disagree with each other.
Move work out of the request
The user is waiting. Anything that does not affect what they see next does not belong in the request: sending the email, generating the PDF, calling the analytics endpoint, resizing the upload.
Queues are the standard answer and the standard mistake is running them without supervision. A queue worker that has silently died looks exactly like a system where nothing needs doing, right up until someone asks why no email has gone out since Tuesday. Run workers under a supervisor that restarts them, and monitor queue depth as a first class metric.
The database is usually where the ceiling is
Application servers scale sideways easily. The database does not, which is why it becomes the constraint first.
- Index what you filter and sort on, and confirm with EXPLAIN rather than assuming. An index that is not used is disk and write cost for nothing.
- Read replicas take reporting load off the primary. Laravel supports a read and write connection split natively, and the trap is replication lag: a record written and read back immediately may not be there yet.
- Connection limits arrive earlier than people expect. Every PHP worker holds a connection, so the ceiling is workers times servers, not requests.
Measure before you change anything
Every item above is worth doing when it is your bottleneck and worth nothing when it is not. Application performance monitoring on a real environment will tell you which in an hour. Optimising the wrong layer is how a team spends a month on caching when the answer was one missing index.
If you want this looked at on a system that is already live, that is what our web application work covers.