“The database is slow” is often a hypothesis disguised as a conclusion. The same request can spend time waiting for a database, serialising a large response, calling an external API, transferring bytes across the network or executing JavaScript in the browser. Bottleneck hunting works when those layers are measured separately.

1. Map one slow request end to end

Pick one representative user action and trace it from browser interaction to server response and back to rendering. Establish timestamps at meaningful boundaries. The aim is to produce a timeline rather than a collection of guesses.

// A useful server-side timing shape
const t0 = performance.now();
const rows = await repository.search(query);
const t1 = performance.now();
const body = serialize(rows);
const t2 = performance.now();
logger.info({ dbMs: t1 - t0, serializeMs: t2 - t1 });

2. Separate waiting from computation

On the server, distinguish CPU work from I/O waits. A request using little CPU can still be slow if it waits on a database, filesystem or remote service. Conversely, high CPU can point toward serialization, template rendering, compression, inefficient algorithms or excessive application-level processing.

3. Prove database claims with query evidence

Inspect slow queries, query count and execution plans. Look for repeated queries, missing or ineffective indexes, large scans, poor join strategies and lock contention. Use EXPLAIN or equivalent tooling to understand the database's chosen plan rather than assuming an index will help.

4. Check bytes and round trips

Network cost comes from both payload size and the number of round trips. A chatty API that performs ten sequential requests can be slower than one well-designed response even when each individual request looks fast. Compression, caching and batching help only when they address the measured cost.

5. Measure client-side work

A fast server response does not guarantee a fast experience. Profile main-thread activity, JavaScript execution, layout, rendering and long tasks. Large client bundles and expensive re-renders can dominate interaction latency after the network request has already completed.

6. Isolate external services

Record third-party calls separately with duration, status and timeout behaviour. If a payment, search, analytics or identity provider is slow, increasing your application's database resources will not fix the user-visible delay. Consider timeouts, fallbacks and asynchronous work where the business semantics permit them.

7. Change one variable and re-measure

Performance work is an experiment. Change one meaningful variable, run the same representative workload, and compare the same metrics. Avoid combining five optimisations into one deployment because you then lose the ability to explain which change produced the improvement or regression.

8. Optimise for percentiles and user journeys

Averages are useful but can hide tail latency. For APIs and critical workflows, inspect p50 alongside p95 and p99 when the distribution matters. Also connect technical metrics to actual journeys: login, search, checkout, dashboard load or report generation. The goal is faster user outcomes, not prettier infrastructure graphs.

A bottleneck is not the component that sounds slowest. It is the measured constraint that currently limits the operation you care about.

Engineering references

This article is an original synthesis of established engineering practices. The references below are included for readers who want the underlying technical documentation and deeper treatment.