skip to content

Why does a k6 scenario with `duration: '10s'` often keep running for longer than ten seconds?

level: middleimportance: should knowfreq 55%

answer

  1. two deadlines, not one
  2. stop starting, not stop running
  3. thirty seconds unless you say otherwise
  4. complete versus interrupted iterations

basics

~20 s

Because of gracefulStop, a per-scenario key that defaults to 30s on every k6 executor. At the end of the declared duration k6 stops starting new iterations but lets in-flight ones finish, cutting whatever is still running when gracefulStop expires.

solid answer

~40 s

Every k6 scenario has a `gracefulStop` window on top of whatever length its executor declares, and it defaults to `30s`. k6 runs the scenario against two deadlines: at the end of the regular duration it stops handing out new iterations, and at `duration + gracefulStop` it cancels whatever is still executing. So a `constant-vus` scenario with `duration: '10s'` and the default `gracefulStop` can occupy up to 40 seconds of wall clock, which is why the run header reports a *max duration (incl. graceful stop)* that is longer than the durations you wrote. Iterations cut at the outer deadline are reported separately as interrupted iterations and emit no `iterations` or `iteration_duration` sample. Setting `gracefulStop: '0s'` collapses the two deadlines so iterations are cut the instant the duration ends.

code

javascript · 16 lines
javascript
import http from 'k6/http';

export const options = {
  scenarios: {
    quick_smoke: {
      executor: 'constant-vus',
      vus: 50,
      duration: '10s',
      gracefulStop: '3s',
    },
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com/api/delay/5');
}

go deeper

for a junior

Know that gracefulStop exists on a k6 scenario and defaults to 30s, so a run can outlast the duration you wrote. Do not assume the declared duration is the wall-clock time.

for a middle

Explain the two deadlines: new iterations stop at the scenario's length, running ones are cancelled at length plus gracefulStop, and cancelled ones are reported as interrupted rather than complete.

for a senior

Diagnose a run from its complete-versus-interrupted split, size the window against measured iteration length, and know that the window also extends the scenario's slot in k6's execution plan.

for a principal

Judge the trade-off across a suite: a generous window costs wall-clock time in every pipeline run, a tight one leaves a tail of interrupted iterations, and only one of those costs is visible in the summary.

## What `gracefulStop` is `gracefulStop` is one of the keys every k6 scenario entry accepts, whichever of the six executors it names — it lives in the shared block alongside `executor`, `startTime`, `exec`, `env` and `tags`. It is a duration string, and **its default is `30s`**. A negative value is rejected at config time with *the gracefulStop timeout can't be negative*. It exists because an executor's own length — a `duration`, a set of stages, an iteration budget — describes when k6 should stop *starting* work, not when the work already started must be over. An HTTP request issued at second 9.9 of a ten-second scenario has to land somewhere. ## The two deadlines Internally k6 gives each scenario two nested deadlines and drives the whole mechanism from them: | moment | what k6 does | |---|---| | the scenario's regular length elapses | stops starting new iterations; each VU that finishes its current one is returned to the pool | | between then and `length + gracefulStop` | iterations already in flight run on to completion, still emitting their metrics normally | | `length + gracefulStop` elapses | every iteration still running is cancelled where it stands | While the second window is open the scenario's progress bar switches to a stopping state, so a run that appears frozen at 100% for a few seconds is usually just waiting out its graceful stop. When `gracefulStop` is `0`, the two deadlines are the same instant: there is no window, and any iteration in flight at the end of the duration is cancelled immediately. ## What an interrupted iteration costs An iteration cancelled at the outer deadline is **not** treated as a completed one: - it does not emit an `iterations` sample, nor an `iteration_duration` sample; - it is counted separately, and k6 reports the split in its progress line — for example `349 complete and 23 interrupted iterations`; - errors raised by the cancellation are deliberately not logged, so an interrupted iteration is silent rather than noisy. Samples already emitted earlier in that iteration — the HTTP requests it did complete — remain in the results. So a large interrupted count does not lose those requests, but it does mean the tail of your run contains iterations whose remaining work never happened. ## Where you can see it Three places in ordinary k6 output expose `gracefulStop` without any flags: 1. the run header line ends with *max duration (incl. graceful stop)*, and that figure is the end of the last scenario's window, graceful stop included; 2. each scenario's description in the header appends `gracefulStop: <value>` whenever it is non-zero; 3. the final progress line reports complete and interrupted iterations side by side. ## Choosing a value The default of 30 seconds is generous for typical HTTP work and stingy for long iterations. Some rules of thumb that stay inside what k6 itself defines: - an iteration that routinely takes longer than `gracefulStop` will be interrupted at the end of every scenario, so size the window against your observed iteration length rather than against the scenario duration; - lowering it to a second or two keeps a short smoke scenario short, at the cost of a handful of interrupted iterations; - setting it to `'0s'` is the right call when a scenario must not overlap the one scheduled after it, because the window also extends the scenario's reserved slot in k6's execution plan; - raising it far above the default lengthens the whole run, since k6 will not finish until the last scenario's outer deadline has passed. ## Two things `gracefulStop` is not It is not a global setting: it is declared per scenario, so a map with several entries can give each one a different window, and a scenario that omits it simply takes the 30-second default. And it is not the option that governs VUs being retired mid-run as a ramping scenario steps its target down — that is `gracefulRampDown`, which only the `ramping-vus` executor accepts, and it is a separate key with its own timer. Finally, `gracefulStop` has nothing to do with a manual interrupt. If you press Ctrl+C, or the run is aborted from the script, iterations are stopped immediately; the graceful window applies only to a scenario reaching the end of its own configured length.

  • Does `gracefulStop` apply to every k6 executor or only the ones with a duration?
    Every one. `gracefulStop` sits in the shared block of the scenario entry, so all six executors accept it and all six default to `30s`. For the iteration-based executors the window opens when their own ceiling expires rather than at a declared duration, but the mechanism is identical.
  • What is the effect of `gracefulStop: '0s'` on a k6 scenario?
    It removes the window entirely: the two deadlines collapse into one, so every iteration still running at the end of the scenario's length is cancelled on the spot and counted as interrupted. It also stops the scenario's slot in the execution plan from being extended, which matters when a later scenario is scheduled to start right after it.

Last orders at a bar: the taps close at closing time, but the drinks already poured can be finished. gracefulStop is how long after last orders before the lights actually go out.

saying these in an interview costs you the question

  • Thinking gracefulStop is a global option rather than per scenario
  • Assuming the default is zero and a duration is exact
  • Confusing gracefulStop with ramping-vus gracefulRampDown
  • Believing interrupted iterations still count in the iterations metric
  • Expecting gracefulStop to apply when the run is aborted with Ctrl+C