Module 4: Load Profiles And Stages

7. Choosing the profile according to the question

Overview

You already have all the pieces: the shapes (constant, ramp, spike) and the executors (by VUs and by arrival rate). This lesson brings them together into a single practical skill —the most important of the module—: choosing the right profile according to the question you want to answer. Because a profile isn't an aesthetic decision; it's the direct consequence of your question. "Does it handle the expected load?" calls for one profile; "where does it break?" for another; "does it survive a peak?", another; "does it degrade over hours?", yet another. Choosing the wrong profile is like using a thermometer to measure weight: the tool works, but it doesn't answer your question. Here we build the map question → test type → profile (shape + executor), closing the circle with the five types you met in module 1 (smoke, load, stress, spike, soak) and connecting them with this module's shapes and executors.

Connection to the module: this is the synthesis lesson. Lesson 4 gave you the shapes, lessons 5 and 6 the executors; here you decide which to use and why. It reuses the five test types from module 1 —which you met conceptually there— and now gives them the concrete shape and executor you learned. It's also the rehearsal for the mini-project (lesson 8), where you'll apply one of these decisions. It adds no new tools: it's pure judgment, the glue that makes everything before useful. Thresholds (the pass/fail criterion) still belong to module 5; here we only choose the shape of the load.

The analogy: the doctor who chooses the test

A good doctor doesn't order "all the tests"; they order the test that answers their question. If they suspect anemia, they order a blood count. If they want to see the heart under exertion, a stress test. If they're looking for a fracture, an X-ray. Each test is designed for a question, and ordering the wrong one wastes time and money without getting you closer to the diagnosis. The skill isn't in knowing how to run the tests —the machine does that— but in knowing which to order.

Choosing a load profile is the same clinical act. The machine (k6, the generator) knows how to apply any shape; your job is to translate the business question into the test that answers it. "Will we survive Black Friday?" isn't a test; it's a vague symptom. As the system's doctor, you break it down: "do we handle that day's expected load?" (a load test), "where's our limit in case the estimate falls short?" (a stress test), "do we survive if the traffic arrives all at once when we open at midnight?" (a spike test). Each is a different test with a different profile. The lesson is learning that translation.

The map: question → type → profile

Here's the table that summarizes the whole module. Read it as a translation dictionary: from the question (left) to the profile that answers it (right).

QuestionTypeShapeExecutorHow it's read
Does the system work under minimal load?smokeconstant, few VUs, short timeconstant-vusDoes it respond 200 with no errors?
Does it handle the expected load?loadramp-up → steady (at expected) → ramp-downramping-vus (or constant-arrival-rate if the load is in RPS)Does the plateau's p95 meet the target?
Where's the limit?stressincreasing ramp, beyond expected, until it yieldsramping-vus (or ramping-arrival-rate)At what level does the p95 spike or do errors appear?
Does it survive a sudden peak?spikebaseline → abrupt jump → baselineramping-vus with abrupt stagesThe max/p99 in the blow and the p95 in the recovery
Does it degrade with hours of load?soakconstant, long (hours)constant-vusDo the p95 or the memory grow over time?

Each row is a legitimate and distinct question, and none replaces another. A system can pass the smoke, handle the expected load (load), and still break with a sudden peak (spike) or leak after six hours (soak). That's why a serious load test isn't one test: it's a battery, each with its profile.

The five types, with their concrete profile

Let's walk through each type, giving it the shape and executor you already know.

Smoke — does it even work? The most humble one and the one that goes first, always. Before investing in big tests, you confirm the system starts and responds under a minimal load. Shape: constant, 1-5 VUs, a few seconds. Executor: constant-vus. If the smoke fails, there's no point running a stress test —something is broken at the base—. It's the "is the patient breathing?" before any other test.

Load — does it handle what I expect? The central test. You fix the expected load (your normal traffic peak) and verify the system meets its targets there. Shape: ramp-up → steady (at the expected load) → ramp-down; you read the p95 on the plateau. Executor: ramping-vus, or constant-arrival-rate if your expected load is in RPS ("500 orders per second"). It's the test that answers "are we ready for the traffic we anticipate?".

