Systematic Laravel performance analysis instead of blind optimisation

From user impact and tracing to SQL, queues and load tests: a dependable order for performance engineering.

When a Laravel application is slow, the first question is not which cache is missing. Establish which user workflow is slow, how often it happens and where time is actually spent. Without measurement, a team can optimise a visible but insignificant part of the system.

Translate user impact into a technical measurement

“The application is slow” may mean high server response time, a blocked queue, an oversized browser payload, a slow provider API or a search that degrades for one filter combination. A useful problem statement names the route or job, data volume, role, time and expected duration.

For web requests, p50, p95 and p99 are more informative than an average that hides individual slow responses. For jobs, measure throughput, waiting time, execution time and failure rate. A business signal such as import duration or workflow completion rate prevents optimisation without product value.

Observe before changing

A useful assessment combines:

  • request and job tracing,
  • slow SQL queries and query plans,
  • database query counts,
  • CPU, memory and I/O,
  • queue depth and external latency,
  • failures and retries.

Debug tools are useful locally but must not be exposed in production. Production telemetry and logs should be filtered so passwords, tokens and sensitive business data are not recorded.

Review database access by impact

Eloquent is rarely the problem by itself. Unclear access patterns become expensive. N+1 behaviour runs more queries for every result. Missing indexes force large scans. Unlimited result sets consume memory and network time. Complex aggregations are calculated on every request.

Use this order:

  1. capture the slow query with realistic parameters,
  2. inspect the database execution plan,
  3. assess selectivity and order of possible indexes,
  4. limit selected columns and relationships,
  5. measure the result and write cost again.

An index is not free. It occupies storage and increases write cost, so it should not be added on intuition alone.

Move appropriate work out of the request

Email, image processing, exports and external synchronisation often do not need to finish inside the user request. Queues improve perceived speed where the workflow can be asynchronous.

The job then needs its own quality controls:

  • idempotent behaviour on retry,
  • limited attempts and useful backoff,
  • explicit error states,
  • monitoring for queue depth and failed jobs,
  • protection from parallel work on the same object.

A request does not become reliable merely because its slow work is now invisible in the background.

Treat cache as a contract

Caching helps with expensive, frequently read and sufficiently stable information. Before implementation, define key, tenant boundary, lifetime and invalidation. A cache that mixes data across roles or tenants is a security issue.

Question Why it matters
What is the key? It must include all relevant business dimensions.
Who may see the value? User and tenant boundaries must remain intact.
When is it stale? TTL and invalidation need a product reason.
What happens on failure? The source remains correct or fails safely.

Build load tests around the hypothesis

A load test that requests random homepages proves little. It must represent the problem workflow, realistic data distribution and competing actions. A search needs common and rare filters. An import needs representative file size, concurrency and downstream jobs.

Compare the same measurements before and after each change. Faster responses with a higher failure rate are not an improvement.

A measurement record prevents success by intuition

Each optimisation needs only a short, versioned record. It forces the team to separate cause from effect:

Field Example
User problem invoice overview takes more than 3 seconds for large tenants
Reproducible load 50,000 invoices, accounting role, open filter
Baseline p95 3.4 s, 142 SQL queries, 1.1% errors
Hypothesis one relationship creates N+1 queries
Single change targeted eager loading for the three displayed fields
Result measure p95, query count, memory and errors again
Decision adopt, revert or test the next hypothesis

The measurement uses the same dataset and concurrency. It also checks at least one business property, such as correct tenant filtering or complete results. A faster query that returns the wrong records is a regression.

If the application is not yet understood well enough, complete the structured project takeover before optimisation. When a cache touches tenant boundaries, tenant isolation and authorisation provides the necessary negative tests.

Sources and further reading

Conclusion

Performance engineering is a sequence of testable hypotheses. Define user impact, break down runtime, prove the bottleneck, make a small change and measure again. That turns “Laravel is slow” into a technical decision with visible business value.

Facing a similar decision in your project?

Describe the context. I will assess the technical options, risks and a useful next step.

Discuss the project question ↗