Skip to content

Support backfilling null value vector from the default null value#19072

Open
Jackie-Jiang wants to merge 1 commit into
apache:masterfrom
Jackie-Jiang:nullvalue-backfill
Open

Support backfilling null value vector from the default null value#19072
Jackie-Jiang wants to merge 1 commit into
apache:masterfrom
Jackie-Jiang:nullvalue-backfill

Conversation

@Jackie-Jiang

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in, per-column reload-time backfill that reconstructs a column's null value vector for segments that were created before null handling was enabled, by treating every stored value equal to the column's default null value as null. This lets operators enable null handling on a table and have IS NULL / null-aware queries work on already-ingested data without re-ingesting.

The reconstruction mirrors how ingestion records nulls:

  • single-value: a doc is null when its stored value equals the column's default null value;
  • multi-value: a doc is null when its entry is a single-element array holding the default null value.

Opt-in and safety

The backfill is lossy — a genuine value that happens to equal the default null value is also marked null — so it is opt-in per column, via the null value vector index config:

"fieldConfigList": [
  { "name": "status", "indexes": { "null": { "backfill": true } } }
]
  • A new NullValueVectorConfig carries the backfill flag; its enabled state is still derived from null handling (column-based isNullable or the table-level nullHandlingEnabled), and the two are merged in NullValueIndexType's deserializer.
  • Enable it only for columns whose default null value is a non-occurring sentinel (e.g. a dimension's MIN_VALUE), not for metric/boolean columns whose default is a common value like 0.
  • MAP/complex types are rejected at table-config validation time (NullValueIndexType.validate), since the default (empty map) is an ordinary value and OPEN_STRUCT-backed maps have no single scannable parent forward index. A TODO is left to revisit if complex-type null handling matures.

nonNull column-metadata flag

A new nonNull column-metadata property records the "null handling enabled, but no null values" case. A bitmap file is written only when a column actually has nulls (unchanged from today); the no-null case is recorded via this flag instead. This keeps behavior symmetric between segment creation and backfill — no empty bitmap files — and makes the backfill idempotent (a flagged column is not re-scanned on subsequent reloads) while distinguishing a null-handled-but-empty column from one that was never null-handled.

Backward / mixed-version compatibility

  • The metadata flag defaults to false and is additive; older segments are unaffected.
  • The feature is off unless a column explicitly opts in.
  • Rolling-upgrade note: once a column opts in (writes indexes.null to the table config), a node still running the pre-change code will fail to resolve that table's index configs (the old null-vector deserializer treats the config as an exclusive alternative and every column already gets a derived config). Enable backfill only after the whole cluster is upgraded; a rollback with the config still set will fail on that table.

Tests

  • NullValueVectorHandlerTest — backfill across SV/MV and dict/raw scalar types, selectivity (an un-opted column holding the sentinel is left untouched), the creation-time nonNull flag write, and idempotency on reload.
  • NullValueIndexTypeTest — config resolution (enabled derived from null handling) and validate rejecting MAP opt-in.
  • TableConfigUtilsTest — end-to-end validation: scalar opt-in passes, MAP opt-in is rejected.

Add an opt-in, per-column reload-time backfill that reconstructs a column's
null value vector for segments that predate null handling being enabled, by
treating every stored value equal to the column's default null value as null
(single-value: value == default; multi-value: single-element array holding the
default null value, mirroring how ingestion records a null).

The opt-in lives under the null value vector index config
(`indexes.null.backfill`), backed by a new `NullValueVectorConfig` whose
`enabled` state is still derived from null handling while `backfill` is
user-set. Because the reconstruction is lossy (a genuine value equal to the
default is also marked null), it is per-column and only enabled columns are
touched; `MAP`/complex types are rejected at table-config validation time.

A new `nonNull` column-metadata flag records the "null handling enabled, no
nulls" case so no empty bitmap file is written and the column is not re-scanned
on every reload. Segment creation and the reload backfill set it symmetrically.
@Jackie-Jiang Jackie-Jiang added feature New functionality null support Related to NULL value handling labels Jul 24, 2026
@Jackie-Jiang
Jackie-Jiang requested review from Copilot and xiangfu0 July 24, 2026 00:12

Copilot AI 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.

Pull request overview

Adds an opt-in, per-column reload-time mechanism to reconstruct missing null value vectors for legacy segments by treating stored default-null sentinel values as null, and introduces a column-metadata flag to represent the “null handling enabled but no nulls” case without writing empty bitmap files.

Changes:

  • Introduces NullValueVectorConfig with a backfill flag and updates null-vector config resolution to derive enabled from null-handling while honoring backfill from fieldConfig.indexes.null.
  • Adds isNonNull column-metadata flag and wires it into both segment creation and reload-time backfill to keep behavior symmetric and make backfill idempotent.
  • Adds a reload-time NullValueVectorHandler plus validation/tests covering SV/MV, dict/raw, selectivity, idempotency, and rejection of MAP/complex opt-in.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pinot-spi/src/main/java/org/apache/pinot/spi/config/table/NullValueVectorConfig.java New index config type carrying the backfill opt-in flag.
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/V1Constants.java Adds the isNonNull column-metadata key constant.
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/StandardIndexes.java Tightens the null-vector index type signature to NullValueVectorConfig.
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/ColumnMetadataImpl.java Persists/loads and exposes the new nonNull column-metadata flag.
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/ColumnMetadata.java Adds default isNonNull() API to represent “null-handled but no nulls” columns.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/nullvalue/NullValueVectorHandler.java New reload-time handler that scans forward indexes to backfill null bitmaps from default-null sentinels.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/nullvalue/NullValueIndexType.java Updates deserialization/validation and wires the new handler into the index type.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/nullvalue/NullValueVectorCreator.java Tracks whether any nulls were seen to avoid writing empty bitmap files and expose isNonNull().
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/BaseSegmentCreator.java Writes the isNonNull metadata flag at segment creation when null handling is enabled but no nulls exist.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java Validates that scalar backfill opt-in passes and MAP backfill opt-in is rejected.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/nullvalue/NullValueVectorHandlerTest.java New tests for backfill correctness across types, selectivity, creation-path flagging, and idempotency.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/nullvalue/NullValueIndexTypeTest.java Tests config resolution semantics and MAP backfill validation behavior.

Comment on lines +128 to +129
// TODO: Revisit MAP/complex backfill if complex-type null handling matures and a safe (non-occurring) sentinel
// default null value becomes available.
Comment on lines +101 to 104
public static IndexType<NullValueVectorConfig, NullValueVectorReader, ?> nullValueVector() {
return (IndexType<NullValueVectorConfig, NullValueVectorReader, ?>)
IndexService.getInstance().get(NULL_VALUE_VECTOR_ID);
}
@codecov-commenter

codecov-commenter commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.93720% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.42%. Comparing base (706b772) to head (1453ddb).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...egment/index/nullvalue/NullValueVectorHandler.java 50.64% 65 Missing and 12 partials ⚠️
.../pinot/spi/config/table/NullValueVectorConfig.java 40.00% 3 Missing and 3 partials ⚠️
...al/segment/index/nullvalue/NullValueIndexType.java 95.83% 0 Missing and 1 partial ⚠️
...a/org/apache/pinot/segment/spi/ColumnMetadata.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19072      +/-   ##
============================================
- Coverage     65.43%   65.42%   -0.01%     
  Complexity     1421     1421              
============================================
  Files          3425     3427       +2     
  Lines        216173   216360     +187     
  Branches      34239    34287      +48     
============================================
+ Hits         141449   141551     +102     
- Misses        63340    63415      +75     
- Partials      11384    11394      +10     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 65.42% <58.93%> (-0.01%) ⬇️
temurin 65.42% <58.93%> (-0.01%) ⬇️
unittests 65.42% <58.93%> (-0.01%) ⬇️
unittests1 56.80% <20.77%> (-0.06%) ⬇️
unittests2 37.82% <55.07%> (+0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

feature New functionality null support Related to NULL value handling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants