Repository navigation
HIVE-30059: LLAP IO Cache: native parquet data cache - #6793
abstractdog wants to merge 71 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect schema projection, cache ownership, row lineage, configuration, and fallback behavior.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds opt-in native Parquet LLAP caching with vectored reads, cached-page decoding, fallback integration, and supporting tests.
Changes:
- Adds native Parquet LLAP configuration, APIs, and dispatch.
- Implements cache layouts, range buffers, vectored reads, and page decoding.
- Refactors shared Parquet reader logic and expands test coverage.
File summaries
| File | Reviewed changes and final comments |
|---|---|
ql/src/test/org/apache/hadoop/hive/llap/TestParquetRangeBuffers.java |
Tests range-buffer reuse and slicing; no final comments. |
ql/src/test/org/apache/hadoop/hive/llap/TestParquetCacheLayout.java |
Tests cache layout behavior; no final comments. |
ql/src/java/org/apache/hadoop/hive/ql/plan/MapWork.java |
Enables native Parquet LLAP eligibility; no final comments. |
ql/src/java/org/apache/hadoop/hive/ql/io/parquet/vector/VectorizedParquetRecordReader.java |
Critical, 1 vote: Native reads omit row-lineage columns, producing null/default lineage values. |
ql/src/java/org/apache/hadoop/hive/ql/io/parquet/vector/ParquetRowGroupDecoder.java |
Shares row-group decoder construction; no final comments. |
ql/src/java/org/apache/hadoop/hive/ql/io/parquet/ParquetRecordReaderBase.java |
Reviewed as part of Parquet reader integration; no final comments. |
ql/src/java/org/apache/hadoop/hive/ql/io/HiveInputFormat.java |
Gates native Parquet wrapping; no final comments. |
ql/src/java/org/apache/hadoop/hive/llap/ParquetRangeBuffers.java |
Manages vectored-read buffers; no final comments. |
ql/src/java/org/apache/hadoop/hive/llap/ParquetCacheLayout.java |
Moderate, 1 vote: Native layout can create fallback-incompatible cache gaps and trigger an assertion instead of refetching. |
ql/src/java/org/apache/hadoop/hive/llap/LlapCacheAwareFs.java |
Supports cache-aware fallback reads; no final comments. |
llap-server/src/test/org/apache/hadoop/hive/llap/io/encoded/TestParquetEncodedDataReader.java |
Tests native reads and caching; no final comments. |
llap-server/src/test/org/apache/hadoop/hive/llap/io/api/impl/TestLlapRecordReader.java |
Tests LLAP reader behavior; no final comments. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/encoded/ParquetEncodedDataReader.java |
Critical, 2 votes: Top-level field indexes are used against flattened leaf columns, misselecting nested projections. Critical, 1 vote: Partial cache insertion failure can cause cache-owned buffers to be deallocated. Nit, 1 vote: count(*) fetches and caches unnecessary column chunks. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/encoded/ParquetEncodedColumnBatch.java |
Carries cached row-group data; no final comments. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/decode/ParquetEncodedDataConsumer.java |
Moderate, 1 vote: Decode exceptions are swallowed, allowing later I/O and resource use after failure. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/decode/ParquetColumnVectorProducer.java |
Creates native Parquet pipelines; no final comments. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/decode/ParquetCachedPageReadStore.java |
Reads cached Parquet pages; no final comments. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapRecordReader.java |
Supports producer fallback; no final comments. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapIoImpl.java |
Moderate, 1 vote: Discards supplied fileKey and tag. Moderate, 1 vote: Can create the native producer without a metadata cache in memory mode none. Moderate, 1 vote: Does not honor the per-query native Parquet disable flag. |
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapInputFormat.java |
Integrates LLAP input handling; no final comments. |
llap-client/src/java/org/apache/hadoop/hive/llap/io/api/LlapIo.java |
Extends the Parquet LLAP API; no final comments. |
iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/vector/HiveVectorizedReader.java |
Updates footer-cache API usage; no final comments. |
iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergStorageHandler.java |
Reviewed for storage-handler integration; no final comments. |
common/src/java/org/apache/hadoop/hive/conf/HiveConf.java |
Adds native Parquet configuration; no final comments. |
Review details
Suppressed comments (6)
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapIoImpl.java:503
- This constructs a plain
FileSplitand silently discards thefileKeyandtagsupplied by the public API. The native reader then derives a different key from the job configuration/filesystem inloadFooter()and computes its own tag, so callers that provide a stable file identity cannot read or populate the intended data-cache entry or preserve the administrative cache tag. Propagate these arguments through the native reader, or remove them from the API instead of ignoring them.
FileSplit split = new FileSplit(path, offset, length, (String[]) null);
try {
LlapRecordReader rr = LlapRecordReader.create(conf, split, tableIncludedCols, HiveStringUtils.getHostname(),
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapIoImpl.java:285
dataCacheis non-null inLLAP_IO_MEMORY_MODE=nonebecauseLlapIoImplassigns aSimpleBufferManagerto it while leavingfileMetadataCachenull. With the native flag enabled, this condition still createsparquetCvp;ParquetEncodedDataReader.loadFooterthen callsgetParquetFooterBuffersFromCache, whose first operation requires a non-null metadata cache, so the LLAP input format fails instead of falling back to the normal Parquet reader. Gate this producer on the metadata cache as well (or make the native reader bypass the footer cache when it is unavailable).
this.parquetCvp = dataCache != null
? new ParquetColumnVectorProducer(dataCache, bufferManagerOrc, conf, cacheMetrics, ioMetrics)
: null;
llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapIoImpl.java:500
- The API contract says this method returns null when native Parquet IO is disabled, but
parquetCvpis constructed independently of the per-query flag and this check only tests for null. A caller can therefore sethive.llap.io.parquet.native.enabled=falseand still start the native reader, bypassing the configuration gate used byHiveInputFormat. IncludeConfVars.LLAP_IO_PARQUET_NATIVE_ENABLEDin this guard.
if (parquetCvp == null) {
return null;
}
llap-server/src/java/org/apache/hadoop/hive/llap/io/decode/ParquetEncodedDataConsumer.java:195
- These exceptions are reported but swallowed, so
EncodedDataConsumer.consumeDatareturns normally andParquetEncodedDataReader.performDataReadcontinues fetching and decoding later row groups before callingsetDone. A corrupt page in an early group can therefore keep doing I/O and holding cache resources after the downstream reader has failed. Propagate the decode failure after recording it (or letconsumeDataperform the error notification) so the reader's abort path stops the split.
} catch (IOException | RuntimeException e) {
// parquet-mr reports decode failures as runtime ParquetDecodingException.
LlapIoImpl.LOG.error("Parquet decodeBatch failed for rowGroup " + batch.rowGroupIx + " of " + path, e);
downstreamConsumer.setError(e);
} finally {
llap-server/src/java/org/apache/hadoop/hive/llap/io/encoded/ParquetEncodedDataReader.java:197
- For the
count(*)projection,colsToIncludecan be empty whilerequestedSchemastill contains the table fields; the shared decoder explicitly documents that all readers are null in this case. This code nevertheless derives every requested field as a projected leaf, so the native path fetches and caches every column chunk even though it only needs each row group's row count. Short-circuit the projected-leaf list whenincludes.getPhysicalColumnIds()is empty.
int[] projected = projectedLeaves(requestedSchema, fileSchema);
ql/src/java/org/apache/hadoop/hive/llap/ParquetCacheLayout.java:88
- This native layout is incompatible with the existing Parquet fallback cache writer.
LlapCacheAwareFs.getAndValidateMissingChunksrequires every missing gap to start and end onmaxAllocboundaries, while this method can split a 5 MiB chunk into 4 MiB + 1 MiB. If the 1 MiB native buffer is evicted and a later nested-type/fallback reader requests the chunk, the fallback sees a gap starting atchunkStart + 4 MiBand throws anAssertionErrorinstead of refetching it. Both paths need to share the same layout, or the fallback must accept arbitrary cache gaps.
public int[] bufferSizes(long length) {
int count = 0;
for (long left = length; left > 0; ++count) {
left -= left < minBuffer ? left : Math.min(maxBuffer, Long.highestOneBit(left));
}
- Files reviewed: 24/24 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Three Critical findings on the native Parquet cache path:
- projectedLeaves used fileSchema.getFieldIndex to pick column chunks, but
BlockMetaData.getColumns() is in flat leaf order. A file schema like
'group nested {a,b}, x' projecting 'x' would hit nested.b instead of x.
Map each requested top-level primitive to its single-segment leaf in
fileSchema.getColumns() instead. Add a regression test.
- putColumn set part.owned only after processing all missing ranges for the
column, so a mid-run throw from putFileData (e.g. its length-mismatch
guard) left already-inserted cache buffers looking like raw allocations
and finishFetch would allocator.deallocate cache-owned memory. Insert
one range at a time and flip ownership per part.
- ParquetEncodedDataReader.loadFooter builds requestedSchema without the
row-lineage columns the fallback reader adds via
RowLineageUtils.getRequestedSchemaWithRowLineageColumns, so with row
lineage on the native path would silently emit nulls for
ROW__LINEAGE__ID / LAST__UPDATED__SEQUENCE__NUMBER. Detect that case in
the producer and fall back, matching the nested-projection fallback.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Fix the mechanical issues flagged on files this PR modifies. Skips the 15 findings inside verbatim extractions from VectorizedParquetRecordReader (kept behavior-preserving) and the switch/case indentation reports. Notable non-trivial fixes: - LlapIo.llapVectorizedParquetReaderForPath: 9 params -> 3, collected into a new LlapParquetReadRequest record. - LlapInputFormat.getRecordReader: extract wrapOrFallback() to bring cognitive complexity under threshold. - ParquetEncodedDataReader: extract planMissRun() from planColumnChunk; drop unused 'includes' field/param, unused maxAlloc param, and Part.miss; narrow catch(Throwable) -> catch(Exception) at four sites and preserve InterruptedException on the thread instead of masking it. - ParquetCacheLayout / ParquetEncodedDataReader: replace loop-counter mutation with while loops. - CacheChunk: drop stale @VisibleForTesting; it is part of the cache's public read surface for both ORC and Parquet. - checkstyle/suppressions.xml: suppress VisibilityModifier for ParquetEncodedColumnBatch (matches parent EncodedColumnBatch pattern). - Delete stale checkListColumnSupport from VectorizedParquetRecordReader (moved to ParquetRowGroupDecoder). Style-only elsewhere: pattern-instanceof, split multi-decls, empty-body 'why' comments, header rewraps, paren-pad, restricted-identifier rename (record() -> recordRanges()). Verified: mvn checkstyle:check on llap-server (0 violations) and the existing test suites: TestParquetEncodedDataReader (27/27), TestLlapRecordReader (2/2), TestParquetRangeBuffers (2/2). Co-Authored-By: Claude Code <noreply@anthropic.com>
66f34dc to
65352ad
Compare
Three Critical findings on the native Parquet cache path:
- projectedLeaves used fileSchema.getFieldIndex to pick column chunks, but
BlockMetaData.getColumns() is in flat leaf order. A file schema like
'group nested {a,b}, x' projecting 'x' would hit nested.b instead of x.
Map each requested top-level primitive to its single-segment leaf in
fileSchema.getColumns() instead. Add a regression test.
- putColumn set part.owned only after processing all missing ranges for the
column, so a mid-run throw from putFileData (e.g. its length-mismatch
guard) left already-inserted cache buffers looking like raw allocations
and finishFetch would allocator.deallocate cache-owned memory. Insert
one range at a time and flip ownership per part.
- ParquetEncodedDataReader.loadFooter builds requestedSchema without the
row-lineage columns the fallback reader adds via
RowLineageUtils.getRequestedSchemaWithRowLineageColumns, so with row
lineage on the native path would silently emit nulls for
ROW__LINEAGE__ID / LAST__UPDATED__SEQUENCE__NUMBER. Detect that case in
the producer and fall back, matching the nested-projection fallback.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Fix the mechanical issues flagged on files this PR modifies. Skips the 15 findings inside verbatim extractions from VectorizedParquetRecordReader (kept behavior-preserving) and the switch/case indentation reports. Notable non-trivial fixes: - LlapIo.llapVectorizedParquetReaderForPath: 9 params -> 3, collected into a new LlapParquetReadRequest record. - LlapInputFormat.getRecordReader: extract wrapOrFallback() to bring cognitive complexity under threshold. - ParquetEncodedDataReader: extract planMissRun() from planColumnChunk; drop unused 'includes' field/param, unused maxAlloc param, and Part.miss; narrow catch(Throwable) -> catch(Exception) at four sites and preserve InterruptedException on the thread instead of masking it. - ParquetCacheLayout / ParquetEncodedDataReader: replace loop-counter mutation with while loops. - CacheChunk: drop stale @VisibleForTesting; it is part of the cache's public read surface for both ORC and Parquet. - checkstyle/suppressions.xml: suppress VisibilityModifier for ParquetEncodedColumnBatch (matches parent EncodedColumnBatch pattern). - Delete stale checkListColumnSupport from VectorizedParquetRecordReader (moved to ParquetRowGroupDecoder). Style-only elsewhere: pattern-instanceof, split multi-decls, empty-body 'why' comments, header rewraps, paren-pad, restricted-identifier rename (record() -> recordRanges()). Verified: mvn checkstyle:check on llap-server (0 violations) and the existing test suites: TestParquetEncodedDataReader (27/27), TestLlapRecordReader (2/2), TestParquetRangeBuffers (2/2). Co-Authored-By: Claude Code <noreply@anthropic.com>
65352ad to
2ac8454
Compare
- ORC dot-continuation lines: fix 4-space continuation indent
- LlapIoImpl: split combined declarations, remove chained assignments
- ParquetEncodedDataConsumer: NOSONAR on try line for S2093
- ParquetEncodedColumnBatch: make fields private, add accessors
- ParquetEncodedDataReader / TestParquetEncodedDataReader: fix
'{ '-after-brace whitespace, use accessors
- ParquetCachedPageReadStore: use accessors
- ParquetRowGroupDecoder: reduce parameter counts via
TimestampConversionOptions record; replace generic
RuntimeException with UnsupportedOperationException /
IllegalStateException / InvalidSchemaException; pattern-match
instanceof; rewrite commented-out MAP schema example; merge
case labels; retire the moved TODO
- TestParquetRangeBuffers: drop unused throws IOException
Co-Authored-By: Claude Code <noreply@anthropic.com>
… overlap F3: the four cleanup handlers caught Exception, but every one of them guards an allocation or a decode, and those throw Errors - a failed allocation, an assertion, an OutOfMemoryError. Such a throw escaped without abandon() or setError(), which leaks the row group's locked cache buffers and leaves the consumer waiting on a setDone that is never coming. Widen all four to Throwable; precise rethrow keeps the declared throws clauses unchanged. The InterruptedException re-interrupt in callInternal stays. F10: readVectored is allowed to do the IO inline on the calling thread, and several FileSystem implementations do. When it has, the futures are already complete by the time startFetch returns, so issuing the next row group's requests early overlaps nothing and only doubles how much cache memory the fragment holds locked. Latch that off the first fetch that had misses and skip the lookahead from then on. Self-detected rather than read off hasCapability(VECTOREDIO_*), which says nothing about asynchrony. Co-Authored-By: Claude Code <noreply@anthropic.com>
The footer read establishes an inode-based file key and the cache entries are keyed on it, but the data read then opened the file by name again. A rename or a re-create in between hands back a different file's bytes, which are then decoded against the cached footer and mixed with cache entries belonging to the original inode. Open the file through /.reserved/.inodes/<id> instead when the key is an inode id and hive.llap.io.use.fileid.path is on, as OrcEncodedDataReader does. That check now lives once, in HdfsUtils.getFileIdPath(Path, Object, Configuration), shared by OrcEncodedDataReader, VectorizedParquetRecordReader and this reader. The inode path is used only to open the file, not stored over path: the cache tag is a table/partition identity derived from the path components and must keep seeing the real name, as must the decode error messages, and the inode path carries no scheme so the file system still has to be resolved from the real path. Co-Authored-By: Claude Code <noreply@anthropic.com>
F5: build ParquetColumnVectorProducer only in cache mode. With hive.llap.io.memory.mode=none there is no data cache and no buffer manager behind it, so the producer was handed out anyway and the native path took splits it could not serve. Making it null makes the existing cvp == null guard in getInputFormat live, and the split declines into the fallback. F6 (plan side): usesNativeParquetLlapIo was OR'd in unconditionally, but doCheckIfs is true exactly when the query is not vectorized and the plan is relying on the non-vector wrapper. The native reader produces batches and has no wrapper, so the daemon will not take that split - the plan's LLAP IO string claimed otherwise. Guard it with !doCheckIfs. F11: the fallback now goes through HiveInputFormat.injectLlapCaches rather than calling initCacheOnlyInputFormat itself. injectLlapCaches also pins parquet.read.allocation.size, which is what keeps the fallback reader from splitting a column chunk and writing its own buffer geometry into the cache. The pre-PR dispatch always set it and the new fallback silently lost it. F12: drop LlapIo.llapVectorizedParquetReaderForPath and LlapParquetReadRequest. Both were added on this branch for a future Iceberg caller, nothing in tree calls them, and API shaped for a caller that does not exist yet should land with that caller. Co-Authored-By: Claude Code <noreply@anthropic.com>
The store listed page checksum verification as deliberately unsupported. That is the wrong call for a cache: these bytes have been through LLAP's allocator and may have been sitting in off-heap memory for hours, so this path has strictly more ways to produce a corrupt page than a fresh file read does, and an uncaught corrupt page is a wrong query answer rather than an error. Mirror ParquetFileReader.Chunk.verifyCrc, gated on the same parquet.page.verify-checksum.enabled and header.isSetCrc(), with parquet's own two messages, and carry the header checksum onto the page as upstream does. The CRC runs over the cache slices in place rather than over a materialized BytesInput - upstream has to borrow an allocator for that, we already hold the bytes. V2 hashes the three parts in page order. Co-Authored-By: Claude Code <noreply@anthropic.com>
Each new unit test was checked against the pre-fix tree, so it fails for the
reason the finding describes rather than merely passing after it:
- testFallbackReaderRefillsAHoleOnTheNativeReaderBoundaries: the F1 blocker.
Fills the cache with the native reader, evicts one buffer whose chunk-relative
start is deliberately off the maxAlloc grid, then reads the file through
LlapCacheAwareFs. Pre-fix this throws AssertionError: Lower bound for start
offset 5124 is [4, 6040).
Worth recording what the repro turned out to need: the two tilings are
interoperable on geometry alone, because sliceChunk accepts any contiguous
tiling and getFileData returns whatever covers the range. The observable
failure was the two "% maxAlloc" assertions in getAndValidateMissingChunks,
which only run on a partially cached chunk - so a full cache hit in either
order never reproduced it, and only an eviction does.
- testCollisionWithDifferentLengthUnlocksBothBuffers: F2. Fails pre-fix with
expected:<2> but was:<3>, the refcount the failed put left behind. Asserts
refcount deltas rather than isLocked(), since both buffers legitimately hold a
reference at that point.
- testCorruptCachedPageIsRejectedWhenChecksumsAreVerified: F7. Flips a byte in
the chunk payload the simulated cache buffers are filled from, leaving footer
and page headers intact, and asserts the page is rejected with parquet's own
message - and handed over unchecked with verification off.
Two fixture problems the new coverage exposed:
- TestParquetEncodedDataReader was running on a geometry production never uses.
ParquetCacheLayout clamps minBuffer up to the allocator maximum, so with the
256Kb default floor and the harness's 4Kb maxAlloc it collapsed to
minBuffer == maxBuffer and tiled every chunk the way the pre-layout code did.
Setting hive.llap.io.encode.alloc.size to 1Kb puts the harness on the split
geometry a real daemon uses.
- Comparing getCrc() in the page parity test found a real bug in the F7 commit:
the checksum was carried onto the compressed page but dropped when readPage()
built the decompressed copy, at all three sites upstream's chunk reader copies
it. Fixed with carryCrc. Coverage.pagesWithCrc now asserts every page in the
fixture carries a checksum, so the comparison cannot go vacuous.
q-tests: parquet_llap_counters.q (cold/warm cache byte counters, then the same
file read by both readers in one session) and
parquet_types_vectorization_llap_io.q (the existing types test re-run with the
native flag, same expected answers). Both under the minillap driver. The golden
.q.out files are not generated here - that needs a cluster run:
mvn test -pl itests/qtest -Pitests -Dtest=TestMiniLlapCliDriver \
-Dqfile=parquet_llap_counters.q,parquet_types_vectorization_llap_io.q \
-Dtest.output.overwrite=true
Co-Authored-By: Claude Code <noreply@anthropic.com>
No behaviour change, except where a counter or a log level was itself the finding. ParquetColumnVectorProducer: incrCacheReadRequests() now runs after the fallback decision. A split that declines - nested types in the projection - or that throws reading the footer never touches the cache, so counting it made the daemon's hit ratio a ratio over requests that were never made. The decline log drops from INFO to DEBUG, since it is the planned outcome for a whole class of queries and fires once per split; the IOException path keeps WARN but now logs the throwable instead of e.toString(), so the stack survives. ParquetRangeBuffers -> VectoredReadBuffers, moved from ql org.apache.hadoop.hive.llap to llap-server org.apache.hadoop.hive.llap.io.encoded next to its only caller, and tightened to package-private. Nothing in ql referenced it. Nothing in it is Parquet-specific either - it is the staging area for readVectored - so the name no longer claims otherwise. ParquetCacheLayout stays in ql: since the layout was shared with LlapCacheAwareFs it has callers in both modules. ParquetCacheLayout: maxRange -> rangeCapBytes, and the repeated largest-power-of-two-or-remainder expression is now one nextPiece(long) used by both passes of bufferSizes, so the counting pass and the filling pass cannot drift apart. Javadoc says where the 8 MB floor comes from and why it is a floor rather than the cap. ParquetRowGroupDecoder: getColumns() hoisted out of the per-column loop (it rebuilds the whole leaf list on each call, and it is the same list for every column of every row group). The class javadoc no longer claims the logic was lifted verbatim from VectorizedParquetRecordReader - the schema errors are parquet's own exception types here. checkListColumnSupport's javadoc described an accept-list; it is a reject-list. The MAP wrapper-group comment was off by one: the check fires after the increment, so it tolerates one group more than the constant says. Left as behaviour, it predates this work. LlapIoImpl: the two footer-cache lines go to DEBUG - they fire per file and report nothing abnormal. HiveConf: hive.llap.io.parquet.native.enabled now says it is read per query from the session conf, that it needs hive.llap.io.memory.mode=cache, that it takes precedence over hive.llap.io.encode.formats, and what the nested-type fallback costs. Co-Authored-By: Claude Code <noreply@anthropic.com>
Two review follow-ups, one refactor from both ends. LlapIo kept three methods that only make sense for one file format, so every caller of the facade saw the union of all of them and adding a format widened the interface. Move them behind LlapIo.orc() / LlapIo.parquet(): getOrcTailFromCache -> orc().getTailFromCache llapVectorizedOrcReaderForPath -> orc().vectorizedReaderForPath getParquetFooterBuffersFromCache -> parquet().getFooterBuffersFromCache LlapParquetIo gets only the footer method; its vectorized-reader-for-path counterpart was deleted in de1e03100bd for having no in-tree caller and lands with the Iceberg one that needs it. In LlapIoImpl the two halves become inner classes holding their own producer, which also replaces the orcCvp/parquetCvp fields and the instanceof chain in getInputFormat: each implements LlapFormatIo (input format class + nullable producer) and nativeProducerFor walks the list. Adding a format is now one entry there rather than another branch. Behaviour is unchanged on every reachable path - OrcInputFormat is the only LlapWrappableInputFormatInterface in the tree, so a non-null SerDe never arrives with a natively served format, which is what lets the SerDe alone pick the generic producer. Co-Authored-By: Claude Code <noreply@anthropic.com>
ROW__LINEAGE__ID / LAST__UPDATED__SEQUENCE__NUMBER cannot be filled here: this pipeline takes its projection from DataWritableReadSupport.getRequestedSchema alone, while the stock reader augments it via RowLineageUtils#getRequestedSchemaWithRowLineageColumns (VectorizedParquetRecordReader:272). An unfilled slot is not a visible failure. initializeRowLineageColumns seeds -1, and HiveBatchIterator:144 reads that as "the file predates lineage, synthesize the id from the manifest" - so the query answers with well-formed row ids that are not the stored ones, and CopyOnWriteMergeRewriter writes them back into the data files. Unreachable in-tree today: only Iceberg offers these columns, and its Parquet reads do not come here yet. HIVE-30103 changes that, so the check is cheap now rather than remembered later. Conf-only by design - an admission check, not the lineage support that belongs in HIVE-30103. Co-Authored-By: Claude Code <noreply@anthropic.com>
Eight production call sites repeated the same expression: getVar on HIVE_VECTORIZED_INPUT_FORMAT_SUPPORTS_ENABLED, equalsIgnoreCase against the HIVE_VECTORIZED_INPUT_FORMAT_SUPPORTS_DECIMAL_64 token. They now call HiveConf.isDecimal64VectorizationEnabled(conf). The conf != null guard that RecordReaderImpl and WriterImpl carried is folded into the helper, so the other six change from NPE-on-null to not-enabled. A null conf means no Hive conf was threaded through, which is what those two already treated as not-enabled; the javadoc says so rather than leaving it implicit. The helper sits with the getVar statics, not beside the token constant - that is a field block, and getLogIdVar is the precedent. Co-Authored-By: Claude Code <noreply@anthropic.com>
copyCrc and carryCrc become two withCrc overloads, one reading the page header and one reading the compressed page. Both now return the page, so every page is built as withCrc(source, new Page(...)), with no separate statement to patch the CRC afterwards. Co-Authored-By: Claude Code <noreply@anthropic.com>
…em counters StatsRecordingThreadPool adds an LLAP IO thread's file system statistics to the fragment's counters only when the read callable is a TezCounterSource. OrcEncodedDataReader is one; ParquetEncodedDataReader was not, so the native path's column chunk reads were dropped (with a "TezCounters is null" warning) and HDFS_BYTES_READ left out every cache miss. Implement it the way ORC does. parquet_llap_counters.q.out now counts the cold reads: 2454 bytes for the first query (873 of them the t chunk) and 14570 for the i and s chunks in the third. Co-Authored-By: Claude Code <noreply@anthropic.com>
The native reader opened the split's file before looking anything up, so a split the cache served whole still paid an open: on HDFS a NameNode round trip (getBlockLocations) per split. ORC opens lazily. The stream and its vectored-read buffers now sit behind LazyFile, which opens on the first miss. parquet_llap_counters.q.out: the two fully cached queries drop from 3 to 2 HDFS_READ_OPS, as for ORC's warm reads in orc_llap_counters.q.out. Co-Authored-By: Claude Code <noreply@anthropic.com>
…l variations, stock reads of natively cached files - TestParquetNativeIOCacheDisabled: with hive.llap.io.memory.mode=none there is no Parquet producer and LLAP IO declines the split (building the producer unconditionally fails it) - testDecimalsFollowThePlansPhysicalVariation: decimals come in the variation the plan reads them in (deciding from the job's DECIMAL_64 input format support instead fails it; the existing tests all pass under that change) - TestLlapCacheAwareFsInterop: the stock reader caches a chunk on the native layout and re-caches an evicted piece as that piece; two readers missing the same chunk free the buffers the cache did not take (removing allocator.deallocate(fresh) fails it) - testStockReadOfANativelyCachedFileWithAnEvictedPiece: the stock reader's fallback read of a natively cached file
… as the native reader does - LlapCacheAwareFs built its ParquetCacheLayout from the conf given to registerFile, the job's, while ParquetEncodedDataReader builds it from the daemon's; when the job's hive.llap.io.encode.alloc.size differs from the daemon's (set only on the daemon, or by a session SET) the two readers cut a chunk into different pieces: a stock read re-caches an evicted tail as one buffer, and a concurrent miss fails the query with "Found a block with different length at the same offset" - registerFile takes the layout; VectorizedParquetRecordReader builds it from the daemon's cache conf - testStockReadRecachesAnEvictedTailAsItsPieces fails with the layout built from the job conf
e4ad527 to
78b4620
Compare
What changes were proposed in this pull request?
Native LLAP encoded-data path for Parquet, mirroring the existing ORC pipeline:
ParquetColumnVectorProducer→ParquetEncodedDataReader→ParquetEncodedDataConsumer, feedingVectorizedColumnReaders through a sharedParquetRowGroupDecoder(extracted fromVectorizedParquetRecordReader).MemoryBuffer[]inParquetEncodedColumnBatch;ParquetCachedPageReadStoreparses pages in place over those cached buffers with lazy per-page decompression — noParquetFileReader, no copy from cache into a Parquet read buffer.hive.llap.io.parquet.native.enable=true.Design doc: HIVE-30059-design-v3.pdf.
Why are the changes needed?
Parquet under LLAP already caches byte-ranges via
LlapCacheAwareFs, but reads still go through a stockParquetFileReaderover a virtualllapcache://URL: cached bytes get copied into the reader's own buffers, page parsing and decompression run per-read, and the path isn't wired into LLAP's encoded-column pipeline or fragment counters. This PR brings Parquet to parity with ORC's native LLAP path: zero-copy page parsing over cached buffers, lazy decompression, full LLAP IO counter integration (METADATA_CACHE_HIT/MISS,SELECTED_ROWGROUPS, HDFS/IO time), and the sharedColumnVectorProducer/EncodedDataConsumerexecution model.Does this PR introduce any user-facing change?
Better Parquet read performance under LLAP (zero-copy page parsing over cached buffers, lazy decompression). Query summaries now report
DATA_HIT/METADATA_CACHE_HIT(and their_MISScounterparts) for Parquet, so cache effectiveness is visible the same way it is for ORC. Opt-out viahive.llap.io.parquet.native.enable=false.How was this patch tested?
TestParquetEncodedDataReader(27 tests) — plan/fetch/assemble, cache hit/miss, footer caching, encryption fallback, schema evolution.TestParquetCacheLayout,TestParquetRangeBuffers(8 tests) — page-range math and buffer reuse.TestVectorizedParquetRecordReaderunchanged (sharedParquetRowGroupDecoder).