Skip to content

Retrieve routing hash from synthetic id for translog operations#140221

Merged
tlrx merged 7 commits intoelastic:mainfrom
tlrx:2026/01/06/ES-13603-test
Jan 8, 2026
Merged

Retrieve routing hash from synthetic id for translog operations#140221
tlrx merged 7 commits intoelastic:mainfrom
tlrx:2026/01/06/ES-13603-test

Conversation

@tlrx
Copy link
Member

@tlrx tlrx commented Jan 6, 2026

In a TSDB datastream shard that uses synthetic id, the operations that are read from the translog have no routing set. They should have a routing computed from the routing hash stored as a suffix in the synthetic id.

This change fix the routing hash to be retrieved correctly and also adds a test.

Relates ES-13606

In a TSDB datastream shard that uses synthetic id, the operations that are read
from the translog have no routing set. They should have a routing computed from
the routing hash stored as a suffix in the synthetic id.

This change fix the routing hash to be retrieved correctly and also adds a test.

Relates ES-13606
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-storage-engine (Team:StorageEngine)

@elasticsearchmachine
Copy link
Collaborator

Hi @tlrx, I've created a changelog YAML for you.

assertThat(luceneSnapshot.totalOperations(), equalTo(docsIdsBySeqNo.size()));
// TODO Once ES-13603 is implemented, change this to also check operations (and maybe tombstone doc too?)
if (docsIdsBySeqNo.isEmpty() == false) {
expectThrows(NullPointerException.class, luceneSnapshot::next);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will also be useful for ES-13603 , shown here as an expected issue: the stored _id field is not materialized yet and an NPE is thrown.

@tlrx tlrx unassigned fcofdez and burqen Jan 6, 2026
@tlrx tlrx requested review from burqen and fcofdez January 6, 2026 16:26
Copy link
Contributor

@fcofdez fcofdez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Subtle stuff... thanks for fixing this!

);
if (context.indexSettings().useTimeSeriesSyntheticId()) {
int hash = TsidExtractingIdFieldMapper.extractRoutingHashFromSyntheticId(Uid.encodeId(context.sourceToParse().id()));
routingHash = encode(hash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this got really complex, but I find it a bit confusing that we're using two encode methods for the routing hash (synthetic and the regular one using the Uid.encode), not much that we can do... :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure we share the same understanding:

  • Base64 encoding is used to encode/decode the routing hash integer as a routing String key in several places (mostly when routing operations or parsing documents from source like here)
  • Base64 encoding is also used to encode the (regular or synthetic) id from byte[] array to String and vice-versa
  • Uid.encode and Uid.decode are used to encode the (regular or synthetic) String id into the binary data stored in Lucene (as byte[] array too in BytesRef)

Here we need to extract the routing hash integer value back from the (regular or synthetic) String id. In order to have only one "extract routing hash from id" method I reused TsidExtractingIdFieldMapper.extractRoutingHashFromSyntheticId but it means that I have to Uid.encode the String back to its Lucene represention.

Maybe it would be more readable if I only read the last 4 bytes of the id (like regular id do)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my understanding too, but I would prefer to keep it as it's so we try to centralize the knowledge on how we encode the _id.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too. I added a comment.

"Lucene document [" + expectedDocId + "] has wrong value for _ts_routing_hash field",
luceneDocument.getField(TimeSeriesRoutingHashFieldMapper.NAME).binaryValue(),
equalTo(
Uid.encodeId(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can extract this to a helper method, feels like inception ⏳ haha

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 519be37


Translog.Operation operation;
while ((operation = translogSnapshot.next()) != null) {
if (operation instanceof Translog.Index index) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could use the Translog.Operation#opType and a switch instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 519be37

Arrays.copyOf(Base64.getUrlDecoder().decode(context.sourceToParse().id()), 4)
);
if (context.indexSettings().useTimeSeriesSyntheticId()) {
int hash = TsidExtractingIdFieldMapper.extractRoutingHashFromSyntheticId(Uid.encodeId(context.sourceToParse().id()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we decode the base64 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The String id is already encoded as Base64 and we encoded it using Uid.encodeId to have the binary data that would be indexed in Lucene and that the extractRoutingHashFromSyntheticId expects.

See my other comment here, I get how it can be confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 I was fooled by the encodeId name, which looking into the implementation does decode the base64.

@@ -107,7 +107,7 @@ public static ParsedDocument deleteTombstone(
// _id terms over tombstones also work as if a regular _id field was present.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that we could avoid the Uid.encode in:

            document.add(IdFieldMapper.syntheticIdField(id));

Since we have the uid already?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8a2c585

@tlrx tlrx merged commit b1146ee into elastic:main Jan 8, 2026
35 checks passed
@tlrx tlrx deleted the 2026/01/06/ES-13603-test branch January 8, 2026 11:48
@tlrx
Copy link
Member Author

tlrx commented Jan 8, 2026

Thanks Francisco!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment