All Resources
Feature Spotlight 9 min read May 20, 2026

The 13-Second Schedule: What We Found When We Profiled Our Slowest Page

The calendar took 13 seconds to load on the biggest fleet in our system. Every morning, at 8 AM, the dispatcher waited. Here's exactly what we found, what we fixed, and how fast it runs now.

The calendar took 13 seconds to load on the biggest fleet in our system. Every morning, at 8 AM, the dispatcher sat and waited. Then they clicked to the next month. Another 13 seconds. They did this five or six times a day.

Google’s web.dev guidance targets a Largest Contentful Paint of 2.5 seconds or less, measured at the 75th percentile. The classic human-factors basis goes back further: Robert B. Miller’s 1968 paper on “Response Time in Man-Computer Conversational Transactions” established that a wait longer than 2 seconds breaks the user’s concentration. We were 5x over the modern threshold and 6x over the human-factors threshold.

Here’s how we fixed it.


How We Profiled It

Server timing broke down like this:

  • 9 seconds in the database
  • 2 seconds in serialization
  • 1.5 seconds on the wire
  • 0.5 seconds in render

The database was the obvious target. But why?


The Bug

A single N+1 query. For every job on the month view, we fetched the associated workorders one row at a time. On a typical mid-sized fleet, the month view shows ~150 jobs. Each job averages ~2.5 workorders. That’s 375 individual queries to render one page.

On the largest fleet — 300+ jobs in a month — it was ~1,200 queries to load one calendar view. Every. Single. Time.


Fix #1: Eager-Load the Workorders

Instead of fetching workorders one-at-a-time per job, we loaded them all in a single batch query and joined them in memory.

Result: 13 seconds → 4 seconds. The database time dropped from 9 seconds to 2.5 seconds. The serialization cost went up slightly (more data in memory), but the net effect was a 3x improvement.

This was a one-line ORM change: .includes(:workorders) instead of letting the serializer call .workorders in a loop.


Fix #2: Materialize the “Is This Day Fully Booked?” Calculation

The month view shows a visual indicator for each day: is the day fully booked, partially booked, or available? That calculation was happening at read time — the server iterated over every job on every day, summed the truck-hours, compared them to the fleet capacity, and produced a boolean per day.

For a 30-day month with 300 jobs, that’s 300 comparisons per page load. Most of them return the same result day after day — the only time a day’s booked status changes is when a job is created, moved, or cancelled.

We moved the calculation to write time. When a job is created, moved, or cancelled, we recalculate the day’s booked status and store it. The month view reads the pre-calculated boolean instead of computing it on the fly.

Result: 4 seconds → 1.4 seconds. The database time dropped again, and the serialization cost dropped because the payload was smaller (no per-job truck-hour summation needed).


Fix #3: Prefetch the Next Month While the Dispatcher Is Still on This One

The dispatcher is looking at June. They’re going to click “next” to see July. We know this. So while they’re looking at June, the server is already rendering July and caching it.

When they click “next,” July loads from cache. To the dispatcher, it feels instant. The actual render time for July is still ~1 second, but the user never waits for it because it was already done.

Result: perceived load time from the user’s perspective → under 200ms for navigation.


What We Measured After

The largest fleet in the system now loads the month view in under 1 second. That’s measured on a real user’s session, on a real network, during morning peak.

For the typical fleet (50–100 jobs/month), the load time is under 400ms. The dispatcher clicks, the calendar appears, they keep working. No waiting. No frustration. No “I’ll come back to this later” — which is what happens when software makes you wait 13 seconds. You stop using it.


What This Is Worth

12 seconds saved per click × 5–6 clicks per day × every dispatcher × every location.

For a single dispatcher, that’s roughly 1 hour per month of recovered time. For a network with 5 dispatchers, that’s 5 hours a month. Not of idle waiting — of active dispatching that wasn’t happening because the screen was loading.

Slow software doesn’t just waste time. It changes behavior. People stop clicking. They stop checking. They work from memory instead of from the system. The data gets stale. The decisions get worse. Speed is not a nice-to-have — it’s a functional requirement.


If your dispatch board takes more than 2 seconds to load, that’s an answerable problem. Talk to us.

Book a performance review


References:

MR

MoveRight Team

MoveRight

performance dispatch calendar engineering speed

Ready to put this into practice?

Start a 5-day free trial and see how MoveRight handles this in your business.