Skip to content

feat(index): add vector index option surface and validation - #19310

Open
chrevanthreddy wants to merge 1 commit into
apache:masterfrom
chrevanthreddy:rfc-109-pr01-ddl-options
Open

feat(index): add vector index option surface and validation#19310
chrevanthreddy wants to merge 1 commit into
apache:masterfrom
chrevanthreddy:rfc-109-pr01-ddl-options

Conversation

@chrevanthreddy

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

Resolves #19096 (umbrella: #19094, RFC-109)

First foundational slice of RFC-109 (Hudi Native Vector Index): the option surface and validation for vector indexes. This PR is intentionally scoped to the self-contained option/config layer so it compiles and tests standalone; the DDL command dispatch, MDT schema/payload, and bootstrap execution land in dependent follow-up PRs (#19097, #19099, …) where their partition-type and executor dependencies are introduced.

Summary and Changelog

  • Add org.apache.hudi.common.index.vector.VectorIndexOptions — the full OPTIONS(...) key surface for CREATE INDEX ... USING VECTOR (dimension, metric, algorithm, quantizer, cluster/probe/refine counts, RaBitQ bits/seed/storage/posting layout, search-time flags) with typed accessors, defaults, and early validation.
  • Add VectorIndexType — routing algorithm enum (IVFFLAT, IVFPQ_HNSW) with case/separator-insensitive parsing.
  • Add VectorDistanceMetricCOSINE / L2 / DOT_PRODUCT distance enum (min-heap-consistent distances) with parsing.
  • Add PARTITION_NAME_VECTOR_INDEX / PARTITION_NAME_VECTOR_INDEX_PREFIX constants to HoodieTableMetadataUtil.
  • Add TestVectorIndexOptions — 9 unit tests covering defaults, metric/algorithm parsing, and early-failure on invalid configs (missing dimension, invalid metric, out-of-range RaBitQ bits).

Impact

No behavioral impact on existing tables or code paths. Adds new, unreferenced option/enum classes plus two constants. No schema, writer, reader, or DDL execution changes in this PR.

Risk Level

none

New self-contained classes + two constants + unit tests; nothing wires into existing runtime paths yet.

Documentation Update

Option semantics are documented as Javadoc on each option key. The user-facing RFC document is tracked separately (see #19309). No site docs change required until the feature is user-invokable.

Contributor's checklist

@github-actions github-actions Bot added the size:L PR with lines of changes in (300, 1000] label Jul 16, 2026

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR adds the option/config surface and validation for vector indexes (VectorIndexOptions, VectorIndexType, VectorDistanceMetric) plus two MDT partition-name constants, with nothing wired into runtime paths yet. A couple of things worth double-checking in the inline comments — mainly locale-sensitive case parsing and whether the required dimension value should be range-validated. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. Two small naming nits worth a quick look.

* @return matching enum constant
*/
public static VectorDistanceMetric fromString(String name) {
return valueOf(name.toUpperCase().replace(" ", "_").replace("-", "_"));

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.

🤖 name.toUpperCase() uses the default locale here. In a Turkish-locale JVM "cosine"/"ivfflat" uppercase to COSİNE/İVFFLAT (dotted İ), so valueOf(...) throws on otherwise-valid input. Elsewhere in hudi-common this is pinned to Locale.ROOT (e.g. WriteConcurrencyMode, WriteOperationType). Same pattern applies to VectorIndexType.fromString (line 57) and the toUpperCase()/toLowerCase() calls in VectorIndexOptionsgetQuantizer (293), getRaBitQStorage (346), getSearchMode (376). Could you pin these to Locale.ROOT?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

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.

Fixed by using Locale.ROOT for metric and algorithm parsing; quantizer normalization now uses Locale.ROOT as well.

throw new IllegalArgumentException(
"Vector index requires '" + DIMENSION + "' in OPTIONS");
}
return Integer.parseInt(v);

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.

🤖 Since this PR is the validation layer, would it be worth also checking dimension > 0 here? Right now 'vector.dimension' = '0' or a negative value parses cleanly and only surfaces much later during index build (e.g. a NegativeArraySizeException when allocating float[dimension]), far from the config site. getRaBitQBits already does an early range check, so a matching guard here would keep the fail-early story consistent.

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

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.

Added an explicit dimension > 0 validation and focused coverage for zero and negative dimensions.

*/
public enum VectorDistanceMetric implements Serializable {

/**

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.

🤖 nit: all Java enums already implement java.io.Serializable by contract, so implements Serializable here is redundant — could you drop it (and the import) to avoid implying there's something special about the serialization of this enum?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

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.

Removed the redundant Serializable declaration and import.

public static final int DEFAULT_REFINE_FACTOR = 10;
public static final int DEFAULT_MAX_ITER = 20;
public static final long DEFAULT_RABITQ_SEED = 42L;
public static final int DEFAULT_RABITQ_BITS = 2;

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.

🤖 nit: all other defaults follow the DEFAULT_<KEY_NAME> pattern (e.g. NUM_CLUSTERSDEFAULT_NUM_CLUSTERS), but here the key is RABITQ_RANDOM_SEED while the default is DEFAULT_RABITQ_SEED (drops _RANDOM). Could you rename this to DEFAULT_RABITQ_RANDOM_SEED for consistency?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

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.

Renamed the default to DEFAULT_RABITQ_RANDOM_SEED and updated all uses.

@chrevanthreddy
chrevanthreddy force-pushed the rfc-109-pr01-ddl-options branch from de7e166 to cda1749 Compare July 27, 2026 19:30

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR adds the self-contained option/config surface and early validation for CREATE INDEX ... USING VECTOR (RFC-109), plus two new MDT partition-name constants — no runtime wiring yet. The logic is straightforward; a couple of consistency points around locale-safe normalization and validation of the string-typed options are worth double-checking in the inline comments. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. A few naming-consistency nits in the boolean config-reader helpers.

}

public static String getRaBitQStorage(Map<String, String> opts) {
return opts.getOrDefault(RABITQ_STORAGE, DEFAULT_RABITQ_STORAGE).toLowerCase();

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.

🤖 This .toLowerCase() (and the one in getSearchMode below) still uses the default locale — the earlier metric/algorithm/quantizer fixes switched to Locale.ROOT for exactly this reason. On a Turkish-locale JVM an input like HIDDEN_COLUMNS or APPROXIMATE lowercases to hıdden_columns/approxımate (dotless ı) and silently misses the equality check. Could you use toLowerCase(Locale.ROOT) here too?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

}

public static String getSearchMode(Map<String, String> opts) {
return opts.getOrDefault(SEARCH_MODE, DEFAULT_SEARCH_MODE).toLowerCase();

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.

🤖 Since this PR is specifically the validation layer, I wonder if the string-typed enum-like options (quantizer, storage, search mode, offset-index policy) should also fail fast on unknown values the way metric/algorithm do via valueOf. Right now 'vector.search.mode' = 'aproximate' silently falls back to exact, and 'vector.rabitq.storage' = 'hiden_columns' silently disables MDT storage — both easy to miss. Worth validating against the allowed set here?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

return "approximate".equals(getSearchMode(opts));
}

public static boolean isDataTableClusterByIvf(Map<String, String> opts) {

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.

🤖 nit: this class already uses should… for boolean config readers (shouldMaterializeRaBitQColumnsOnCreate, shouldStoreRaBitQCodesInMdt) — could you rename isDataTableClusterByIvf, isRaBitQResidualEncoding, and isRaBitQAsymmetricScoring to shouldClusterDataTableByIvf, shouldUseRaBitQResidualEncoding, and shouldUseRaBitQAsymmetricScoring to make the convention consistent throughout the class?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@codecov-commenter

codecov-commenter commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 53.57143% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.11%. Comparing base (4a5d5b0) to head (cda1749).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...hudi/common/index/vector/VectorDistanceMetric.java 17.24% 24 Missing ⚠️
...e/hudi/common/index/vector/VectorIndexOptions.java 70.58% 14 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19310      +/-   ##
============================================
+ Coverage     70.49%   72.11%   +1.62%     
+ Complexity    32775    32549     -226     
============================================
  Files          2700     2564     -136     
  Lines        152896   148389    -4507     
  Branches      19009    18659     -350     
============================================
- Hits         107778   107012     -766     
+ Misses        36596    32914    -3682     
+ Partials       8522     8463      -59     
Components Coverage Δ
hudi-common 81.34% <53.57%> (-0.07%) ⬇️
hudi-client 81.55% <ø> (-0.03%) ⬇️
hudi-flink 78.58% <ø> (-0.05%) ⬇️
hudi-spark-datasource 58.39% <ø> (+0.01%) ⬆️
hudi-utilities 70.42% <ø> (+0.04%) ⬆️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.49% <ø> (ø)
hudi-sync 70.62% <ø> (ø)
hudi-io 79.57% <ø> (ø)
hudi-timeline-service 83.44% <ø> (-0.30%) ⬇️
hudi-cloud 64.00% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 46.69% <53.57%> (+0.59%) ⬆️
flink-integration-tests 47.66% <0.00%> (+0.11%) ⬆️
hadoop-mr-java-client 43.27% <0.00%> (-0.06%) ⬇️
integration-tests 13.58% <0.00%> (+<0.01%) ⬆️
spark-client-hadoop-common 48.65% <0.00%> (-0.06%) ⬇️
spark-java-tests 49.32% <53.57%> (+0.07%) ⬆️
spark-scala-tests 44.61% <0.00%> (-0.05%) ⬇️
utilities 36.45% <0.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ache/hudi/common/index/vector/VectorIndexType.java 100.00% <100.00%> (ø)
.../apache/hudi/metadata/HoodieTableMetadataUtil.java 82.19% <ø> (ø)
...e/hudi/common/index/vector/VectorIndexOptions.java 70.58% <70.58%> (ø)
...hudi/common/index/vector/VectorDistanceMetric.java 17.24% <17.24%> (ø)

... and 163 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

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

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

Labels

size:L PR with lines of changes in (300, 1000]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add vector index options and Spark DDL scaffolding

4 participants