Columnar map 2 query#18263
Closed
tarun11Mavani wants to merge 2 commits into
Closed
Conversation
…immutable read path Introduces the COLUMNAR_MAP index type for MAP columns with per-key columnar storage. Includes ComplexFieldSpec enhancements, SPMX v3 binary format with dense/sparse two-tier storage, dictionary encoding, forward index reader with co-iterator, per-key inverted index, and index plugin/type/handler wiring. Format details: - 56-byte header (magic + version + numKeys + numDocs + numDenseKeys + numSparseKeys + 4 section offsets) - 70-byte key metadata (tier flag + storedType + numDocs + 4 offset/length pairs for nullBitmap/forward/inverted/dictIdForward) - Dense tier: full forward index per key with run-optimized null bitmap - Sparse tier: JSON sidecar file with per-key SPMX entries reduced to type metadata (per-key presence bitmap added in PR-2 query layer) Quality fixes (from self-review): - sortValues() uses type-aware comparator matching ColumnarMapKeyDictionary, preventing wrong range query results and GROUP BY ordering for numeric keys - Sparse sidecar JSON serialization uses Jackson ObjectMapper to handle control characters per RFC 8259 - Class-level Javadoc accurately documents the 56-byte header and 70-byte key metadata layout - StandardIndexes.columnarMap() returns parameterized IndexType<> matching other accessor methods - Preconditions.checkState guards bufferSize long-to-int cast - ColumnarMapIndexHandler.updateIndices declares throws Exception - DataOutputStream wrapped in try-with-resources - WARN log when sparse sidecar missing but SPMX has sparse keys Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ter delegation Builds on the COLUMNAR_MAP storage layer (PR apache#18167) to enable query execution against the index. New classes: - ColumnarMapDataSource: query-side data source backed by ColumnarMapIndexReader. Returns a NullDataSource for unknown keys (matches BaseMapDataSource contract; callers never see null). Sparse-key forward index throws on JSON parse / numeric parse failures with column/key/docId context (was previously silent). - ColumnarMapForwardIndexReader: per-key forward index reader (immutable) - ColumnarMapRealtimeInvertedIndex: per-key inverted index (mutable segment). Wraps each per-dictId bitmap in ThreadSafeMutableRoaringBitmap and returns a synchronized clone from getDocIds() — readers can iterate concurrently with the writer's add() calls (mirrors RealtimeInvertedIndex pattern). - MutableColumnarMapIndexImpl: mutable index for consuming segments Wire-up: - ColumnarMapIndexType.createMutableIndex returns MutableColumnarMapIndexImpl instead of throwing UnsupportedOperationException. Storage format: - OnHeapColumnarMapIndexCreator now writes a per-sparse-key presence bitmap (run-optimized) into the SPMX layout for sparse-tier keys, reusing the nullBitmap slot. ImmutableColumnarMapIndexReader loads it directly. Without this, IS NULL / IS NOT NULL on sparse keys returned wrong results. - Layout javadoc updated to clarify the slot semantic per tier. Query operators: - MapFilterOperator: adds per-key inverted index path and presence bitmap path alongside the existing JSON-index and full-scan paths. explainAttributes now emits the per_key_inverted_index branch. - ItemTransformFunction: captures per-key null bitmap for null-aware item() evaluation; exposes getKeyPath() for direct MAP key resolution. getNullBitmap is gated on the query's nullHandlingEnabled flag and skips allocation when the segment-level null bitmap doesn't intersect the block's docId range. SPI hardening: - ImmutableColumnarMapIndexReader/MutableColumnarMapIndexImpl getKeyDataSource throw UnsupportedOperationException — callers must go through ColumnarMapDataSource so the unknown-key fall-back is consistent. Tests: - ColumnarMapIndexTest: testSparseKeyPresenceBitmapMatchesPresentDocs verifies IS NULL / IS NOT NULL correctness on sparse-tier keys; testRealtimeInvertedIndexConcurrentReaderCorrectness exercises 1-writer + 4-reader stress with strict membership assertions on observed docIds; testSparseKeyDataSourceReturnsNullDataSourceForUnknownKey verifies the NullDataSource fall-back for unknown keys and the SPI-level UOE. - ColumnarMapIndexEndToEndTest, ColumnarMapBenchmarkTest Sourced from columnar-map-split-wip via 3-way merge against the storage branch's current state. Storage-side conflicts resolved in favor of the storage branch's deep-review fixes (RLE bitmaps, two-tier flag, JsonUtils, try-with-resources, type-aware sort comparator). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #18263 +/- ##
=============================================
- Coverage 63.31% 34.97% -28.34%
+ Complexity 1627 789 -838
=============================================
Files 3229 3259 +30
Lines 196705 199210 +2505
Branches 30408 30879 +471
=============================================
- Hits 124544 69675 -54869
- Misses 62183 123341 +61158
+ Partials 9978 6194 -3784
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is PR 2 of 3 introducing the columnar MAP query layer on top of the storage layer landed in #17896. This PR makes COLUMNAR_MAP columns actually queryable — adding the per-key DataSource, mutable consuming-segment index, filter-operator delegation strategies, and null-aware item() evaluation.
Stack
ColumnarMapIndexTest(46 unit tests covering both storage and query paths, including regression tests for the CRITICAL fixes in this PR — see "Notes for reviewers" below)Motivation
PR 1 added the binary format and immutable read path for COLUMNAR_MAP, but no query path was wired up —
MapFilterOperatorwould fall through to the slowExpressionFilterOperator(per-docMap<String, Object>materialization),ItemTransformFunctionhad no per-key null bitmap, and consuming (real-time) segments would fail at segment creation becausecreateMutableIndexthrewUnsupportedOperationException. This PR closes those gaps and delivers the actual query speedups the columnar storage was designed to enable.What's in this PR
Query data source (
pinot-segment-local)ColumnarMapDataSourceimplementsMapDataSourceand routesgetKeyDataSource(key)to one of four paths:ColumnarMapKeyForwardIndexReader+ per-key dictionary + per-key inverted indexFixedByteSVMutableForwardIndexdirectly (O(1) lock-free)NullDataSource(key)to matchBaseMapDataSource(callers likeProjectionBlockandItemTransformFunctionnever see null)Supporting classes:
ColumnarMapForwardIndexReader(column-level wrapper),ColumnarMapRealtimeInvertedIndex(mutable per-dictId inverted index, thread-safe viaThreadSafeMutableRoaringBitmap),MutableColumnarMapIndexImpl(consuming-segment columnar map implementingColumnarMapIndexReader).Filter operator delegation (
pinot-core)MapFilterOperatorselects between four strategies:MapDataSourceexposes a per-key inverted index for the requested keyIS NULL/IS NOT NULLon COLUMNAR_MAP, viaBitmapBasedFilterOperatorover the per-key presence bitmapexplainAttributesnow emitsdelegateTo:per_key_inverted_indexanddelegateTo:presence_bitmap(was previously misreported asexpression_filter).ItemTransformFunctionnull-aware reads (pinot-core)Captures the per-key null bitmap from
keyDS.getNullValueVector()and projects it to block-local docIds ingetNullBitmap. Gated on the query'snullHandlingEnabledflag; short-circuits when the segment-level null bitmap doesn't intersect the block's docId range. AddsgetKeyPath()for direct key resolution byTransformBlock/ProjectionBlock.Storage format addition
OnHeapColumnarMapIndexCreatornow writes a per-sparse-key presence bitmap (run-optimized) into the SPMX layout, reusing the dense-tiernullBitmapOffset/Lenslot pertierFlag. Without this, IS NULL / IS NOT NULL on sparse keys returned wrong results. Format version unchanged (still SPMX v3); legacy v3 segments without the new bytes (nullBitmapLen == 0) preserve the prior empty-bitmap behavior.Wire-up + SPI hardening
ColumnarMapIndexType.createMutableIndexreturnsnew MutableColumnarMapIndexImpl(...)instead of throwingColumnarMapIndexReader.getKeyDataSource(String)throwsUnsupportedOperationExceptionon both reader implementations — callers must go throughColumnarMapDataSourceso the unknown-key fall-back stays consistentRuntimeExceptionwithcolumn/key/docId/valuecontext on JSON parse and numeric parse failures (was previously silent)How to use
Schema and table-config setup is unchanged from PR 1 (#17896). Once both PRs are landed, queries against COLUMNAR_MAP columns work transparently:
EXPLAIN PLAN VERBOSE will show
delegateTo:per_key_inverted_indexfor fast-path EQ predicates anddelegateTo:presence_bitmapfor IS NULL / IS NOT NULL on sparse keys.Test plan
ColumnarMapIndexEndToEndTest— 4 tests (mutable→immutable round-trip, undeclared-key default type, user-metrics scenario, cross-segment merge)ColumnarMapFilterOperatorTest— 7 tests (IS_NULL / IS_NOT_NULL / NOT_EQ / NOT_IN against absent and partially-present keys)ColumnarMapSegmentCreationTest— 1 test (segment build pipeline)ColumnarMapBenchmarkTest— disabled (@Test(enabled = false)); manual perf benchmark, run with-DenableBenchmark=trueand the annotation flipped