## TL;DR
PromQL refuses to guess which series on the many side pairs with each series on the one side, so it demands an explicit group_left or group_right modifier. Add the modifier naming the extra labels to carry through, and the vector match becomes deterministic.

```text
multiple matches for labels: many-to-one matching must be explicit (group_left/group_right)
```

## Steps

1. Find the cardinality mismatch by counting series on each side:
```bash
curl -s --data-urlencode "query=count([LEFT_EXPR])" http://YOUR-PROM_HOST:9090/api/v1/query
curl -s --data-urlencode "query=count([RIGHT_EXPR])" http://YOUR-PROM_HOST:9090/api/v1/query
```
Expected: different counts, confirming one side has many series per label set of the other side.

2. Decide which side is the one and which is the many:
```bash
curl -s --data-urlencode "query=count by ([JOIN_LABELS]) ([LEFT_EXPR])" http://YOUR-PROM_HOST:9090/api/v1/query
```
Expected: the grouping shows whether the left or right side fans out per join label, telling you whether you need group_left or group_right.

3. Add the explicit modifier with the labels to carry from the many side:
```promql
[LEFT_EXPR] * on([JOIN_LABELS]) group_left([EXTRA_LABEL]) [RIGHT_EXPR]
```
Expected: the query parses and returns one result per many-side series, each enriched with the one-side value.

4. Verify the result shape matches your intent:
```bash
curl -s --data-urlencode "query=[FIXED_EXPR]" http://YOUR-PROM_HOST:9090/api/v1/query | python3 -c "import json,sys; r=json.load(sys.stdin)['data']['result']; print(len(r), 'series'); print(sorted(r[0]['metric'].keys()))"
```
Expected: the series count equals the many side's count and the extra label is present on each series.

5. Save the corrected query back into the dashboard, rule, or alert:
```bash
# update the panel JSON, recording rule, or alerting rule with the fixed expression
```
Expected: the panel renders and the rule evaluates without the matching error.

## When to use
- A PromQL binary operation fails with the many-to-one matching error
- You are joining per-pod metrics with per-node or per-deployment info
- An info metric like kube_pod_info is multiplied into resource metrics

## When not to use
- The error is about no matching labels at all; that is a label name mismatch, not cardinality
- Both sides already match one-to-one; then the problem is elsewhere in the expression
- You want an aggregation, not a join; sum by the shared labels instead of matching

## Why it happens
Vector matching pairs series by their label sets, and when several series on one side share the join labels of a single series on the other, there is no unique pairing. Rather than pick silently, PromQL requires group_left or group_right so the author states the intended direction and which extra labels survive the join.

## Edge cases
- group_left without a label list drops all many-side labels except the join labels; name the labels you need or the result loses dimensions you wanted
- Ignoring labels with `on()` changes the join key; a mismatch between `on()` labels and the actual shared labels still errors
- The one side must have exactly one series per join label set; if it also fans out you get the same error and need to aggregate first
- Recording rules that change cardinality break downstream queries silently; recheck consumers after adding the modifier

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Eo-u8JLe9XDnsQWXKQHnKg
