In k6, what happens to a threshold on http_req_duration{endpoint:checkout} if no sample ever carries that tag?
answer
- an empty filter is not an error
- empty sinks skipped during the run
- zero satisfies a less-than rule
- guard with a count greater than zero
basics
~20 sThe threshold passes. k6 creates the sub-metric at startup, no sample ever matches it, and on an empty Trend sink every aggregation reads 0 - so p(95)<500 is satisfied and the run ends green with the endpoint unmeasured.
solid answer
~40 sk6 never checks that a selector matches anything. The sub-metric is created before the run, the in-run evaluation that fires every two seconds skips metrics with an empty sink, and the final evaluation reads an empty Trend sink as 0 for `min`, `max`, `avg`, `med` and every `p(N)` - so a `<` comparison passes. An empty Rate produces no value at all, which also counts as passing. Meanwhile a typo in the metric name *in front of* the brace is fatal: k6 looks that up in the registry and refuses to start. The standard guard is a companion rule on a Counter, such as `'http_reqs{endpoint:checkout}': ['count>0']`, which breaches when the slice is empty.
code
javascript · 17 linesimport http from 'k6/http';
const CHECKOUT = { endpoint: 'checkout' };
export const options = {
vus: 5,
duration: '30s',
thresholds: {
'http_req_duration{endpoint:checkout}': ['p(95)<500'],
// breaches if the selector above ever matches nothing
'http_reqs{endpoint:checkout}': ['count>0'],
},
};
export default function () {
http.get('https://quickpizza.grafana.com/api/json', { tags: CHECKOUT });
}go deeper
Remember that a k6 threshold judges only the samples that arrived. If the tag in the braces never appears on a request, there are no samples and nothing fails.
Explain the two evaluation passes, that empty sinks are skipped during the run and judged at the end, and why a Trend reading 0 satisfies a less-than comparison.
Diagnose a green run that proves nothing: name the likely causes of an empty slice, contrast the fatal metric-name typo with the silent tag typo, and add a Counter existence rule.
Argue for a house rule that every scoped rule ships with an existence rule, and be able to say what that costs in extra metrics and in noise when a slice is legitimately empty.
## The failure this question is about A scoped k6 threshold has two independent parts, and only one of them is checked. `'http_req_duration{endpoint:checkout}': ['p(95)<500']` asserts something about a slice of the run — but nothing anywhere asserts that the slice contains anything. If the `endpoint` tag was misspelled, applied to the wrong call, or never applied at all, k6 still creates the sub-metric, still prints it in the end-of-test summary, still evaluates the rule, and still passes. The verdict is green and the endpoint was never measured. ## Why the slice ends up empty The usual causes, in rough order of how often they bite: - **The tag key or value in the threshold does not match the one on the request.** `{endpoint:checkout}` against a request tagged `{ endpoint: 'Checkout' }` matches nothing: values compare as exact, case-sensitive strings. - **The tag was attached to the wrong thing.** A tag passed as the third argument of `check()` lands on the `checks` metric, not on `http_req_duration`, and a tag set on one request never reaches another request's samples. - **The selector names a non-indexable tag.** `vu` and `iter` are stored as sample metadata, not as indexed tags, so no sample's tag set can ever contain them. k6 does warn about this one at startup. - **The tag is a system tag that was switched off.** Narrowing the `systemTags` option removes tags from samples; a `checks{check:...}` selector stops matching the moment the `check` tag is not emitted. - **The requests genuinely did not happen, or all failed.** A request that never received a response is tagged `status:0`, so `{status:200}` legitimately selects nothing when the target is down — the case where you most want the rule to speak up. ## What k6 does with an empty sink Two evaluations exist. A ticker runs every two seconds during the test and **skips metrics whose sink is empty**, so an empty sub-metric is invisible for the whole run. The finaliser runs once after the ingester stops and does **not** skip empty sinks. What happens then depends on the sub-metric's type, which it inherited from its parent, and on the direction of your comparison: | sub-metric type (example parent) | what an empty sink offers the expression | `p(95)<500` or `count<100` | `p(95)>0` or `count>0` | |---|---|---|---| | Trend (`http_req_duration`) | every aggregation reads 0 | passes | breaches | | Counter (`http_reqs`) | `count` reads 0 | passes | breaches | | Gauge (`vus`) | `value` reads 0 | passes | breaches | | Rate (`checks`, `http_req_failed`) | nothing at all is offered | passes | passes | Two things fall out of that table. First, the normal shape of a latency rule is a `<` comparison, and `<` is exactly the direction that an all-zeros sink satisfies — which is why the default outcome of a broken selector is a pass. Second, a Rate sub-metric is worse still: with no samples k6 produces no value for the expression to read, and a threshold whose value is missing is treated as passing in **both** directions. ## The asymmetry that hides the mistake k6 validates the two halves of the key very differently: 1. **The metric name in front of the brace is checked.** k6 looks it up in the registry while consolidating the config; an unknown name is an invalid configuration and the test never starts, with the message *no metric name "..." found*. 2. **The tags inside the braces are not checked against anything.** There is no list of legal tag keys to validate against — custom tags are arbitrary strings invented by your script — so any syntactically well-formed pair is accepted. So `htp_req_duration{endpoint:checkout}` stops the run instantly, while `http_req_duration{endpont:checkout}` runs to completion and reports success. Nothing in k6 warns that a sub-metric received no samples. ## Guarding against it 1. **Pair every scoped latency rule with an existence rule on a Counter.** `'http_reqs{endpoint:checkout}': ['count>0']` breaches when the slice is empty, because an empty Counter reports `count` as 0. Do not use a Rate metric for this: an empty Rate passes. 2. **Keep the tag literal in one place.** Define the tag object once as a constant and reference it from both the request options and, by eye, the threshold key, so a rename cannot silently desynchronise them. 3. **Prove it once in a short smoke run.** The sub-metric is printed in the summary whether or not it has data; a row with no samples is the signal. 4. **Read the startup log.** The `vu` and `iter` warnings are the only ones k6 emits about a selector, and they are easy to scroll past. The underlying rule to carry away: in k6 a threshold is a statement about the samples that arrived, so a filter that admits no samples makes a statement about nothing — and reports it as success.
- Why does an empty sub-metric behave differently during the run and at the end?The in-run evaluation runs on a two-second ticker and deliberately skips any metric whose sink is empty, to avoid judging a rule before data arrives. The final evaluation, which decides the verdict, does not skip them, so an empty sink is judged exactly once - at the end.
- Would 'checks{check:cart loads}': ['rate>0.99'] catch a selector that matches nothing?No, and it is the worst case. `checks` is a Rate, and k6 only publishes a rate when the sink has recorded at least one sample. With no samples the expression has no value to read, and a threshold whose value is missing is treated as passing regardless of the comparison.
- Does k6 log anything when a threshold's sub-metric receives no samples?No. The only selector-related warnings k6 emits are for the non-indexable `vu` and `iter` tags. An empty sub-metric produces no warning at any point, only a summary row with no data next to a rule that passed.
A smoke detector whose wiring never reached the room it was meant to watch. It never sounds, and its silence is indistinguishable from a room that never caught fire.
saying these in an interview costs you the question
- Assuming an unmatched selector fails the run or aborts at startup
- Believing k6 validates tag keys inside the braces against a known list
- Expecting a warning when a sub-metric receives no samples
- Using a Rate metric such as checks as the existence guard
- Treating a green scoped threshold as proof the endpoint was exercised