Stress — where do I break? Here you look for the limit: you raise the load beyond the expected until the system yields —the p95 spikes non-linearly or errors begin—. Shape: increasing, sustained ramp, pushing upward. Executor: ramping-vus (or ramping-arrival-rate if you think in RPS). It gives you the breaking point and, with it, how much margin you have over your normal load. The staged ramp you ran is a stress test in miniature: rising 4 → 12 → 24, you saw the p95 accelerate (22 → 77 → 175 ms), the signature of approaching the limit.

Spike — do I survive a blow? You already know it in depth from lesson 4. A low baseline, an abrupt jump to a peak, and a return to the baseline. Executor: ramping-vus with very short stages on the rise. It's read differently: the blow's damage lives in the max/p99 (you saw it: max of 2016 ms in the executed spike), and the recovery in the p95 back to the baseline (15 ms, like the start). It answers "do we survive traffic arriving all at once and do we recover?".

Soak — do I degrade over time? The slowest one and the one that catches the most leaks. A constant but long load —hours—, to see if something degrades over time: memory not released, connections exhausted, a disk that fills. Shape: flat plateau. Executor: constant-vus. The key variable isn't the height of the load (it's moderate) but the duration. It answers "does it hold up a whole day, or does it leak at six hours?". It's the only type where time is the test.

The profile isn't chosen by taste: it's the translation of your question. Minimal state → smoke (short constant). Expected load → load (ramp to expected, read the plateau). Limit → stress (ramp until it yields). Sudden peak → spike (abrupt jump, look at max and recovery). Temporal degradation → soak (long constant). Choosing the wrong profile answers a question you didn't ask.

How they combine in a single test

In practice, these types don't always live in separate tests: they're often chained into a single profile, taking advantage of the phases. A common pattern is smoke → load → stress in a single increasing run:

// CONTENT (not run here). A profile that chains smoke, load, and stress.
export const options = {
  stages: [
    { duration: '30s', target: 5 },   // smoke: confirm it works
    { duration: '1m',  target: 50 },  // ramp-up to the expected load
    { duration: '3m',  target: 50 },  // load: steady at expected (you read the p95 here)
    { duration: '2m',  target: 200 }, // stress: push beyond to find the limit
    { duration: '1m',  target: 0 },   // ramp-down: recovery
  ],
};

Read the profile as a story: it starts gently (smoke), rises to the expected load and holds it (load, the plateau where you read the official number), then pushes much higher to find the limit (stress), and drops (recovery). A single run answers three questions. What you don't usually mix in is the soak (it needs its own long duration) or the spike (its abruptness dilutes if it comes after a ramp). The criterion: chain what shares an increasing shape; separate what needs a shape or duration of its own.

Common mistakes

Running a stress or a load without having passed the smoke. What happens: someone launches straight into a big test, it fails, and they lose an hour debugging when the problem was trivial (a badly written endpoint, a wrong port). Why it happens: the humble step gets skipped out of haste. How to detect it: if your first test of the day is big and something fails, you don't know whether it's the load or a base bug. How to fix it: always a smoke first —few VUs, confirm it responds 200— before any serious test; it's cheap and saves you debugging what isn't the issue.

Testing only the expected load and declaring yourself ready. What happens: a load test is run at the anticipated load, it meets the SLO, and the system is declared ready —until the estimate falls short or a peak arrives and everything falls over—. Why it happens: the load test answers "do I handle what I expect?", and it's easy to forget the expected can fail. How to detect it: if you never ran a stress (how much margin do I have?) or a spike (do I survive a blow?), your "ready" only covers the optimistic scenario. How to fix it: complement the load with a stress (to know your limit and your margin) and, if traffic can arrive all at once, a spike.

Using a short soak or a gradual spike. What happens: someone runs a "soak" of ten minutes (too short to see a slow leak) or a "spike" that actually rises over a minute (too gradual to be a blow). Why it happens: the type's name gets copied without respecting its key variable. How to detect it: a soak without hours isn't a soak; a spike without abruptness is a ramp. How to fix it: respect what defines each type —the soak is duration (hours), the spike is abruptness (almost instantaneous jump)—; if you can't give it that, you're not running that type.

Exercises

