Skip to content
beginner Phase 13 · Performance Testing

Performance Concepts and Baselines

Learn latency percentiles, throughput, test types, and how to establish a baseline.

50m
0 problems
Topic Progress 0%

Why the Dashboard Says 180 ms While Five Users Waited Six Seconds

qa-performance-concepts-baseline-percentiles -->

Your checkout API has a monitoring panel that proudly reports average response time: 180 ms, yet support keeps getting tickets saying checkout "hung for several seconds." Both statements are true, and the gap between them is the single most common performance mistake in interviews and in production: the average is the wrong summary for latency.

Latency is a distribution, and a distribution is summarized with percentiles. Take every request in the window, sort them by duration, and read the value at each position:

1,000 requests sorted ascending
   950 × 120 ms     → p50 = 120 ms, p95 = 120 ms
    45 × 800 ms     → p99 = 800 ms
     5 × 6,000 ms   → worst case, gateway timeout
average = (950×120 + 45×800 + 5×6,000) / 1,000 = 180 ms

The average is 180 ms and five real people waited six seconds. Reporting only the mean hides exactly the users who file tickets. The percentile set you should always report together:

Metric Question it answers The trap if you skip it
p50 What does a typical request experience? Flatters you when a minority is slow
p95 What does an unhappy-but-common user see? Misses the tail your SLA is written against
p99 What does one request in a hundred suffer? Regressions appear here first, weeks before the mean moves
max What is the absolute worst observed? Noisy — use it for hunting, not for thresholds

Interviewers ask "how would you measure this API?" to see whether you reach for p95/p99 instead of the mean. Say percentiles first, then throughput, then error rate.

Throughput is the second pillar: requests completed per second (rps) or transactions per second (tps) over the window. It only means something next to latency, and Little's Law connects them:

concurrent requests in flight = throughput × latency
40 rps × 0.5 s = 20 requests in flight

That equation is a sanity check you can run in your head during a load test. If throughput stays at 40 rps but p95 climbs from 500 ms to 2 s, concurrency has quadrupled to 80 — your thread pool, database pool, or upstream is queueing, and the queue is now the bottleneck, not the CPU. It also converts user counts into a load model: 200 users who each issue a request every 5 seconds is 40 rps, not 200 concurrent.

Error rate is the third pillar and the one teams forget under load. Count HTTP 5xx, gateway timeouts, connection resets, and — critically — correctness failures: a 200 response that returns a stale price is a failed request. Timed-out requests must be counted as errors at the timeout threshold (say 6 s), never quietly dropped as "slow successes," or your error rate reads 0 % while users see failures. That is why performance runs need the same response assertions functional tests use, a point Test Reporting and Dashboards already made for functional runs: a green run with wrong answers is not green.

Hold the three metrics in one sentence when you report: p95 latency, throughput at that latency, and error rate — for example "240 rps at 310 ms p95 with 0.02 % errors." Any one of them alone is a number without meaning, and the follow-up to this topic, Designing JMeter Test Plans, is where you start producing those triples on demand.

Load, Stress, Spike, or Soak: Choose the Test by the Question You Are Asking

qa-performance-concepts-baseline-test-types -->

Teams routinely run one long test at one load level and call it "performance testing." It answers exactly one question, usually the least interesting one. Each test type is defined by the shape of the load curve and the question it answers — pick the question first, then the shape.

Test Load shape The question Passing looks like Typical length
Load Ramp to expected peak, hold Does the system meet its SLO at normal peak? p95 under target, error rate near zero, flat latency through the hold 10–30 min
Stress Ramp past peak until it breaks Where is the ceiling, and does it fail gracefully? Degradation is orderly: 429s or queued, then recovery after load drops 20–40 min
Spike 0 → 10× in seconds, hold, drop Do buffers, autoscaling, and pools react fast enough? No dropped connections; latency recovers within seconds of the drop 5–10 min
Soak ~70 % of peak, constant, hours Does steady use leak anything? Flat memory, connections, disk, and latency for the whole run 6–8 hours

Load is the release gate: it validates the number in your SLA. The classic mistake is testing at exactly 100 % of expected peak with zero headroom — real traffic has bursts, so test at peak plus 20–30 %.

