Why can a k6 threshold on http_req_duration{vu:3} never match a sample, and what does k6 report?
answer
- two bags per sample, not one
- high cardinality stays out of the index
- selector tests indexed tags only
- a warning, not a startup error
basics
~20 sk6 stores vu and iter as non-indexed sample metadata rather than as indexed tags, and a selector can only test indexed tags. k6 logs a startup warning naming that threshold, then runs with a permanently empty sub-metric.
solid answer
~40 sA k6 sample carries indexed tags, which are part of its time series, and separate non-indexed metadata for high-cardinality values. `vu` and `iter` are routed to metadata every time, so the subset test a selector performs against the indexed tag set can never succeed. When the metrics engine builds such a sub-metric it warns that the tag *was made non-indexable in k6 v0.41.0, so thresholds like this won't work correctly*, and then carries on: the run starts, the sub-metric stays empty, and a `p(95)<500` rule over an empty Trend passes. Note the two are opt-in tags as well - but unlike `ip` and `ocsp_status`, enabling them in `systemTags` still does not make them selectable.
code
javascript · 17 linesimport http from 'k6/http';
export const options = {
vus: 3,
duration: '10s',
systemTags: ['status', 'method', 'vu'],
thresholds: {
// never matches: vu is metadata, not an indexed tag - k6 warns at startup
'http_req_duration{vu:3}': ['p(95)<500'],
// works: status is an indexed system tag
'http_req_duration{status:200}': ['p(95)<500'],
},
};
export default function () {
http.get('https://quickpizza.grafana.com/api/json');
}go deeper
Recall that k6 refuses to make per-VU and per-iteration thresholds work, and that a threshold key can only select on tags k6 indexes, such as status, method or a tag your script sets.
Explain the split between indexed tags and non-indexed metadata, why the subset test a selector performs cannot reach metadata, and that k6 warns rather than failing the config.
Recognise the warning in a startup log, connect it to the silently passing rule that follows, and redirect the intent onto an indexed tag such as scenario, group or a custom one.
Decide how a team treats warnings that leave a green build: a threshold that cannot ever observe a sample is worse than none, because it is read as evidence that something was measured.
## Indexed tags and non-indexed metadata Every metric sample k6 emits carries two separate bags of string key-value data. The **indexed tags** are part of the sample's time series: they are what outputs group by, and they are the only thing a threshold selector can test. The **metadata** bag is deliberately outside the time series, for values whose cardinality would explode the number of series if they were indexed. k6 decides which bag a system tag goes into from a fixed set: `vu` and `iter` are the non-indexable ones, and they are routed to metadata every time they are emitted. A threshold selector is matched by asking whether the selector's tag set is a subset of the sample's **indexed** tag set, and metadata is not in that set. So `http_req_duration{vu:3}` cannot match any sample, in any run, under any configuration. ## Two separate barriers, not one It is worth keeping these apart, because the first one is easy to fix and the second is not: - **`vu` and `iter` are not emitted by default.** k6's default system tag set enables fourteen tags; `vu`, `iter`, `ip` and `ocsp_status` are opt-in through the `systemTags` option. - **`vu` and `iter` are non-indexable even when enabled.** Turning them on with `systemTags` makes them appear in outputs that carry metadata, but it does not put them into the tag set a selector tests. The contrast with the other two opt-in tags is the useful part. `ip` and `ocsp_status` are ordinary indexable tags that simply happen to be off by default, so once `systemTags` includes them a selector such as `http_req_duration{ip:10.0.0.7}` works normally. `vu` and `iter` never become selectable. ## What k6 actually reports When the metrics engine builds a sub-metric whose tag set contains `vu` or `iter`, it emits a warning naming the offending threshold, once per sub-metric: > The high-cardinality 'vu' metric tag was made non-indexable in k6 v0.41.0, so thresholds like 'http_req_duration{vu:3}' that are based on it won't work correctly. Three details in that line are worth noticing. It is a **warning**, not an error: the run starts, the threshold is registered, and the test proceeds. It names the version in which the change landed, `v0.41.0`, which reads oddly in a v2 binary but is simply historical. And it is emitted only the first time that sub-metric is created, so it will not repeat. After the warning, the threshold behaves like any other rule over a permanently empty slice. The in-run evaluation skips it because its sink is empty; the final evaluation judges it, and an empty Trend reports zero for every aggregation, so the usual `p(95)<500` shape passes. The verdict is green and the rule measured nothing. ## Where the docs will not help you The k6 v2.2.x documentation lists `vu` and `iter` in the table of optional system tags with the note "enable them as needed", and says nothing about non-indexability or about thresholds. A reader who follows that page, enables `vu` in `systemTags` and then writes a per-VU threshold gets no error, no failing test, and a warning line that is easy to lose in the startup output. The binary is the authority here, not the tag table. ## What to do instead If the intent behind a per-VU or per-iteration threshold is to catch one virtual user or one iteration behaving differently from the rest, a selector is the wrong instrument, because the sub-metric is a single aggregate sink and cannot separate them anyway. Practical alternatives: 1. **Scope on an indexed tag that carries the distinction you actually care about** - `scenario`, `group`, `name`, `status`, or a custom tag your script sets on the requests in question. 2. **Send the per-VU detail to an output rather than a threshold.** Enabling `vu` in `systemTags` still gets the value into outputs that carry metadata, where it can be inspected after the fact. 3. **Treat the startup warning as a build failure in CI.** It is the only signal k6 gives, and a scoped threshold that silently passes is worse than no threshold, because it is read as evidence. ## The rule to carry away A k6 threshold selector can only test what is in a sample's indexed tag set. Being a documented system tag is not sufficient, and enabling a tag is not sufficient either: `vu` and `iter` are metadata by design, so a selector on them is a rule that can never observe a sample and can therefore never fail for the right reason.
- Does enabling vu through the systemTags option make the selector work?No. `systemTags` controls whether the value is emitted at all, not which bag it lands in. `vu` and `iter` are routed to a sample's non-indexed metadata whenever they are emitted, and a selector only tests the indexed tag set, so the sub-metric stays empty either way.
- Are ip and ocsp_status the same kind of problem, since they are also off by default?No, and the contrast is the point. Both are ordinary indexable tags that simply are not in the default set. Add them to `systemTags` and a selector such as `http_req_duration{ip:10.0.0.7}` matches normally, with no warning. Only `vu` and `iter` are non-indexable.
- Is the warning enough to fail a CI run?Not on its own - k6 exits normally and the threshold reports a pass, so a pipeline that only checks the exit code sees green. Treat the line as a build failure yourself, or pair the rule with a Counter existence threshold that breaches when the slice is empty.
saying these in an interview costs you the question
- Thinking systemTags makes vu selectable once it is enabled
- Expecting k6 to fail the config instead of warning about it
- Assuming every documented system tag can be used in a selector
- Claiming the vu selector matches only virtual user number three
- Reading the passing threshold as evidence that VU 3 was fast