Skip to content

[FLINK-41110][core] Compare UTF-8 bytes to find VARIANT object fields - #29580

Closed
raminqaf wants to merge 1 commit into
apache:masterfrom
raminqaf:variant-field-byte-equality
Closed

raminqaf wants to merge 1 commit into
apache:masterfrom
raminqaf:variant-field-byte-equality

Conversation

@raminqaf

Copy link
Copy Markdown
Contributor

What is the purpose of the change

Variant.getField decoded every metadata key it compared into a new String. v['a'] in SQL calls it once per record, so one lookup in a 13-key object could create 13 throwaway strings.

For objects below 32 fields, getField now encodes the wanted key once and compares UTF-8 bytes with the metadata dictionary. The binary search for larger objects is unchanged. It searches in the builder's String.compareTo order, which is not the UTF-8 byte order the Parquet Variant spec asks for. Fixing that order is a separate change.

JMH on JDK 17, 4 lookups per row of 13 keys:

master this PR
time per row 285 ns 132 ns
allocated per row 1368 B 296 B

Two edge cases change below the threshold. Both are pinned by the new tests:

Lookup master this PR
getField("a\uD800") on a field written with that key null found
getField("�") on a key whose bytes are not valid UTF-8 found null

Flink never writes keys that are not valid UTF-8. Two distinct malformed keys already decoded to the same name.

Brief change log

  • BinaryVariant.getFieldByKey compares UTF-8 bytes in its linear search.
  • BinaryVariantUtil adds encodeKey, which addKey in the builder now uses too, and metadataKeyEquals. getMetadataKey shares the key range lookup with it.

Verifying this change

This change added tests and can be verified as follows:

  • BinaryVariantTest#testGetFieldWithUnpairedSurrogate, #testGetFieldWithEmptyKey and #testGetFieldWithMalformedUtf8Key.
  • The existing VARIANT tests in flink-core and flink-table-common cover hits, missing keys, prefix keys and multi-byte keys.

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): no
  • The public API, i.e., is any changed class annotated with @Public(Evolving): no
  • The serializers: no
  • The runtime per-record code paths (performance sensitive): yes
  • Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: no
  • The S3 file system connector: no

Documentation

  • Does this pull request introduce a new feature? no
  • If yes, how is the feature documented? not applicable

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code 2.1.309 (Claude Opus 5.5)

@flinkbot

flinkbot commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands The @flinkbot bot supports the following commands:
  • @flinkbot run azure re-run the last Azure build

@raminqaf

Copy link
Copy Markdown
Contributor Author

@flinkbot run azure

Variant.getField decoded each key it compared into a new String. Finding a field in a 13-key object could create 13 throwaway strings.

For objects below BINARY_SEARCH_THRESHOLD fields, getField now encodes the wanted key once and compares it with the UTF-8 bytes of the metadata dictionary. Larger objects still decode each key and compare Strings, because the binary search depends on the order in which the builder sorts the keys.

The wanted key is encoded the same way the builder encodes the keys it writes. So below the threshold, getField("a\uD800") now finds the field stored as "a?". Larger objects still miss such a key.

Metadata keys that are not valid UTF-8 no longer match the U+FFFD name that getFieldNames lists for them, in objects below the threshold. Flink does not write such keys, and distinct malformed keys already decoded to the same name.

JMH on JDK 17, 4 lookups per row of 13 keys: 285 ns to 132 ns per row, 1368 to 296 bytes allocated.

Generated-by: Claude Code 2.1.309 (Claude Opus 5.5)
@raminqaf
raminqaf force-pushed the variant-field-byte-equality branch from 4b611aa to ec2ef48 Compare October 24, 2026 06:26
Comment on lines +368 to +369
// Compare decoded Strings here. The builder sorts fields by
// String.compareTo, which is not UTF-8 byte order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

And this is the follow up PR: #29607

@twalthr twalthr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks @raminqaf

@raminqaf

Copy link
Copy Markdown
Contributor Author

Merged as: 82834bb

@raminqaf raminqaf closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants