Skip to content

[improvement](storage) Support empty ANN arrays and NULL vector distances - #70552

Draft
yiguolei wants to merge 3 commits into
apache:masterfrom
yiguolei:improvement/ann-empty-array-opt-in
Draft

yiguolei wants to merge 3 commits into
apache:masterfrom
yiguolei:improvement/ann-empty-array-opt-in

Conversation

@yiguolei

@yiguolei yiguolei commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Background

ANN indexes identify vectors by their row IDs in a segment. Empty arrays have no index entry, so omitting them requires preserving those IDs and excluding missing vectors from range-search complements. Distance expressions also run without an ANN index, including when index training is skipped or a query falls back to a scan.

What problem does this PR solve?

Issue Number: None
Related PR: None
PR Type: Enhancement

Problem Summary:

Allow empty arrays in ANN-indexed columns through an explicit, default-off BE option while preserving segment row IDs. Unify array-distance semantics: NULL and empty vectors produce NULL distances, whether they come from data rows or query literals. Both indexed and scalar query paths handle missing vectors consistently; most of the additional changes cover tests and nullable expression results.

Details

  • Change map: 634 changed production lines and 1297 changed test lines; no generated files or documentation changes. Production changes cover ANN writing/persistence and row IDs, range-search domains, Group Commit validation, and FE/BE nullable distance evaluation. Tests cover storage, expression evaluation, and SQL query paths.
  • Before and after: With enable_ann_index_empty_array=false, empty ANN arrays still fail writes. With it enabled, empty rows remain in the data file and retain their positions while nonempty vectors are indexed under their data-file row IDs. Range comparisons exclude empty vectors in both directions. NULL or empty query vectors return NULL distances through scalar evaluation instead of entering FAISS.
  • Configs and defaults: New mutable BE config enable_ann_index_empty_array=false, observed during write validation. After persisting empty arrays, keep the flag enabled across restarts, compaction, index rebuilds and WAL replay. Readers do not consult the flag; toggling it does not convert stored data.
  • Impact on existing users: Distance NULL semantics apply independently of the write flag. Dense indexes retain their native representation, and dense IVF complement searches avoid collecting all inverted-list IDs. The writer retains its existing treatment of historical NULL coordinates. This PR does not enable ANN indexes on nullable columns.
  • Behavior change: Array overloads of l1_distance, l2_distance, inner_product, cosine_distance, cosine_similarity, l2_distance_approximate and inner_product_approximate return nullable FLOAT. Either whole-array NULL or an empty array yields NULL, including [] versus [] and explicit literals. NULL elements in non-NULL arrays and mismatched nonempty dimensions remain errors. Nonempty zero-vector behavior and the MAP overload of inner_product are unchanged.
  • Compatibility: Plan a coordinated FE/BE upgrade and restart. This change intentionally does not preserve the old FE non-nullable result layout and does not claim rolling mixed-version query compatibility. Enable empty-array writes only after all FEs and BEs run the new version. After those writes, downgrading to an old BE is unsupported: deserializing a native FAISS file does not make old complement, HNSW range-search, untrained-IVF and rewrite paths compatible.
  • Index implementation: IVF stores explicit row IDs natively; HNSW uses IDMap for sparse segments. All-empty segments persist an empty index without training. Sparse complement domains are reconstructed lazily from FAISS IDs, including IDs-only reads for IVF_ON_DISK. HNSW range queries translate selectors and output labels around the child index to retain derived search parameters in the pinned FAISS version. FAISS versions and index filenames are unchanged.
  • Query execution: Sparse-segment TopN and NULL/empty query vectors use normal SQL evaluation and sorting to preserve filters, NULL ordering and LIMIT. This patch does not implement the separate optimization that folds a missing query vector to a constant NULL and avoids reading the vector column entirely.

Testing

Passed locally on macOS arm64:

  • Repository clang-format v16 fix/check scripts on a scoped snapshot of all 25 changed C++ files.
  • bash build-support/check-build-hygiene.sh.
  • git diff --cached --check.
  • Passed Checkstyle 9.3 (the version pinned by fe/pom.xml) on all 11 changed Java files, using the repository configuration and standalone JDK 21. This is a style check, not a JDK 17 compilation or test run.

Added or extended tests cover:

  • Writer opt-in rejection without partial buffer mutation, cross-batch labels, all-empty segments and legacy NULL-coordinate writes.
  • HNSW/IVF/IVF_ON_DISK native reload, filtering, both range directions, concurrent row-ID initialization, IO retry, and the dense-IVF fast path.
  • Reader-level all-empty result contracts for both metrics and all index types.
  • Every array-distance function with nullable/non-nullable inputs, data/literal NULLs and empty arrays on either side, constant columns, NULL elements, dimension errors and FE nullable declarations/constant folding.
  • Missing-query extraction and SegmentIterator TopN fallback before accessing ANN readers, with candidate rows preserved.
  • Group Commit off/sync/async cases and differential indexed/scalar SQL checks, including missing query vectors, cached results, NULL ordering and LIMIT.