Exercise 1 — Translate the question into the type. For each question, say what test type answers it and with what shape. (a) "Does our API meet p95 < 400 ms with the 200 users we expect at peak hour?" (b) "Beyond how many users does it fall over?" (c) "Does it survive 1000 people entering all at once when the ticket sale opens?" (d) "Does it hold up running all night without degrading?"

See solution
  • (a) Load. Expected load (200 users), verify the SLO on the plateau. Shape: ramp-up → steady at 200 → ramp-down.
  • (b) Stress. Look for the limit by rising until it yields. Shape: increasing ramp beyond the expected.
  • (c) Spike. Sudden peak and recovery. Shape: baseline → abrupt jump to 1000 → baseline.
  • (d) Soak. Temporal degradation. Shape: moderate but long constant (all night = hours).

Exercise 2 — Critique the profile. A colleague wants to answer "where does Reservo break?" and writes: { vus: 30, duration: '5m' }. Does it answer their question? If not, what profile should they use?

See solution

It doesn't answer it. { vus: 30, duration: '5m' } is a constant-vus: a flat plateau of 30 VUs. That answers "how do I do with 30 sustained users?" (a load/soak at 30), but it never looks for the limit —it stays fixed at 30 and never rises to see where it yields—.

For "where does it break?" they need a stress test: an increasing ramp that pushes beyond 30 until the p95 spikes or errors appear. For example:

stages: [
  { duration: '1m', target: 30 },   // start at the known
  { duration: '3m', target: 150 },  // push upward
  { duration: '2m', target: 300 },  // keep rising until it yields
  { duration: '1m', target: 0 },
]

The key is that the load rises until finding the breaking point; a fixed plateau can't.

Exercise 3 — Design the battery. Your boss says: "I need to be sure Reservo handles the launch." Design a battery of tests (not just one) that covers the relevant questions, naming each type and what it answers. Assume you expect 200 concurrent peak users and that the traffic can arrive all at once on opening.

See solution

A reasonable battery, in order:

  1. Smoke (constant-vus, 3 VUs, 30 s): confirms Reservo starts and responds 200 before anything. Cheap, always first.
  2. Load (ramping-vus, ramp to 200 → steady 3-5 min → ramp-down): does it meet the SLO (p95 < X) with the 200 expected users? It's the central "are we ready?" question.
  3. Stress (ramping-vus, ramp beyond 200 until it yields): where's the limit? How much margin do we have if the estimate of 200 falls short?
  4. Spike (ramping-vus, baseline → abrupt jump to ~200+ → baseline): since traffic can arrive all at once on opening, does it survive the blow and recover?
  5. (Optional) Soak (constant-vus, moderate load, several hours): if the launch lasts a day, does it degrade or leak over time?

The key is the pattern: "does it handle the launch?" isn't a test, it's several, each answering a different question (works / expected load / limit / blow / temporal endurance). You start with the smoke and rise in ambition.

Summary and next step

In this lesson you learned the skill that makes the whole module useful: choosing the profile according to the question. A profile isn't an aesthetic decision, it's the translation of what you want to know. You built the complete map: smoke (short constant → does it work?), load (ramp to the expected load, read the plateau → do I handle the anticipated?), stress (ramp until it yields → where's my limit?), spike (abrupt jump and recovery → do I survive a blow?), and soak (long constant → do I degrade over the hours?). Each with its shape and its executor from the previous lessons.

You saw that no test replaces another —a system can pass the load and break in the spike or leak in the soak— and that in practice some types are chained into a single increasing profile (smoke → load → stress), while the soak and the spike call for their own run because of their duration or abruptness. The clinical lesson: like a doctor, you don't run "all the tests"; you run the one that answers your question, and you always start with the smoke.

Before moving on you should be able to: translate a business question into the test type and the profile that answers it; explain why you always start with the smoke; critique a profile that doesn't answer its question; and design a test battery for a broad goal like "handling a launch."

What comes next is putting it all into practice with your own hands. In lesson 8, the mini-project: you write a staged load profile —the k6 script with ramp-up/steady/ramp-down stages (content) and its executable equivalent in Python—, run it against Reservo, report the p95 per stage, and reflect on what the shape revealed. It's the synthesis of the eight lessons.

Resources