Skip to content

HIVE-30059: LLAP IO Cache: native parquet data cache - #6793

Open
abstractdog wants to merge 71 commits into
apache:masterfrom
abstractdog:parquet-io-cache
Open

abstractdog wants to merge 71 commits into
apache:masterfrom
abstractdog:parquet-io-cache

Conversation

@abstractdog

@abstractdog abstractdog commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Native LLAP encoded-data path for Parquet, mirroring the existing ORC pipeline:

  • ParquetColumnVectorProducer → ParquetEncodedDataReader → ParquetEncodedDataConsumer, feeding VectorizedColumnReaders through a shared ParquetRowGroupDecoder (extracted from VectorizedParquetRecordReader).
  • Column chunks are held natively as MemoryBuffer[] in ParquetEncodedColumnBatch; ParquetCachedPageReadStore parses pages in place over those cached buffers with lazy per-page decompression — no ParquetFileReader, no copy from cache into a Parquet read buffer.
  • Enabled by default via 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 stock ParquetFileReader over a virtual llapcache:// 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 shared ColumnVectorProducer/EncodedDataConsumer execution 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 _MISS counterparts) for Parquet, so cache effectiveness is visible the same way it is for ORC. Opt-out via hive.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.
  • Existing TestVectorizedParquetRecordReader unchanged (shared ParquetRowGroupDecoder).
  • Manual verification through miniHS2 with LLAP.

@abstractdog abstractdog changed the title Parquet io cache HIVE-30059: LLAP IO Cache: native parquet data cache Sep 16, 2026
Comment thread llap-server/src/java/org/apache/hadoop/hive/llap/io/api/impl/LlapIoImpl.java Outdated

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.

🟡 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 FileSplit and silently discards the fileKey and tag supplied by the public API. The native reader then derives a different key from the job configuration/filesystem in loadFooter() 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

  • dataCache is non-null in LLAP_IO_MEMORY_MODE=none because LlapIoImpl assigns a SimpleBufferManager to it while leaving fileMetadataCache null. With the native flag enabled, this condition still creates parquetCvp; ParquetEncodedDataReader.loadFooter then calls getParquetFooterBuffersFromCache, 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 parquetCvp is constructed independently of the per-query flag and this check only tests for null. A caller can therefore set hive.llap.io.parquet.native.enabled=false and still start the native reader, bypassing the configuration gate used by HiveInputFormat. Include ConfVars.LLAP_IO_PARQUET_NATIVE_ENABLED in 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.consumeData returns normally and ParquetEncodedDataReader.performDataRead continues fetching and decoding later row groups before calling setDone. 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 let consumeData perform 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, colsToInclude can be empty while requestedSchema still 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 when includes.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.getAndValidateMissingChunks requires every missing gap to start and end on maxAlloc boundaries, 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 at chunkStart + 4 MiB and throws an AssertionError instead 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.

abstractdog added a commit to abstractdog/hive that referenced this pull request Sep 17, 2026
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>
abstractdog added a commit to abstractdog/hive that referenced this pull request Sep 17, 2026
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>
Comment thread common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
abstractdog added a commit to abstractdog/hive that referenced this pull request Sep 18, 2026
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>
abstractdog added a commit to abstractdog/hive that referenced this pull request Sep 18, 2026
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>
abstractdog added a commit to abstractdog/hive that referenced this pull request Sep 18, 2026
- 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>
abstractdog and others added 25 commits October 15, 2026 10:58
… 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

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants