Repository navigation
Fix prefix escape for synthetic id - #150433
Conversation
|
Hi @dnhatn, I've created a changelog YAML for you. |
|
Pinging @elastic/es-storage-engine (Team:StorageEngine) |
🔍 Preview links for changed docs⏳ Building and deploying preview... View progress This comment will be updated with preview links when the build is complete. |
ℹ️ Important: Docs version tagging👋 Thanks for updating the docs! Just a friendly reminder that our docs are now cumulative. This means all 9.x versions are documented on the same page and published off of the main branch, instead of creating separate pages for each minor version. We use applies_to tags to mark version-specific features and changes. Expand for a quick overviewWhen to use applies_to tags:✅ At the page level to indicate which products/deployments the content applies to (mandatory) What NOT to do:❌ Don't remove or replace information that applies to an older version 🤔 Need help?
|
tlrx
left a comment
There was a problem hiding this comment.
LGTM, thanks for fixing this Nhat!
For your information and in case you haven't seen it, I fixed an escape char related issue in #145018. I think both are complementary and your PR here fixes the handling of the escape char on the input (seekCeil) path, while mine fixed on the output (term) path. Hopefully it's correct everywhere now :)
| // Lookup whatever term `id` has been provided | ||
| tsIdOrd = docValues.lookupTsIdTerm(id); | ||
| private static BytesRef extractTsid(BytesRef id) { | ||
| final int escapeBytes = id.length > 0 && Byte.toUnsignedInt(id.bytes[id.offset]) >= Uid.BASE64_ESCAPE ? 1 : 0; |
There was a problem hiding this comment.
Can we add a bit of documentation explaining what the escape what is and why it needs special handling here?
| } | ||
|
|
||
| // See #createSyntheticId | ||
| public static BytesRef extractTimeSeriesIdFromSyntheticId(BytesRef id) { |
There was a problem hiding this comment.
This one is also used in ParsedDocument. I think it has the nice effect to also strip the escape char for the _tsid that is set to tombstones documents, and that fixes a double-escaping of the value indexed in Lucene.
It also fixes some tests too :)
| } | ||
|
|
||
| public void testSyntheticId() { | ||
| for (int prefix = 0; prefix <= 0xff; prefix++) { |
There was a problem hiding this comment.
Good idea to just test all values 👍
|
Thanks friends! |
💚 Backport successful
|
We have an ES|QL query running for many hours on a tsdb index that is
just 32M docs / 83MB - this should complete within seconds.
```sql
FROM metrics-k8sclusterreceiver.otel-default METADATA _id, _source
| WHERE k8s.deployment.available < k8s.deployment.desired
AND _id NOT IN ()
| LIMIT 1000
```
```
_id: [fd ff c4 6f 4b 15 68 a0 8a 1d 7 b1 38 5f 3f 29 9a 7f ff fe 61 9c de bc b8 87 7a b5 b1]
```
Some ids start with `0xfd`, a special escape byte that is prepended by
`Uid#encodeId` when the first byte is greater than `0xfd`, but
`SyntheticIdTermsEnum#seekCeil` doesn't strip it.
Here is how the infinite loop occurs:
1. `AbstractMultiTermQueryConstantScoreWrapper#collectTerms` with `[fd ff c4 ...]`
2. `FilteredTermsEnum#next` calls `seekCeil` with `[fd ff c4 ...]`
3. `SyntheticIdTermsEnum#seekCeil` extracts the tsid but includes the
`0xfd` escape prefix, producing `[fd ff c4 ...]` instead of the actual
tsid `[ff c4 ...]`
4. `SyntheticIdTermsEnum#seekCeil` positions at a term greater than `[fd
ff c4 ...]`, but `docSyntheticId` synthesizes the Lucene term by
prepending `0xfd` - making it smaller than the seeking term. This
violates the seekCeil contract.
5. `FilteredTermsEnum#next` sees the positioned term is still before the
seeking term and calls seekCeil again - repeating steps 2–4 infinitely.
|
Thanks @dnhatn - please backport this as far as you can, where applicable. 9.3 at minimum. |
synthetic id was introduced in |
|
Yes, I was introduced in 9.4.0. |
We have an ES|QL query running for many hours on a tsdb index that is
just 32M docs / 83MB - this should complete within seconds.
```sql
FROM metrics-k8sclusterreceiver.otel-default METADATA _id, _source
| WHERE k8s.deployment.available < k8s.deployment.desired
AND _id NOT IN ()
| LIMIT 1000
```
```
_id: [fd ff c4 6f 4b 15 68 a0 8a 1d 7 b1 38 5f 3f 29 9a 7f ff fe 61 9c de bc b8 87 7a b5 b1]
```
Some ids start with `0xfd`, a special escape byte that is prepended by
`Uid#encodeId` when the first byte is greater than `0xfd`, but
`SyntheticIdTermsEnum#seekCeil` doesn't strip it.
Here is how the infinite loop occurs:
1. `AbstractMultiTermQueryConstantScoreWrapper#collectTerms` with `[fd ff c4 ...]`
2. `FilteredTermsEnum#next` calls `seekCeil` with `[fd ff c4 ...]`
3. `SyntheticIdTermsEnum#seekCeil` extracts the tsid but includes the
`0xfd` escape prefix, producing `[fd ff c4 ...]` instead of the actual
tsid `[ff c4 ...]`
4. `SyntheticIdTermsEnum#seekCeil` positions at a term greater than `[fd
ff c4 ...]`, but `docSyntheticId` synthesizes the Lucene term by
prepending `0xfd` - making it smaller than the seeking term. This
violates the seekCeil contract.
5. `FilteredTermsEnum#next` sees the positioned term is still before the
seeking term and calls seekCeil again - repeating steps 2–4 infinitely.
The prefix escape fix for synthetic id in #150433 is not enough for two cases: 1. The prefix starts with 0xFE or 0xFF - these terms never exist and we can safely return END. Currently, seekCeil(0xFF_00) calls seekCeil(0x00) internally and the returned term is before 0xFF_00, breaking the seekCeil contract and leading to an infinite loop. 2. The prefix starts with 0xFD, but the second byte is less than 0xFD. For example: 0xFD_00 - currently, we call seekCeil(0x00) internally and the found term (e.g. 0x00_01) is less than 0xFD_00 because no escape prefix is prepended, leading the returned term to be smaller than the seeking term, breaking the seekCeil contract. #150433 focused on valid terms; this change covers invalid terms. Relates #145018 Relates #150433
The prefix escape fix for synthetic id in #150433 is not enough for two cases: 1. The prefix starts with 0xFE or 0xFF - these terms never exist and we can safely return END. Currently, seekCeil(0xFF_00) calls seekCeil(0x00) internally and the returned term is before 0xFF_00, breaking the seekCeil contract and leading to an infinite loop. 2. The prefix starts with 0xFD, but the second byte is less than 0xFD. For example: 0xFD_00 - currently, we call seekCeil(0x00) internally and the found term (e.g. 0x00_01) is less than 0xFD_00 because no escape prefix is prepended, leading the returned term to be smaller than the seeking term, breaking the seekCeil contract. #150433 focused on valid terms; this change covers invalid terms. Relates #145018 Relates #150433
The prefix escape fix for synthetic id in #150433 is not enough for two cases: 1. The prefix starts with 0xFE or 0xFF - these terms never exist and we can safely return END. Currently, seekCeil(0xFF_00) calls seekCeil(0x00) internally and the returned term is before 0xFF_00, breaking the seekCeil contract and leading to an infinite loop. 2. The prefix starts with 0xFD, but the second byte is less than 0xFD. For example: 0xFD_00 - currently, we call seekCeil(0x00) internally and the found term (e.g. 0x00_01) is less than 0xFD_00 because no escape prefix is prepended, leading the returned term to be smaller than the seeking term, breaking the seekCeil contract. #150433 focused on valid terms; this change covers invalid terms. Relates #145018 Relates #150433
We have an ES|QL query running for many hours on a tsdb index that is just 32M docs / 83MB - this should complete within seconds.
Some ids start with
0xfd, a special escape byte that is prepended byUid#encodeIdwhen the first byte is greater than0xfd, butSyntheticIdTermsEnum#seekCeildoesn't strip it.Here is how the infinite loop occurs:
AbstractMultiTermQueryConstantScoreWrapper#collectTermswith[fd ff c4 ...]FilteredTermsEnum#nextcallsseekCeilwith[fd ff c4 ...]SyntheticIdTermsEnum#seekCeilextracts the tsid but includes the0xfdescape prefix, producing[fd ff c4 ...]instead of the actual tsid[ff c4 ...]SyntheticIdTermsEnum#seekCeilpositions at a term greater than[fd ff c4 ...], butdocSyntheticIdsynthesizes the Lucene term by prepending0xfd- making it smaller than the seeking term. This violates the seekCeil contract.FilteredTermsEnum#nextsees the positioned term is still before the seeking term and calls seekCeil again - repeating steps 2–4 infinitely.Closes #150389
Relates #145018