skip to content

In k6, how do the shared-iterations and per-vu-iterations executors differ in spending an iteration budget?

level: middleimportance: must knowfreq 76%

answer

  1. one pool, or one count each
  2. same three keys, different reading
  3. total versus a multiplication
  4. vus times iterations for per-vu

basics

~20 s

k6's shared-iterations executor treats iterations as one total pool that any free VU pulls from, so per-VU counts come out uneven. per-vu-iterations gives every VU that exact count, making the scenario total vus times iterations.

solid answer

~40 s

Both are k6's iteration-budget executors and both take the same three keys — `vus`, `iterations`, `maxDuration` — but they read `iterations` differently. Under `shared-iterations` it is the **total** for the scenario: k6 keeps one counter and each VU claims the next iteration as it frees up, so a fast VU completes more than a slow one and the split is deliberately uneven. Under `per-vu-iterations` it is **per VU**: each VU runs exactly that many and the total is `vus * iterations`. So `shared-iterations` pins the total work regardless of `vus`, while on `per-vu-iterations` changing `vus` changes the total. In k6 v2 both default to `vus: 1`, `iterations: 1`, `maxDuration: '10m'`.

code

javascript · 20 lines
javascript
import http from 'k6/http';
import exec from 'k6/execution';

const rows = open('./accounts.csv').split('\n').slice(1); // 500 data rows

export const options = {
  scenarios: {
    csv_pass: {
      executor: 'shared-iterations',
      vus: 20,
      iterations: 500,
      maxDuration: '30m',
    },
  },
};

export default function () {
  const id = rows[exec.scenario.iterationInTest].split(',')[0];
  http.get(`https://test.k6.io/contacts.php?id=${id}`);
}

go deeper

for a junior

Memorise the one-line difference: shared-iterations counts iterations for the whole scenario, per-vu-iterations counts them for each VU. Recite the total for per-vu-iterations as vus times iterations.

for a middle

Explain the mechanism, not just the arithmetic. One shared counter is why the shared split is uneven, and the private per-VU loop is why the other one is exact but ends only when the slowest VU finishes.

for a senior

Show you pick between them from the requirement. Fixed total work touched once points at shared-iterations; every VU walking an identical slice points at per-vu-iterations. Mention that per-vu totals drift whenever someone edits vus.

for a principal

Frame it as which number the team is allowed to tune without changing the meaning of the run. shared-iterations makes vus a throughput dial and iterations the contract; per-vu-iterations couples the two, so the contract needs a comment or a computed value.

