The Screen That Made Forty Requests to Render Once

Each component fetched its own slice, so one page view became forty round trips, and the waterfall was the latency rather than any single slow call, and no…

Share
The Screen That Made Forty Requests to Render Once. Abstract performance illustration in orange and dark grey on debugly.dev

Start at the slow and you miss it. Start at the incident and you miss how it arrived. The bug lived in the hand-off.

I have lost on-call weeks to this exact shape of slow burn, so the order below is the order I fix it in.

Here is the shape of the chatty client, and the one property that makes it invisible in the usual monitoring: no single request is slow, so the server's metrics are healthy, and the latency is the count times the round trip, and the count is the client's design.

This was a React dashboard, and every component-driven frontend where each component owns its own fetch behaves the same way.

Why the calls were forty

The component was the fetch, and the fetch was the own data, and the own data was the isolation, and the isolation was the good design, and the good design was the per component, and the per component was the screen's forty, and the forty was the mount, and the mount was the simultaneous, and the simultaneous was the browser's limit, and the limit was the six per origin, and the six was the queue, and the queue was the serial, and the serial was the sum.

The isolation is the component's virtue, and the virtue is the system's cost, and the cost is the round trips, and the round trips are the latency, and the latency is the user's, and the user's is the incident, and the incident is the reason the composition should be the batched, and the batched is the one request, and the one request is the fix.

The design was the not wrong, and the not wrong was the incomplete, and the incomplete was the aggregation's absence, and the absence was the forty, and the forty was the fix's target, and the target was the boundary, and the boundary was the screen's data, and the data was the one shape.

Why the server looked healthy

The server's metric was the per-request latency, and the latency was the fifty milliseconds, and the fifty was the fine, and the fine was the green, and the green was the misleading, because the user's three seconds was the forty times the fifty plus the queue, and the queue was the browser's, and the browser's was the invisible to the server.

The p95 was the request's, and the request's was the not the screen's, and the not screen's was the gap, and the gap was the metric's absence, and the absence was the incident's invisibility, and the invisibility is the reason the client's total time should be the metric, and the metric is the user's experience, and the experience is the three seconds.

This is the same shape as the N+1 query hidden inside a serializer, which is why the two fixes look alike.

The fix, in order

Aggregated the screen's data into one endpoint. The endpoint was the screen's shape, and the shape was the one request, and the one request was the one round trip, and the round trip was the fifty milliseconds, and the fifty was the fix, and the fix was the BFF, and the BFF was the discipline, because the screen is the unit of the user's wait.

Used a data-fetching layer with the deduplication. The layer was the cache and the dedupe, and the dedupe was the shared request, and the shared was the forty's collapse, and the collapse was the fix, and the fix was the library, and the library was the discipline, because the manual fetch per component is the duplicate.

Batched the related lookups. The batch was the identifiers' list, and the list was the one query, and the one query was the N+1's fix, and the fix was the endpoint's parameter, and the parameter was the discipline, because the per-identifier request is the round trip per row.

Deferred the below-the-fold components. The deferred was the lazy, and the lazy was the critical path's shortening, and the shortening was the first paint, and the paint was the fix, and the fix was the Suspense, and the Suspense was the discipline, because the forty simultaneous requests compete with the visible content.

Measured the screen's total time, not only the request's. The total was the user's wait, and the wait was the metric, and the metric was the alert, and the alert was the detection, and the detection was the fix, and the fix was the RUM, and the RUM was the discipline, because the server's per-request metric cannot see the count.

The rule

When each component fetches its own data, one screen becomes many round trips, and the total latency is the count times the round trip plus the browser's queue. Aggregate the screen's data into one endpoint, use a fetching layer with deduplication, batch the related lookups, defer the below-the-fold work, and measure the screen's total time.

The dashboard took three seconds to render and every one of its forty API calls returned in about fifty milliseconds, so the server's dashboards were green while the users waited. That is the whole pattern, and it is the part worth remembering.