Allocate ColumnMetadataImpl index sizes lazily and skip the index_map lookup without an index dir - #19480
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #19480 +/- ##
============================================
- Coverage 67.74% 67.70% -0.04%
- Complexity 1424 1450 +26
============================================
Files 3489 3490 +1
Lines 224672 225001 +329
Branches 35468 35521 +53
============================================
+ Hits 152210 152343 +133
- Misses 60445 60628 +183
- Partials 12017 12030 +13
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟢 Approval recommended
The changes are well-scoped, preserve external behavior/serialization, and are covered by new focused tests (with only a minor non-blocking load-time allocation consideration noted).
Pull request overview
This PR reduces per-column retained heap in segment metadata by making index-size storage lazy in ColumnMetadataImpl, and avoids unnecessary filesystem probing when SegmentMetadataImpl is constructed without an index directory (e.g., stream/tiered-storage paths). It keeps the REST/JSON indexSizeMap shape stable ({} when empty) and adds targeted unit tests for the new behavior.
Changes:
- Replace eager
LongArrayListindex-size storage with a lazily-initialized packedlong[]inColumnMetadataImpl. - Guard v3
index_maploading inSegmentMetadataImplto skip the lookup when_indexDirisnull. - Add/extend unit tests covering empty/default index sizes, packing round-trip, invalid-size rejection, and stream-vs-dir loading behavior.
File summaries
| File | Description |
|---|---|
| pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/ColumnMetadataImpl.java | Lazily stores packed index sizes in a long[] and updates equality/hash/JSON serialization accordingly. |
| pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java | Skips v3/index_map probe when there is no index directory (stream constructor path). |
| pinot-segment-spi/src/test/java/org/apache/pinot/segment/spi/index/metadata/ColumnMetadataImplTest.java | Adds tests for default-empty sizes, packing round-trip, value-object participation, and invalid-size rejection. |
| pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java | Adds regression test ensuring index sizes are only present when loading via an index directory (v3 index_map). |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… lookup without an index dir
Every ColumnMetadataImpl eagerly allocated a LongArrayList plus its long[2] for index sizes, although sizes are only ever populated by SegmentMetadataImpl from a local v3 index_map. Metadata loaded from streams (tiered storage), v1/v2 segments and the built-in virtual columns never have any, yet paid ~56 bytes per column for the segment's lifetime; on a wide external-table segment (1000+ columns, tens of thousands of segments per server) that is a measurable share of the per-column heap. The sizes now live in a long[] that stays null until the first addIndexSize. SegmentMetadataImpl skips the v3/index_map probe when it has no index directory: the stream constructor used to stat a cwd-relative v3/index_map once per segment and would have thrown on _indexDir.getPath() had that file existed. No public signature, on-disk or REST JSON change: indexSizeMap still serializes as {} when empty.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
89ea2d2 to
fb925d7
Compare
PR flow
Lazy allocation of index sizes and skipping index_map lookup for stream-loaded metadata.
AI-generated · Green: added · Yellow: modified · Red: removed · Gray: existing
Diff evidence
Summary
Allocate index-size storage only on the first valid
addIndexSizecall. Columns without recorded sizes retain no list or backing array; populated columns useLongArrayList(2)for amortized appends instead of copying the entire array on every append. Positional reads preserve bounds errors, and lookups use primitive indexed access.Skip the local
v3/index_maplookup when metadata is loaded from streams without an index directory. EmptyindexSizeMapvalues still serialize as{}; index-size packing and public signatures are unchanged.The absent-size case avoids the original list plus two-element array (approximately 56 bytes per column with conventional HotSpot compressed references). Populated columns retain the original list-based storage overhead. This is an object-layout estimate for this PR, not an attribution of the full DATA-3221 stack's heap reduction.
Validation
31 tests passed across
ColumnMetadataImplTest,ColumnMetadataTest, andSegmentMetadataImplTest.Focused metadata tests cover empty and populated states, growth/order, 48-bit sizes and signed index IDs, bounds errors, equality/hash/string behavior, and rejected appends preserving existing entries.
Segment-loading coverage compares a local v3 directory with the stream constructor, including index-size lookup and JSON compatibility.
Spotless, Checkstyle, license formatting, and license checks passed for
pinot-segment-spiandpinot-segment-local.The additional strict compiler-warning run passed the SPI tests with no warnings on changed lines, then stopped at the existing missing JetBrains
NotNullannotation dependency in unchangedZstandardDecompressor.java:51.This PR targets
masterand is the first metadata-compaction layer in DATA-3221; #19473 remains based on this branch.