## Two executors that spend a counted budget k6 v2 ships exactly six executors, and two of them run a **counted budget of iterations** rather than running against a clock: `shared-iterations` and `per-vu-iterations`. Both are configured as a scenario entry, and both accept the same three executor-specific keys on top of the keys every executor shares (`executor`, `startTime`, `gracefulStop`, `env`, `exec`, `tags`, `options`): - `vus` — how many virtual users run concurrently. Default `1`. - `iterations` — the budget. Default `1`. - `maxDuration` — a wall-clock ceiling on the scenario. Default `'10m'`. Because the key names are identical, the *only* thing that separates the two executors is **what the number in `iterations` counts**. ## What `iterations` counts in each - With **`shared-iterations`**, `iterations` is the **scenario total**. k6 keeps one counter for the whole scenario; each VU, on finishing an iteration, claims the next number from that counter and runs it. When the counter is exhausted the VUs return and the scenario ends. k6 describes the scenario at startup as `N iterations shared among M VUs`. - With **`per-vu-iterations`**, `iterations` is **per VU**. Every VU runs its own private loop of exactly that many iterations, so the scenario total is `vus * iterations`. k6 describes it as `N iterations for each of M VUs`. The practical consequence is an arithmetic one. On `shared-iterations` the total is pinned to `iterations` and `vus` only changes how fast the budget is spent. On `per-vu-iterations` the total is a product, so editing `vus` from 20 to 25 quietly moves a 500-iteration run to 625. ## Side by side | | `shared-iterations` | `per-vu-iterations` | |---|---|---| | meaning of `iterations` | total for the scenario | count for each VU | | total iterations run | `iterations` | `vus * iterations` | | per-VU split | uneven, whatever each VU manages | exact, identical for every VU | | `iterations` must be at least `vus` | yes, checked before the run starts | no such rule | | scenario ends when | the shared pool is empty | every VU has finished its own count | | startup description line | `N iterations shared among M VUs` | `N iterations for each of M VUs` | ## Why the shared split is uneven The single shared counter is the whole mechanism: a VU that finishes quickly comes back for another number sooner. k6's own documentation states the distribution is not guaranteed to be even and gives the example of one VU performing 50 iterations while another performs only 10. - If iterations differ in cost — different CSV rows, different endpoints — the cheap ones concentrate in whichever VUs happen to draw them. - If one VU stalls on a slow response its share simply shrinks; the budget is still spent, by the others. - `per-vu-iterations` gives up that self-balancing deliberately: the scenario lasts as long as the **slowest** VU needs to finish its count, and a fast VU sits idle once its own loop is done. - When a k6 test is split across instances, `shared-iterations` scales both the VU count and the iteration count, while `per-vu-iterations` scales VUs only — scaling its iterations too would make the total grow quadratically. ## Picking one: a 500-row CSV pass Say you hold a CSV of 500 accounts and want a data-driven pass that touches **each row exactly once**. 1. Use `shared-iterations` with `iterations: 500`. Whatever `vus` you set, k6 runs 500 iterations and no more; 20 VUs simply finish sooner than 5. Index the row from `exec.scenario.iterationInTest`, which is unique across the scenario. 2. Use `per-vu-iterations` instead and you own the arithmetic: `vus: 20, iterations: 25` also yields 500, but the number is now coupled to `vus` and any later edit to `vus` changes it. 3. Either way, raise `maxDuration` above the 10-minute default if 500 iterations could plausibly take longer, because it is a hard ceiling and not a target. The mirror requirement is just as real. If the brief is "every VU must walk the same 25-row slice", then `per-vu-iterations` is the only one of the two that guarantees it. ## The configuration errors k6 raises `shared-iterations` refuses an `iterations` value lower than `vus`, because that would leave VUs with nothing to claim; the message names both numbers. `per-vu-iterations` has no such rule and only requires `iterations` above 0. Both refuse a `vus` of 0 or less and a `maxDuration` under one second. These checks run while k6 consolidates configuration, before any VU is initialised, so a bad iteration budget fails at once rather than half-way through the run.

  • With k6's shared-iterations, what stops one fast VU from taking most of the budget?
    Nothing does, and that is by design. Every VU claims from one shared counter, so a VU with cheap iterations comes back sooner; k6's docs give the example of one VU doing 50 iterations while another does 10. If each VU must complete the same count, `per-vu-iterations` is the executor that guarantees it.
  • A k6 per-vu-iterations scenario has vus: 20 and iterations: 25. How many iterations run, and what changes if vus becomes 25?
    It runs 500, because `per-vu-iterations` totals `vus * iterations`. Raising `vus` to 25 raises the total to 625 — the budget moved without anyone editing `iterations`. On `shared-iterations` the same edit leaves the total at `iterations` and only changes how quickly it is spent.
  • Which of the two k6 iteration executors ends as soon as the fastest VUs have finished the work?
    `shared-iterations`. The scenario ends the moment the shared counter is exhausted, so fast VUs absorb the slack from slow ones. `per-vu-iterations` runs as long as the slowest VU needs for its own count, leaving faster VUs idle once their private loop is done.

shared-iterations is a stack of 500 job tickets on a table that any free worker takes from. per-vu-iterations hands each worker a sealed envelope of 25 tickets to work through alone.

saying these in an interview costs you the question

  • Thinks shared-iterations divides its budget evenly across the VUs
  • Reads per-vu-iterations' iterations key as a scenario-wide total
  • Believes raising vus on per-vu-iterations leaves the total unchanged
  • Claims shared-iterations accepts an iterations value below vus
  • Assumes either executor runs without a maxDuration ceiling by default
  • Says the two executors take different option keys from each other