Reporting screens that timed out under real data volume

Report pages that were fine on seed data timed out against production-sized tables, and the queue was being used for almost nothing.

Engagement

Role: Performance sprint, founder-led.

Shape: Profile, fix the measured hot paths, document the method.

Duration: 1–2 weeks.

Stack: Laravel 10 · MySQL · Redis · Laravel Horizon · Clockwork · Laravel Telescope.

What was wrong

  • Report endpoints that were fast on seed data timed out against production-sized tables.
  • Collections were filtered in PHP after loading full result sets from the database.
  • Relationship access inside loops produced N+1 query patterns on the slowest pages.
  • Exports ran inside the web request, so a large export blocked a worker until the timeout.
  • The queue was configured but almost unused, so nothing heavy ran in the background.

What was built

  1. Query profiling to find N+1 access patterns and unindexed columns behind the slowest endpoints.
  2. Eager loading, targeted indexes and paginated queries in place of collection-level filtering in PHP.
  3. Heavy exports moved to queued jobs with progress feedback instead of blocking the request.
  4. Cache layer for the read-heavy aggregates, with explicit invalidation on write.

Architecture after the work

Profile before changing anything — Query profiling identified the slowest endpoints and the exact statements responsible, so the work targeted measured problems instead of assumptions.

Query-level fixes — Eager loading, targeted composite indexes and paginated database queries replaced in-PHP filtering.

Queued exports — Heavy exports moved to queued jobs with progress feedback and a download link on completion.

Cache with explicit invalidation — Read-heavy aggregates cached with invalidation on write, so figures never go stale silently.

Trade-offs and what was left out

  • No published percentages: before-and-after timings are shared with the client in the report, but they depend on their data and hardware and cannot be verified by a reader here.
  • Denormalised reporting tables were proposed but left out of scope once indexing and queueing made the pages usable.

Outcome

Report generation runs on the queue with the slow query paths indexed and profiled, and the profiling method is documented so the team can repeat it.

Frequently asked questions

Why is my Laravel app slow only in production?
Almost always data volume plus query shape. Patterns that are invisible on a few hundred seeded rows — N+1 relationship access, missing indexes, filtering collections in PHP — become the dominant cost against production-sized tables. Profiling the real workload is the only way to find out which one applies.
Do you publish before-and-after performance numbers?
In the client report, yes. Not on this website: a percentage that a reader cannot independently verify is marketing, not evidence, so it is deliberately left out of the public write-up.

Related services