Skip to content

Fold PhysicalColumnIndexContainer's IndexTypeMap into a presence mask and a dense reader array - #19475

Open
xiangfu0 wants to merge 1 commit into
xiangfu0/data-3221-3-datasource-adapterfrom
xiangfu0/data-3221-4-index-container-mask
Open

Fold PhysicalColumnIndexContainer's IndexTypeMap into a presence mask and a dense reader array#19475
xiangfu0 wants to merge 1 commit into
xiangfu0/data-3221-3-datasource-adapterfrom
xiangfu0/data-3221-4-index-container-mask

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

What

PhysicalColumnIndexContainer stored its readers in an IndexTypeMap holding a span-sized IndexReader[]: with forward index at numeric id 2 and null vector at 8, a column with two readers allocated seven slots. It now keeps a long presence mask plus a densely packed, exactly sized reader array, so getIndex is a shift, mask and Long.bitCount instead of a range check, at the same O(1) cost. Construction also drops the transient per-column ArrayLists.

A Preconditions.checkState rejects a numeric index id of 64 or above at construction rather than silently misbehaving; OSS uses 13 ids today.

Tests

PhysicalColumnIndexContainerTest: forward only, forward plus null vector, and a mix spanning the lowest and a high id, asserting getIndex returns the created reader for present types and null for absent ones, that forwardIndexOnly filtering still applies, and that close closes each reader exactly once.

Why

A server keeps one metadata object graph per (segment, column) for as long as the segment is loaded, so on wide tables the per-column footprint decides how many segments a server can hold. Measured end to end on a 1000-column segment, this series takes the heap retained at load from 4.08 MB to 0.175 MB per segment (4,080 to 174 bytes per column), with a fully compacting collector on both sides. No on-disk format change, the /tables/{table}/segments/{segment}/metadata JSON stays byte-identical, and every public and SPI signature keeps working.

Stack

Part 4 of 9, based on #19474. Review only this part's own commits; the earlier parts account for the rest of the diff.

  1. Allocate ColumnMetadataImpl index sizes lazily and skip the index_map lookup without an index dir #19480 lazy index-size storage
  2. Canonicalize the default null value and intern per-column strings at metadata parse time #19473 canonical default-null values and interned per-column strings
  3. Delegate immutable DataSourceMetadata to ColumnMetadata instead of snapshotting it #19474 delegating immutable DataSourceMetadata
  4. Fold PhysicalColumnIndexContainer's IndexTypeMap into a presence mask and a dense reader array #19475 presence-mask index container
  5. Share segment-derived FieldSpec instances across segments through a weak interner #19476 weak FieldSpec interner
  6. Materialize immutable-segment columns lazily behind an opt-in instance config (default off) #19477 opt-in lazy column materialization
  7. Slim ColumnMetadataImpl to 72 bytes and derive the per-segment Schema lazily #19478 slim ColumnMetadataImpl and lazy per-segment Schema
  8. Store numeric column min/max as primitives instead of boxed Comparables #19479 primitive numeric min/max
  9. Hold segment column metadata in sorted arrays and derive the map on demand #19481 sorted-array column metadata store

… plus dense readers

A server holding tens of thousands of wide segments (1000+ columns) creates one
PhysicalColumnIndexContainer per (segment, column) at load time. Each one held a
package-private IndexTypeMap object plus an IndexReader[] spanning the numeric-id
range of the present readers: the common forward_index (id 2) + nullvalue_vector
(id 8) shape paid for 7 slots to hold 2 readers, i.e. 24 B container + 24 B map +
48 B array = 96 B per column.

The container now stores a `long _presentMask` (bit i set when the index type with
numeric id i has a reader) and an exactly-sized `IndexReader[] _readers` ordered by
numeric id, sharing one empty array for columns without readers. getIndex(type) is
a shift, a mask and a popcount into the dense array, so it stays O(1) on the query
path; the forward + null-vector shape drops to 32 B container + 24 B array = 56 B,
and no shape regresses because a dense array is never larger than a span array.
Construction also writes straight into a scratch array instead of two ArrayLists
and a ShortArrayList per column. Numeric ids are validated against the 64-bit mask
at construction with a clear IllegalStateException (13 index types in OSS today),
rather than adding an unreachable fallback path.

Behaviour is otherwise unchanged: getIndex returns null for absent types, the
forwardIndexOnly filtering, IndexReaderConstraintException handling and
init-failure cleanup are kept, close() closes every reader exactly once in id order,
and the multi-column text reader setter/getter are untouched.

Compatibility: IndexTypeMap was package-private with no references outside this
file, ColumnIndexContainer and IndexService numeric ids are unchanged, and there is
no SPI, on-disk, wire or REST JSON impact, so mixed-version deployments are
unaffected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@xiangfu0
xiangfu0 force-pushed the xiangfu0/data-3221-4-index-container-mask branch from 33a59e3 to 0bbde43 Compare September 9, 2026 01:56
@codecov-commenter

codecov-commenter commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.80%. Comparing base (2b73f0d) to head (0bbde43).

Files with missing lines Patch % Lines
...ent/index/column/PhysicalColumnIndexContainer.java 72.72% 7 Missing and 2 partials ⚠️
Additional details and impacted files
@@                              Coverage Diff                              @@
##             xiangfu0/data-3221-3-datasource-adapter   #19475      +/-   ##
=============================================================================
- Coverage                                      57.81%   57.80%   -0.02%     
- Complexity                                         1        7       +6     
=============================================================================
  Files                                           2688     2688              
  Lines                                         164324   164308      -16     
  Branches                                       26683    26681       -2     
=============================================================================
- Hits                                           95008    94977      -31     
- Misses                                         61291    61293       +2     
- Partials                                        8025     8038      +13     
Flag Coverage Δ
integration 100.00% <ø> (+100.00%) ⬆️
integration1 100.00% <ø> (?)
integration2 0.00% <ø> (ø)
java-25 57.80% <72.72%> (-0.02%) ⬇️
lane-a 100.00% <ø> (+100.00%) ⬆️
lane-b 0.00% <ø> (ø)
temurin 57.80% <72.72%> (-0.02%) ⬇️
unittests 57.80% <72.72%> (-0.02%) ⬇️
unittests1 57.80% <72.72%> (-0.02%) ⬇️

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

memory Related to memory usage or optimization performance Related to performance optimization refactor Code restructuring without changing behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants