What happened?
As a Jaeger operator, I want queries to return only traces that exactly match the requested service, operation, and tags, so that search results are correct and do not include unrelated traces.
Badger's secondary index keys are constructed by concatenating fields without any delimiter or length prefix. Different combinations of service names, operation names, tag keys, and tag values can therefore produce identical index keys. When this happens, queries silently return trace IDs that belong to a different service, operation, or tag combination. No error is returned, and nothing is logged.
For example, service="checkout" with tag id="123" and service="checkout" with tag id1="23" both encode to checkoutid123. The same applies to the operation index: service="A", operation="Bc" and service="AB", operation="c" both encode to ABc.
The keys are built at writer.go:74 (operation index) and writer.go:85,89,94 (tag index), and the reader rebuilds the same concatenation to seek at reader.go:277 (operation) and reader.go:267 (tag).
ADR-005 (docs/adr/005-badger-storage-record-layouts.md:106) states the reader guards against this ambiguity by checking the full key prefix up to the timestamp matches exactly. That check exists at reader.go:547 (bytes.Equal on the key prefix), but it cannot catch this case: collided fields produce byte-identical prefixes rather than a mismatch, so bytes.Equal returns true and the entry passes. The ADR's own example, "foo"+"bar" vs "foobar"+"", concatenates identically and would also pass.
For the tag index, the ADR makes no correctness claim, describing it only as a latent source of subtle bugs, which matches the code: no equivalent check exists there.
Steps to reproduce
- Write a span with service="checkout" and tag id="123".
- Write a second span with service="checkout" and tag id1="23".
Both encode to the index key bytes "checkoutid123".
- Query FindTraceIDs for tag id=123. Both traces are returned.
The same applies to the operation index: service="A", operation="Bc" and service="AB", operation="c" both encode to "ABc".
Reproduced by two tests I've added to internal/storage/v1/badger/spanstore/read_write_test.go, both failing on main: TestFindTraceIDs_TagIndexKeyCollision and TestFindTraceIDs_OperationIndexKeyCollision. The rest of the Badger suite passes.
Expected behavior
Queries should return only traces matching the exact requested service, operation, and tag key/value combination. Different field combinations that happen to concatenate to the same bytes must not match.
In the example above, querying for tag id=123 should return only the trace carrying that tag, not the one tagged id1=23.
Relevant log output
=== RUN TestFindTraceIDs_TagIndexKeyCollision
read_write_test.go:334:
Error: []model.TraceID{{Low:0x2, High:0x1}, {Low:0x1, High:0x1}} should not contain model.TraceID{Low:0x2, High:0x1}
Messages: query for tag id=123 incorrectly matched tag id1=23 due to
delimiter-less tag index key concatenation
read_write_test.go:336:
Error: "[...0002 ...0001]" should have 1 item(s), but has 2
Messages: expected exactly 1 trace id (trace1) to match
--- FAIL: TestFindTraceIDs_TagIndexKeyCollision (0.01s)
=== RUN TestFindTraceIDs_OperationIndexKeyCollision
read_write_test.go:385:
Error: []model.TraceID{{Low:0x2, High:0x1}, {Low:0x1, High:0x1}} should not contain model.TraceID{Low:0x2, High:0x1}
Messages: query for service=A,operation=Bc incorrectly matched
service=AB,operation=c due to delimiter-less operation
index key concatenation
read_write_test.go:387:
Error: "[...0002 ...0001]" should have 1 item(s), but has 2
Messages: expected exactly 1 trace id (trace1) to match
--- FAIL: TestFindTraceIDs_OperationIndexKeyCollis
Screenshot
No response
Additional context
Possible fix
Encode field boundaries in the index key, either by length-prefixing each field or by inserting a separator byte. Length-prefixing seems more robust, since no byte value is genuinely forbidden in a service name, tag key, or tag value. This touches six call sites: four in writer.go and two in reader.go, ideally through a shared helper so the writer and reader cannot drift apart.
On-disk format change
This changes the index key format. I checked both internal/storage/v1/badger and internal/storage/v2/badger for a versioning or migration scheme and found none: no format version byte and no migration path.
In practice, the impact looks limited. Per ADR-005, Ephemeral defaults to true (temp directory, removed on process exit), and TTL.Spans otherwise defaults to 72 hours, so old-format index entries age out within one TTL window. Stale index entries also fail closed rather than returning incorrect data. Flagging it in case maintainers would prefer it handled differently.
I'd be happy to work on this if the approach sounds right.
Jaeger backend version
main, unreleased, post-v2.20.0 (commit 33d2964)
SDK
No response
Pipeline
No response
Stogage backend
Badger (v1 native span store)
Operating system
Linux (Ubuntu on WSL2)
Deployment model
No response
Deployment configs
What happened?
As a Jaeger operator, I want queries to return only traces that exactly match the requested service, operation, and tags, so that search results are correct and do not include unrelated traces.
Badger's secondary index keys are constructed by concatenating fields without any delimiter or length prefix. Different combinations of service names, operation names, tag keys, and tag values can therefore produce identical index keys. When this happens, queries silently return trace IDs that belong to a different service, operation, or tag combination. No error is returned, and nothing is logged.
For example,
service="checkout"with tagid="123"andservice="checkout"with tagid1="23"both encode tocheckoutid123. The same applies to the operation index:service="A", operation="Bc"andservice="AB", operation="c"both encode toABc.The keys are built at
writer.go:74(operation index) andwriter.go:85,89,94(tag index), and the reader rebuilds the same concatenation to seek atreader.go:277(operation) andreader.go:267(tag).ADR-005 (
docs/adr/005-badger-storage-record-layouts.md:106) states the reader guards against this ambiguity by checking the full key prefix up to the timestamp matches exactly. That check exists atreader.go:547(bytes.Equalon the key prefix), but it cannot catch this case: collided fields produce byte-identical prefixes rather than a mismatch, sobytes.Equalreturns true and the entry passes. The ADR's own example,"foo"+"bar"vs"foobar"+"", concatenates identically and would also pass.For the tag index, the ADR makes no correctness claim, describing it only as a latent source of subtle bugs, which matches the code: no equivalent check exists there.
Steps to reproduce
Both encode to the index key bytes "checkoutid123".
The same applies to the operation index: service="A", operation="Bc" and service="AB", operation="c" both encode to "ABc".
Reproduced by two tests I've added to internal/storage/v1/badger/spanstore/read_write_test.go, both failing on main: TestFindTraceIDs_TagIndexKeyCollision and TestFindTraceIDs_OperationIndexKeyCollision. The rest of the Badger suite passes.
Expected behavior
Queries should return only traces matching the exact requested service, operation, and tag key/value combination. Different field combinations that happen to concatenate to the same bytes must not match.
In the example above, querying for tag id=123 should return only the trace carrying that tag, not the one tagged id1=23.
Relevant log output
=== RUN TestFindTraceIDs_TagIndexKeyCollision read_write_test.go:334: Error: []model.TraceID{{Low:0x2, High:0x1}, {Low:0x1, High:0x1}} should not contain model.TraceID{Low:0x2, High:0x1} Messages: query for tag id=123 incorrectly matched tag id1=23 due to delimiter-less tag index key concatenation read_write_test.go:336: Error: "[...0002 ...0001]" should have 1 item(s), but has 2 Messages: expected exactly 1 trace id (trace1) to match --- FAIL: TestFindTraceIDs_TagIndexKeyCollision (0.01s) === RUN TestFindTraceIDs_OperationIndexKeyCollision read_write_test.go:385: Error: []model.TraceID{{Low:0x2, High:0x1}, {Low:0x1, High:0x1}} should not contain model.TraceID{Low:0x2, High:0x1} Messages: query for service=A,operation=Bc incorrectly matched service=AB,operation=c due to delimiter-less operation index key concatenation read_write_test.go:387: Error: "[...0002 ...0001]" should have 1 item(s), but has 2 Messages: expected exactly 1 trace id (trace1) to match --- FAIL: TestFindTraceIDs_OperationIndexKeyCollisScreenshot
No response
Additional context
Possible fix
Encode field boundaries in the index key, either by length-prefixing each field or by inserting a separator byte. Length-prefixing seems more robust, since no byte value is genuinely forbidden in a service name, tag key, or tag value. This touches six call sites: four in
writer.goand two inreader.go, ideally through a shared helper so the writer and reader cannot drift apart.On-disk format change
This changes the index key format. I checked both
internal/storage/v1/badgerandinternal/storage/v2/badgerfor a versioning or migration scheme and found none: no format version byte and no migration path.In practice, the impact looks limited. Per ADR-005,
Ephemeraldefaults totrue(temp directory, removed on process exit), andTTL.Spansotherwise defaults to 72 hours, so old-format index entries age out within one TTL window. Stale index entries also fail closed rather than returning incorrect data. Flagging it in case maintainers would prefer it handled differently.I'd be happy to work on this if the approach sounds right.
Jaeger backend version
main, unreleased, post-v2.20.0 (commit 33d2964)
SDK
No response
Pipeline
No response
Stogage backend
Badger (v1 native span store)
Operating system
Linux (Ubuntu on WSL2)
Deployment model
No response
Deployment configs