Attempted this revision but blocked before execution:

  • run-be-ut.sh --run for the ANN writer/reader/search, Group Commit, distance, extraction and SegmentIterator suites.
  • run-fe-ut.sh --run for ArrayDistanceTest, InnerProductTest and ArrayArithmeticTest.
  • run-regression-test.sh --run -d ann_index_p0 -s ann_empty_array_queries,ann_delete_group_commit,cast_string_as_array -forceGenOut.
  • run-regression-test.sh --run -d query_p0/sql_functions/array_functions -s test_array_distance_functions -forceGenOut.

The scripts stop at the JDK 17 prerequisite; the available standalone JDK is 21 and installed BE third-party dependencies are also absent. Compilation, UT and regression results are not verified. Clang-tidy is pending a successful BE build and compilation database. Changed regression .out files have not been regenerated; existing snapshots must be refreshed through the runner before this PR is ready. Recovery/compaction and actual old/new-binary compatibility scenarios also remain unverified. No expected results were handwritten.

Notes for Reviewers

Source self-review checkpoints:

Checkpoint Conclusion
Goal, scope and parallel paths Writer, indexed/scalar distances, query extraction and FE constant folding follow the documented semantics; runtime proof remains pending.
Concurrency and lifecycle Row-domain initialization holds a mutex and publishes only a completed bitmap. RAII owns list borrows and translated selectors; IDMap owns its child. Sparse first-use IO still runs under the initialization lock.
Config and compatibility Write opt-in is dynamic; coordinated FE/BE restart and the downgrade restriction are explicit.
Special conditions All-empty indexes obey the reader pointer contract; missing queries fall back before dimension checks/FAISS; nonempty invalid dimensions remain errors.
Persistence, transactions and protocol Native FAISS persistence retains row IDs. Group Commit validates before accepting blocks into its background path; transaction protocols and Thrift/Proto are unchanged. Crash/WAL behavior needs runtime verification.
Performance and observability Dense IVF avoids whole-index label collection. Sparse first-use bitmaps and scalar TopN add cost; existing ANN fallback counters/timers and contextual writer logs cover these paths. No benchmark claim.
Tests and outputs Tests are added, but have not run; result regeneration and recovery/upgrade tests are outstanding.

Release note

Add default-off enable_ann_index_empty_array support for ANN writes. Array distance and similarity functions return NULL for NULL or empty vectors, including query literals. Missing-query and sparse TopN paths preserve SQL NULL ordering through scalar evaluation. Upgrade and restart all FEs and BEs together; keep the write flag enabled after storing empty arrays, and do not downgrade those data files to an unsupported BE version.

Check List (For Author)

  • Behavior changed: Yes (see Details).
  • Does this need documentation: Yes; document the write flag, NULL semantics, coordinated upgrade and downgrade restriction in the ANN and distance-function documentation.
  • Pre-submission status: Not ready: draft pending compilation, runtime tests, generated regression outputs, recovery and compatibility validation.

AI Disclosure

  • Tool: OpenAI Codex (GPT-6).
  • Generated: Code, tests and this description.
  • Human review: The author specified the final NULL/empty semantics and coordinated-upgrade approach; final implementation review is pending.

### Background

ANN indexes store vector entries using segment row IDs. Empty arrays can occur in delete rows, but the current writer rejects them as dimension mismatches; simply omitting them would shift HNSW labels and make range-search complements include rows without vectors. Group Commit also needs to reject invalid dimensions before accepting blocks into its background write path.

### What problem does this PR solve?

Issue Number: None
Related PR: None
PR Type: Enhancement

Problem Summary:

Add an explicit opt-in for empty arrays in ANN-indexed columns while preserving data-file row IDs. Nonempty vectors retain native IVF labels, and HNSW uses IDMap only when a segment contains empty arrays. Approximate distance functions return NULL for empty arrays, including scalar fallback, and sparse-segment TopN uses normal SQL evaluation and sorting to preserve NULL ordering and LIMIT. Existing writes continue to reject empty arrays by default.

### Details

