skip to content

When a k6 test is split into four execution segments, how are VU and iteration counts divided?

level: middleimportance: should knowfreq 56%

answer

  1. nothing is ever a fraction of a VU
  2. each segment owns a stripe of indices
  3. the four shares sum to the scripted number
  4. load is divided, wall-clock time is not

basics

~20 s

k6 gives each execution segment a striped, non-overlapping set of indices from the whole partition and scales every count against it, so the per-instance shares always add back up to the scripted total. Durations are never scaled.

solid answer

~40 s

k6 combines the segment with the sequence into an execution tuple, which assigns each segment a repeating set of indices covering the partition exactly once. A count is scaled by asking how many of the first N indices belong to this segment, so the shares are whole numbers that sum to the original: `vus: 200` over four equal quarters is 50 each, and `vus: 10` is `3 + 3 + 2 + 2` rather than 2.5 apiece. VU counts and shared iteration totals go through that integer striping; an arrival rate is instead multiplied by the segment's length as an exact fraction. Durations, stage timings and start times are not scaled at all — segmentation divides the load, not the clock.

code

javascript · 7 lines
javascript
export const options = {
  vus: 200,
  duration: '5m',
  executionSegmentSequence: '0,1/4,2/4,3/4,1',
};

export default function () {}

go deeper

for a junior

Recall the headline: k6 scales the counts in your script down to your slice, in whole numbers, and the pieces add back up to what the script asked for.

for a middle

Explain the striped index assignment and why it, rather than plain rounding, is what guarantees the shares sum correctly and never shrink as the scripted number grows.

for a senior

Sanity-check a proposed fleet size against the scripted counts before launching, so you do not discover after the fact that some instances drew a share of zero.

for a principal

Decide whether the scripted totals should describe the whole test and be divided, or describe one instance and be multiplied by the fleet size, and make that convention explicit for everyone writing scripts.

## Counts are integers, and segments are fractions A k6 script asks for whole things: `vus: 200`, `iterations: 1000`. An execution segment is a fraction of the run. Cutting a test four ways therefore has to answer an awkward question — what does a quarter of seven VUs mean? In k6 v2 the answer is never "a fraction of a VU" and never "round each share and hope". k6 builds an internal *execution tuple* from the segment plus the sequence, and every count in the derived plan passes through it before the instance acts on it. ## The striping rule The tuple gives each segment a repeating, non-overlapping set of indices out of the whole partition. For four equal quarters that pattern is the obvious one — the first instance takes indices 0, 4, 8, …, the second takes 1, 5, 9, …, and so on — so the four instances between them cover every index exactly once, with none shared and none dropped. A count is then scaled by asking **how many of the first N indices belong to me**. Two properties fall out of that, and they are the ones to be able to state: 1. **The shares always add back up to the original.** k6 has a test whose entire purpose is that invariant: scaling every segment of one sequence by the same factor sums to that factor. Ten VUs over four quarters is `3 + 3 + 2 + 2`, never `2.5` each and never `3 + 3 + 3 + 3`. 2. **The scaling is monotonic.** A larger scripted number never yields a smaller share for a given instance, because shares are incremented in a fixed order across the partition rather than recomputed by rounding. ## Worked example over four instances | scripted value | `0:1/4` | `1/4:2/4` | `2/4:3/4` | `3/4:1` | total | |---|---|---|---|---|---| | `vus: 200` | 50 | 50 | 50 | 50 | 200 | | `vus: 10` | 3 | 3 | 2 | 2 | 10 | | `vus: 4` | 1 | 1 | 1 | 1 | 4 | | `vus: 2` | 1 | 1 | 0 | 0 | 2 | The `vus: 2` row is the operationally interesting one: two of the four instances get **zero** VUs. Nothing is broken — the pieces still sum to the scripted total — but a fleet cut finer than the numbers in the script leaves boxes with nothing to do. The evenly divisible case is what k6's own operator documentation describes: a script with 200 VUs and `parallelism: 4` becomes four runner instances of 50 VUs each. ## What gets scaled, and what does not - **VU counts** are scaled — the VU number of a constant-VU scenario, the starting VUs and each stage target of a ramping one, and the pre-allocated and maximum VUs of the arrival-rate scenarios. - **Iteration counts** are scaled where the total is shared across the test. A `shared-iterations` scenario has both its VUs and its total iterations scaled; a `per-vu-iterations` scenario has its VUs scaled but its per-VU iteration count left alone, because that number was already per-VU. - **Arrival rates** are scaled too, but as an exact rational multiplied by the segment's length rather than as a striped integer — the segment length divides the tick interval, so a fractional rate is not lost to rounding. - **Durations, stage timings and start times are not scaled at all.** A five-minute run is five minutes on every instance; segmentation divides the load, not the clock. - **Thresholds are not scaled either.** The expression in the script is loaded verbatim by every instance, so a rule written against the whole test is evaluated against a quarter of it. ## Reading a share back Two habits make the arithmetic checkable instead of mysterious: - **Add the four shares up.** If they do not equal what the script asked for, one instance was given a different sequence from the others. - **Look for a share of zero.** It means the partition is finer than the scripted count, and that instance will start, connect to nothing and finish idle. - **Remember which numbers were never scaled.** A run that finishes in the scripted five minutes on a quarter of the VUs is behaving correctly, not finishing early. ## The consequence to plan around Because the shares are computed from the whole partition, an instance can work out its share **on its own** — that is the whole point of the design, and why no coordinating process is needed. It also means the arithmetic is only correct if every instance was given the same `--execution-segment-sequence`. Give one box a different sequence and its share is computed against a different partition, so the four shares stop summing to the scripted total, quietly.

  • A k6 script sets vus: 2 and you split it four ways. What do the four instances run?
    One VU, one VU, none and none. The shares still sum to the scripted 2, so nothing is lost, but the two instances that drew no VUs have no work. Cutting a plan into more segments than it has countable units leaves idle instances, so the segment count should stay well below the scripted VU count.
  • Does an execution segment shorten the test as well as shrinking the load?
    No. Durations, stage timings and scenario start times pass through untouched, so a five-minute plan is five minutes on every instance. Only counts — VUs, shared iteration totals — and arrival rates are scaled to the segment.
  • Why does k6 use rational numbers rather than floats for segment boundaries?
    So that thirds and other non-terminating fractions divide exactly. The endpoints are stored as exact rationals, which is what lets k6 guarantee that the per-segment shares of an integer add back up to that integer instead of drifting apart as the partition grows.

It works like dealing a deck round the table rather than cutting the deck into four piles by weight: everyone gets whole cards, and the four hands always add back up to fifty-two.

saying these in an interview costs you the question

  • Expects each instance to get a fractional VU count
  • Assumes every share is rounded up independently
  • Thinks the duration is divided along with the load
  • Believes shares can sum to more or less than the script's value
  • Assumes per-VU iteration counts are divided too