A k6 ramping-arrival-rate run records many dropped_iterations but never logs an Insufficient VUs warning. Why?
answer
- two signals, two different questions
- the warning is about the ceiling
- growth is reactive and costs starts
- budget never hit zero
- raise the floor, not the ceiling
basics
~20 sk6 logs the Insufficient VUs warning only once its maxVUs growth budget is exhausted, so its absence means the pool never reached maxVUs. Every start finding no free VU is still dropped while the pool grows.
solid answer
~40 sThe two signals answer different questions. `dropped_iterations` counts every scheduled start the executor could not place; the `Insufficient VUs, reached N active VUs and cannot initialize more` warning fires only when the unused part of `maxVUs - preAllocatedVUs` has hit zero. So drops with no warning mean the executor was still allowed to grow. That is the signature of a pool that was preallocated too small and grew reactively: each dropped start kicks off at most one background VU initialization, that build is non-blocking, and the start that provoked it is lost regardless. The fix is to raise `preAllocatedVUs` so the pool is warm before the clock starts, not to raise `maxVUs` - a bigger ceiling only extends the window during which k6 keeps dropping starts while it builds.
code
javascript · 21 linesimport http from 'k6/http';
export const options = {
scenarios: {
ramp: {
executor: 'ramping-arrival-rate',
startRate: 50,
timeUnit: '1s',
stages: [{ target: 500, duration: '5m' }],
preAllocatedVUs: 10,
maxVUs: 200,
},
},
thresholds: {
dropped_iterations: ['count===0'],
},
};
export default function () {
http.get('https://test.k6.io/');
}go deeper
Know that the two signals are separate: the counter records every start k6 could not place, while the warning only appears once the pool has stopped being allowed to grow.
Explain the growth budget: maxVUs minus preAllocatedVUs, decremented one VU at a time from inside the drop path, with the warning gated on it reaching zero.
Show the diagnosis and the fix in one breath - drops without the warning mean a cold pool that grew reactively, and the correction is a bigger preAllocatedVUs derived from the measured iteration duration, not a bigger ceiling.
Argue for which signal a pipeline should gate on. The warning is a transient log line, the counter is in the summary and can carry a threshold, so a durable verdict has to be built on the metric.
## What the warning actually reports The message is `Insufficient VUs, reached N active VUs and cannot initialize more`, at WARN level, with `N` being the executor's `maxVUs` after execution-segment scaling. Inside the executor it sits behind two conditions: - the growth budget, initialised to `maxVUs - preAllocatedVUs`, must have been decremented to **zero**; - a per-executor flag must still be unset, so the line appears **once per scenario run**. It is therefore a statement about the *ceiling*, not about drops. It never appears while the executor is still allowed to build VUs, no matter how many starts are being lost in the meantime. ## Why starts are dropped while headroom remains Walk the path the executor takes each time its timer fires and the VU pool refuses the handoff: 1. The start is abandoned and one sample lands on `dropped_iterations`. 2. If the growth budget is above zero, k6 signals a background goroutine to initialize one more VU and decrements the budget. The signal is non-blocking - if a build is already in flight, the signal is simply skipped. 3. Building that VU means running the script's init context for it, which takes real time while the run continues. 4. Every start scheduled during that build that also finds no free VU repeats steps 1 and 2. So a scenario configured `preAllocatedVUs: 10, maxVUs: 200` does not jump to 200 VUs on first pressure. It climbs one VU at a time, paying a dropped start for each climb, and can easily finish the run at 60 or 70 VUs having dropped tens of thousands of starts without ever emitting the warning. ## Reading the two signals together | drops | warning | what the pool did | |---|---|---| | none | none | the preallocated pool covered every start | | many | none | the pool was growing the whole time; `preAllocatedVUs` was too small and `maxVUs` was never reached | | many | present | the pool grew all the way to `maxVUs` and then stayed pinned there | | none | present | not reachable - reaching the ceiling requires at least one dropped start first | The fourth row is worth stating explicitly: the warning can only be produced from inside the drop path, so it never appears on a run whose counter is zero. ## What to change - **Raise `preAllocatedVUs`.** VUs built during the `Init VUs...` phase are ready before the first scheduled start, so they cost nothing in dropped starts. This is the only change that removes the drops rather than moving them. - **Do not just raise `maxVUs`.** A higher ceiling lengthens the reactive climb; it does not make the climb free. It also delays the warning, removing the one loud signal you had. - **Re-derive the number** from `iteration_duration` on that run rather than doubling by feel, then confirm with a `dropped_iterations` threshold. ## Why a cold pool loses so many starts The arithmetic is unforgiving. Starting from `preAllocatedVUs: 10` and needing 130, the executor has to add 120 VUs, and it charges one dropped start for each of them at minimum. In practice the bill is far larger, because every start scheduled while a VU is mid-construction is dropped too, and only one construction is in flight at a time. The scenario reaches a workable pool eventually and then runs cleanly - which is exactly why the counter is large but the warning never appears. ## Multi-scenario runs The once-only flag is local to each executor's run, so a script with two arrival-rate scenarios can emit two warnings, one per scenario, each naming that scenario's own `maxVUs`. Conversely, one warning in a multi-scenario run does not tell you which pool ran dry unless you read the accompanying scenario context - and since the `dropped_iterations` samples carry the scenario tag, that counter, broken down by scenario, is what identifies the culprit. ## Where to look The two signals live in different places, which is why one is easy to miss: - The warning is a WARN log line emitted while the run is in progress, so a CI job that keeps only the end-of-test summary throws it away. - `dropped_iterations` is durable: it lands in the summary's execution block next to `vus`, `vus_max`, `iterations` and `iteration_duration`. - Only the counter can carry a threshold, so only the counter can turn a short run into a failed one. Treat the counter as the primary signal and the warning as the extra bit that tells you the ceiling was reached: ```javascript export const options = { scenarios: { ramp: { executor: 'ramping-arrival-rate', startRate: 50, timeUnit: '1s', stages: [{ target: 500, duration: '5m' }], preAllocatedVUs: 10, maxVUs: 200, }, }, thresholds: { dropped_iterations: ['count===0'], }, }; ``` This is the configuration that produces drops without a warning. Move the 10 up to something the peak target justifies, and the counter - not the log - is what confirms you got it right.
- Would raising maxVUs from 200 to 2000 remove the dropped iterations?No. The executor still climbs one VU at a time and pays a dropped start for each step, so a higher ceiling just lengthens the climb. It also pushes the Insufficient VUs warning further out of reach, removing a signal without fixing the cause.
- Can the Insufficient VUs warning appear on a run whose dropped_iterations is zero?No. The warning is emitted from inside the code path that drops a start, so at least one drop must have been recorded before the executor can decide its growth budget is exhausted.
- In a script with two arrival-rate scenarios, how many such warnings can one run produce?Up to one per scenario. The suppression flag is local to each executor's run, and each message reports that executor's own maxVUs. Use the scenario tag on dropped_iterations to attribute the drops.
saying these in an interview costs you the question
- Reads the missing warning as proof the pool was big enough
- Raises maxVUs instead of preAllocatedVUs to stop the drops
- Thinks k6 jumps straight to maxVUs under pressure
- Expects one warning per dropped iteration, not one per run
- Assumes a single warning covers every scenario in the run