- **Change map:** 524 changed production lines and 961 changed test lines; no generated files. The production changes cover writer validation/row IDs and native FAISS persistence, range-search domains, Group Commit validation, and nullable approximate-distance evaluation/materialization. Tests cover writer state, FAISS reload/filtering, reader contracts, expression nullability, and SQL differential checks.
- **Before and after:** With the flag disabled, an empty ANN array fails with an actionable error. With it enabled, empty rows remain in the data file but have no ANN entry. Range predicates exclude them on both sides of a comparison; scalar approximate distance returns NULL. Sparse TopN falls back so NULLS FIRST/LAST and limits larger than the vector count remain correct.
- **Configs and defaults:** New mutable BE config `enable_ann_index_empty_array=false`. After writing empty arrays, keep it enabled, including across restarts, compaction, index rebuilds, and Group Commit WAL replay. Readers do not consult the flag.
- **Impact on existing users:** Dense segments retain the existing native index representation and TopN path. Dense IVF complements do not scan all list IDs. Legacy NULL-coordinate writer behavior is preserved so this change does not reject historical rows during rewrites; scalar distance NULL-input errors remain unchanged.
- **Behavior change:** `l2_distance_approximate` and `inner_product_approximate` are nullable in FE and return NULL if either array is empty in scalar evaluation. Nonempty dimension mismatches remain errors. Exact distance functions are unchanged. Sparse-segment TopN trades ANN acceleration for correct SQL NULL handling.
- **Compatibility:** Upgrade BEs first, then FEs; enable empty-array writes only after every FE and BE is upgraded. The new BE preserves the non-nullable output layout of plans from old FEs. Once empty arrays have been written, downgrade to a BE without this support is unsupported: native FAISS deserialization alone does not make old complement, HNSW range-search, empty-IVF, or rewrite paths compatible. Disabling the flag does not convert persisted data. New FEs with old BEs are not a supported mixed-version configuration for this change; rolling back before opt-in requires reverting FEs before BEs and cross-version verification.
- **Implementation:** All-empty segments persist an empty index without training; complement results retain the required null distance/row-ID pointers. Sparse range domains are derived lazily from FAISS IDs, protected by a mutex, and published only after successful initialization. HNSW range queries translate selectors and returned labels around the child index to preserve derived search parameters with the pinned FAISS version. The FAISS dependency and on-disk filenames are unchanged.

### Testing

Passed locally on macOS arm64:

- Repository clang-format v16 scripts (`clang-format.sh` and `check-format.sh`) on a scoped snapshot of the 22 modified C++ files.
- `bash build-support/check-build-hygiene.sh`.
- `git diff --cached --check`.

Added/extended tests:

- Writer opt-in rejection without partial buffer mutation, cross-batch sparse labels, all-empty finish, and legacy NULL-coordinate writes.
- HNSW/IVF/IVF_ON_DISK native reload, selector filtering, both range directions, concurrent domain initialization and IO failure/retry, and the dense-IVF fast path.
- Reader-level all-empty range contracts for both metrics and every index type.
- Approximate empty-array NULLs, constant query vectors, old-FE result layout, nonempty dimension errors, and unchanged exact-distance behavior; FE nullable expressions.
- Group Commit validation and regression coverage for off/sync/async writes. Differential SQL checks compare forced scalar fallback with index execution and cached results, including sparse/all-empty segments and NULL ordering. No handwritten result files.

Attempted but blocked before tests ran:

- `bash run-be-ut.sh --run --filter='AnnIndexWriterTest.*:AnnIndexReaderTest.*:VectorSearchTest.*:GroupCommitAnnValidationTest.*:FunctionArrayApproximateDistanceTest.*:ExtractQueryVectorTest.*' -j4`.
- `bash run-fe-ut.sh --run org.apache.doris.nereids.trees.expressions.functions.scalar.ApproximateDistanceTest`.
- `bash run-regression-test.sh -d ann_index_p0 -s ann_empty_array_queries,ann_delete_group_commit`.

The environment has no Java/JDK 17 or installed third-party build dependencies. BE/FE compilation, Checkstyle, clang-tidy, and runtime tests have not completed. Compaction/restart/WAL recovery and actual old/new-binary upgrade/downgrade scenarios still need validation. This PR is a draft pending those checks.

### Release note

Add opt-in support for empty arrays in ANN-indexed columns through `enable_ann_index_empty_array`, disabled by default. Approximate distances return NULL for empty arrays; sparse segments use scalar TopN evaluation to preserve NULL ordering. Upgrade all FEs and BEs before enabling the flag, and keep it enabled after writing such data. Downgrading to a BE without sparse ANN support after these writes is unsupported.

### Check List (For Author)

- Behavior changed: Yes (see Details).
- Does this need documentation: Yes; document the BE config, NULL semantics, upgrade order, and downgrade restriction in the ANN user documentation before marking ready.
- Pre-submission status: Not ready: draft pending compilation, runtime tests, recovery tests, and cross-version validation.

### AI Disclosure

- Tool: OpenAI Codex (GPT-6).
- Generated: Code, tests, and this description.
- Human review: Design and compatibility constraints discussed with the author; final implementation review pending.
### Background
ANN data rows may contain empty arrays after opting in. Query vectors still need valid coordinates and dimensions.