Stress deliberately walks past the ceiling. You are watching the shape of collapse. A healthy system at 150 % load returns 429 with Retry-After and holds p95 at a degraded but finite value. An unhealthy one shows latency compounding without bound (the queue grows: remember Little's Law — latency climbs, so concurrency climbs, so the queue grows) and connections timing out. The follow-up question interviewers add: does the system recover when load returns to normal, or is it wedged? Run the stress test long enough to include that recovery ramp.

Spike is a different physics problem. Autoscalers poll every 30–60 s, TCP backlog is finite, and a database pool of 20 connections does not grow on demand. A 10× burst in 2 seconds tests whether the front door absorbs or drops: expect connection errors if the buffer is too small, or a latency cliff if every request queues behind 20 pool slots.

Soak finds the bugs no short test can. Real numbers from runs that mattered: a JVM heap growing 40 MB/hour until java.lang.OutOfMemoryError at hour 6; a connection pool leaking one socket per minute so hour 5 sees HikariPool-1 - Connection is not available, request timed out after 30000ms; a log volume filling at 1 GB/hour. Every one of those passes a 20-minute load test. If you only have budget for two tests, run load on every release and soak nightly — a soak failure at 03:00 is far cheaper than one at 09:00 in front of customers.

Notice what the table shares: every row needs a stable load generator and repeatable numbers, which is what the next topic builds — Designing JMeter Test Plans turns these four shapes into reusable thread-group configurations.

Capturing a Baseline on Staging That Survives the Next Release

qa-performance-concepts-baseline-establishing -->

"Is checkout slower than last sprint?" is unanswerable unless someone wrote down what slower meant before the change. A baseline is a stored, trusted set of measurements — p50/p95/p99, throughput, error rate — taken on a known build under known conditions, against which every future run is compared. Without one you are comparing vibes.

The procedure that produces a trustworthy number:

  1. Fix the environment. Same instance types, same DB size, same data volume. Note the commit SHA and the build number alongside the results — a baseline with no version attached is a rumor.
  2. Fix the data. Seed an identical dataset (say 50,000 orders, 200,000 users) and reset it between runs. Query plans change when tables grow; a baseline taken on an empty database measures an application nobody runs.
  3. Warm up, then repeat. Discard the first run (JIT compilation, connection-pool fill, cold caches), then run at least 3 times and record the spread.
  4. Store the artifact, not just a number — the JTL/CSV and the generated report belong next to the trend data you already publish from Test Reporting and Dashboards.

A small baseline table for one service might look like this:

Endpoint Baseline p95 Alert threshold Throughput Error budget
POST /checkout 310 ms 380 ms (+20 %) 120 rps 0.1 %
GET /catalog 90 ms 110 ms 300 rps 0.05 %
POST /login 220 ms 275 ms 40 rps 0.1 %

Three rules keep those numbers honest:

  • Account for noise. Staging is shared. Expect roughly ±5–10 % run-to-run variation on p95; set your alert threshold above it (a common choice is baseline × 1.2), or you will spend weeks chasing the background job a colleague triggered at 14:00. If a run breaches the threshold, re-run before filing a bug — two consecutive breaches is a regression, one is weather.
  • Never compare across environments. A staging box that is half the production size will produce p95 numbers 2–4× worse, and the ratio does not transfer. Either baseline production passively (real traffic percentiles from your APM) or keep staging sized proportionally and say so in the report.
  • Compare like-shaped runs. Same virtual-user count, same ramp, same payload size, same think time. A baseline taken at 50 users compared against a run at 200 users measures load, not regression.

The comparison itself can be a five-line script rather than a person staring at two charts:

k6 run --summary-export new.json checkout.js          # candidate build
node -e '
  const base = require("./baseline/summary.json").metrics.http_req_duration.values;
  const cur  = require("./new.json").metrics.http_req_duration.values;
  const ratio = cur["p(95)"] / base["p(95)"];
  console.log(`p95 ${base["p(95)"]} -> ${cur["p(95)"]} (${(ratio*100).toFixed(0)}%)`);
  process.exit(ratio > 1.2 ? 1 : 0);'

That exit code is the whole point: the baseline stops being a document people consult and becomes the gate the pipeline enforces. Store the baseline artifacts per release — the summary JSON, the JTL, the commit SHA, the seed-data version — so that when someone asks "when did checkout get slow?", the answer is a bisect over stored baselines rather than archaeology.

One more placement decision matters: how much of the baseline comes from production. Staging baselines answer "did this build regress under this load model," while passive production monitoring answers "is the real p95 within the SLO the business promised." You want both — the staging gate catches the regression before release, and the production percentile confirms the promise after it. The next topic builds the first of those two instruments, turning this procedure into a repeatable JMeter plan: Designing JMeter Test Plans.

Quiz

1. A dashboard reports average latency of 180 ms, but 1 % of requests take 6 seconds. Which reporting change exposes the problem?

Question 1 options

2. A load test holds 40 rps; p95 climbs from 500 ms to 2 s while throughput stays flat. What does that combination indicate?

Question 2 options

3. A JVM heap grows about 40 MB per hour and the service dies around hour six. Which test finds this?

Question 3 options

4. A new build shows p95 of 350 ms against a 310 ms baseline on shared staging where run-to-run variation is about ±10 %. What is the correct call?

Question 4 options

Flashcards

Question

Why do p95 and p99 matter more than average latency?

Answer

Latency is a distribution; the mean dilutes the slow tail that produces user complaints and timeouts. p50 describes the typical request, p95 the unhappy-but-common one, p99 the one-in-a-hundred regression detector — always report the percentile set with throughput and error rate.

Question

State Little's Law and one load-testing use for it.

Answer

Concurrent requests in flight = throughput x latency (40 rps x 0.5 s = 20 in flight). Use it to convert a user count into a rps model (200 users, one request per 5 s = 40 rps) and to diagnose runs: flat throughput with rising latency means concurrency grew, so something is queueing.

Question

Distinguish load, stress, spike, and soak tests by the question each answers.

Answer

Load: does the system meet its SLO at peak (+20-30 % headroom)? Stress: where is the ceiling and does it fail gracefully (429s, recovery)? Spike: do buffers/autoscaling/pools absorb a sudden 10x burst? Soak: does 6-8 h at ~70 % of peak leak memory, connections, or disk?

Question

What makes a performance baseline trustworthy?

Answer

Same environment and seeded data, pinned commit, warm-up run discarded, at least 3 runs recorded with the spread, artifacts (JTL/report) stored with the numbers, and alert thresholds set above run-to-run noise (about 5-10 %, commonly baseline x 1.2). Never compare across differently sized environments.

Question

How should timeouts and 200-OK error responses be counted?

Answer

Timeouts count as errors at the timeout threshold (e.g. 6 s), never as slow successes; 200 responses carrying error payloads need business-level assertions to be caught. Otherwise the error rate reads 0 % while users experience failures.

Revision Notes

Key Takeaways

  • 1. The average is the wrong summary for latency; p95 and p99 are computed from the sorted distribution and expose the tail users complain about
  • 2. Little's Law converts between users, rps, and latency — use it to build the load model and to recognize queueing when throughput stays flat
  • 3. Each test type answers one question: load = SLO at peak, stress = ceiling and recovery, spike = burst handling, soak = slow leaks
  • 4. A baseline is only comparable to an identical-shaped run on the same environment, data, and load level
  • 5. Timeouts are errors at the timeout threshold, and 200 responses with error payloads need assertions — or error rate lies

Interview Tips

  • • "How do you measure an API's performance?" — answer p95/p99 latency, throughput at that latency, and error rate in one sentence, then mention baseline comparison; never lead with the average
  • • "50 users reported slowness but the dashboard was green — why?" — the dashboard showed the mean; the tail was in p99, and percentiles per endpoint would have localized it
  • • "Which test would you run before a Black Friday launch?" — load at peak+30 %, plus a soak for leaks and a spike for burst absorption; explain what each fails to catch
  • • "How do you avoid false regression alarms?" — account for run-to-run noise, set thresholds above it, require consecutive breaches, re-run before filing

Cheat Sheet

Performance Concepts and Baselines

Report every run as one sentence: "240 rps at 310 ms p95, 0.02 % errors" — any single metric alone is meaningless.

Metric Question Trap
p50 typical request flatters a slow minority
p95 / p99 unhappy / one-in-a-hundred user regressions appear here first
throughput (rps) how much work per second meaningless without latency
error rate 5xx + timeouts + wrong answers 200-with-error-body hides in it

Little's Law: in-flight = throughput x latency. Flat rps + doubled latency = queueing (pool/lock/downstream), not CPU.

Test types: load (SLO at peak+20-30 %) · stress (ceiling + graceful failure + recovery) · spike (burst absorption) · soak (6-8 h at ~70 % → leaks).

Baseline recipe: pin env + commit + seeded data → discard warm-up run → 3+ runs, record spread → store JTL/report → alert at baseline x 1.2 → re-run before filing on a single breach. Never compare across environments or load levels.

Continue Learning