### What problem does this PR solve?
Issue Number: None
Related PR: apache#70552 (follow-up)
PR Type: Bugfix
Problem Summary: Empty query vectors were incorrectly treated as NULL distances, and sparse TopN could skip dimension validation when falling back. Only empty data vectors should produce NULL.

### Details
- **Frequency:** Queries with empty vectors or wrong dimensions on sparse segments.
- **Root cause:** The empty-data semantics were also applied to query arguments, and metadata dimension validation happened after fallback decisions.
- **Why existing tests missed it:** The initial tests explicitly accepted empty query vectors and did not cover metadata validation before sparse TopN fallback.
- **Fix:** Restore nonempty query extraction, reject empty query arguments in scalar evaluation, and validate TopN dimensions before fallback. Preserve NULL results for empty data vectors and existing NULL-input rejection.
- **Other paths and branches:** Cover scalar projection, range predicates, TopN, and a constant query in either argument position. No dependency or protocol change.

### Testing
Repository clang-format v16 scripts, build hygiene and git diff checks passed. Targeted BE UT execution was attempted but stopped at the Java check; Java/JDK 17 is unavailable. Updated UT and differential regression coverage have not run.

### Release note
Empty ANN data arrays produce NULL approximate distances. Empty, NULL, NULL-coordinate and dimension-mismatched query vectors are rejected.

### Check List (For Author)
- Behavior changed: Yes (query validation described above).
- Does this need documentation: Covered by the parent PR's documentation plan.
- Pre-submission status: Not ready: parent PR remains draft pending builds and runtime verification.

### AI Disclosure
- Tool: OpenAI Codex (GPT-6).
- Generated: Code, tests and description.
- Human review: Query validation semantics confirmed by the author; final implementation review pending.
### Background

ANN empty-array support needs scalar fallback to agree with indexed queries. The previous revision only made approximate distances nullable, rejected missing query vectors, and retained a non-nullable result layout for old FEs.

### What problem does this PR solve?

Issue Number: None
Related PR: None
PR Type: Enhancement

Problem Summary:

Return NULL from all array-distance functions when either vector is NULL or empty, including literals. Preserve filtering, ordering and LIMIT by falling back before FAISS for missing query vectors, and align FE nullability and constant folding with BE evaluation.

### Details

- **Change map:** 195 changed production lines and 515 changed test lines; no generated files. Production changes cover shared distance evaluation, FE return types/constant folding, and ANN missing-query fallback. Tests cover functions, query extraction, segment fallback and SQL behavior.
- **Before and after:** Empty/NULL query literals no longer fail. Exact and approximate array functions now return nullable FLOAT, including NULL for [] versus []. NULL array elements and mismatched nonempty dimensions remain errors.
- **Configs and defaults:** No new configuration in this commit; the existing empty-array ANN write opt-in remains default-off and independent of distance evaluation.
- **Impact on existing users:** Plan a coordinated FE/BE upgrade and restart; remove old-FE result-layout adaptation. Nonempty zero-vector behavior and the MAP inner_product overload remain unchanged.
- **Behavior change:** Missing queries use scalar TopN/range evaluation without calling FAISS. TopN retains candidate rows and normal SQL NULL ordering and LIMIT. A separate constant-NULL/no-vector-read optimization is not included.

### Testing

- Passed repository clang-format v16 scripts, build hygiene and staged diff checks.
- Passed Checkstyle 9.3 (the version pinned by fe/pom.xml) on all 11 changed Java files, using the repository configuration and standalone JDK 21. This is a style check, not a JDK 17 compilation or test run.
- BE/FE UT and regression runner attempts stopped at the missing JDK 17 prerequisite. Installed BE third-party dependencies are absent. No compilation or runtime-test success is claimed.
- Added/updated parameterized distance tests, FE nullable/constant-folding tests, missing-query extraction and SegmentIterator fallback tests, and regression queries.
- Regression .out regeneration and clang-tidy after a successful build remain pending. Expected outputs were not handwritten.

### Release note

Array distance and similarity functions return NULL for NULL or empty vectors, whether passed as literals or read from data. Missing ANN query vectors fall back to scalar evaluation. Upgrade and restart FE and BE together.

### Check List (For Author)

- Behavior changed: Yes (see Details).
- Does this need documentation: Yes; update vector-distance NULL semantics and coordinated-upgrade guidance.
- Pre-submission status: Not ready: draft pending compilation, runtime tests and regenerated regression outputs.

### AI Disclosure

- Tool: OpenAI Codex (GPT-6).
- Generated: Code, tests and commit description.
- Human review: The author specified the final semantics; final implementation review is pending.
@yiguolei yiguolei changed the title [improvement](storage) Support opt-in empty arrays in ANN indexes [improvement](storage) Support empty ANN arrays and NULL vector distances Nov 13, 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.

1 participant