Skip to content

ORC-2223 : [Java] RecordReaderImpl reads and parses each StripeFooter twice per stripe during scans - #2717

Open
BsoBird wants to merge 1 commit into
apache:mainfrom
BsoBird:Fix-StripeFooter-read/parsed-twice-per-stripe
Open

BsoBird wants to merge 1 commit into
apache:mainfrom
BsoBird:Fix-StripeFooter-read/parsed-twice-per-stripe

Conversation

@BsoBird

@BsoBird BsoBird commented Oct 10, 2026

Copy link
Copy Markdown

(java): reuse stripe footer in RecordReaderImpl to avoid duplicate read

  • Add an overloaded StripePlanner.parseStripe that accepts an OrcProto.StripeFooter already read by the caller, keeping the existing two-arg parseStripe as a thin wrapper that reads the footer itself (java/core/src/java/org/apache/orc/impl/reader/ StripePlanner.java).
  • RecordReaderImpl.readStripe now passes the footer obtained in beginReadStripe to planner.parseStripe, eliminating one readStripeFooter call per stripe during scans (java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java).
  • Lower read-percentage thresholds in TestMinSeekSize, TestRowFilteringComplexTypesNulls, and TestRowFilteringIOSkip to reflect the reduced bytes read per stripe.
  • Add TestRecordReaderImpl.testNoDuplicateStripeFooterRead, a regression test asserting exactly one footer read per stripe for both plain and SArg-filtered scans.

What changes were proposed in this pull request?

This PR eliminates a duplicate StripeFooter read in the Java reader: each stripe's footer was being read and parsed twice per scan.

RecordReaderImpl.beginReadStripe() already reads the OrcProto.StripeFooter for the current stripe, but RecordReaderImpl.readStripe() then called StripePlanner.parseStripe(stripe, fileIncluded), which read the same footer again internally.

Changes:

  1. Add an overloaded StripePlanner.parseStripe(StripeInformation, boolean[], OrcProto.StripeFooter) that accepts a footer already read by the caller. The existing two-argument parseStripe is kept as a thin wrapper that reads the footer itself, so other callers are unaffected.
  2. RecordReaderImpl.readStripe() now passes the footer obtained in beginReadStripe() to the new overload, removing one DataReader.readStripeFooter call per stripe.
  3. Lowered read-percentage thresholds in TestMinSeekSize, TestRowFilteringComplexTypesNulls, and TestRowFilteringIOSkip to reflect the genuinely reduced bytes read per stripe.

Why are the changes needed?

Every stripe read during a scan performed a redundant footer read (plus protobuf re-parse) from the underlying DataReader. On remote/object stores or for files with many stripes, this is a measurable and avoidable source of extra I/O and CPU that scales linearly with the number of stripes read. The fix removes the duplication without changing any read semantics: both reads previously fetched the identical footer bytes for the same stripe.

How was this patch tested?

Added a new regression test TestRecordReaderImpl.testNoDuplicateStripeFooterRead. It installs a Mockito spy on the DataReader (stubbing clone() to return the spy, since RecordReaderImpl clones the supplied reader) and verifies readStripeFooter is invoked exactly once per stripe — covering both a plain scan and an SArg-filtered scan (a predicate that evaluates to YES_NO for every row group, so pickRowGroups is exercised without skipping whole stripes).

Also adjusted the read-percentage assertions in TestMinSeekSize, TestRowFilteringComplexTypesNulls, and TestRowFilteringIOSkip: those tests assert lower bounds on bytes read, and the fix legitimately reduces bytes read, so thresholds were lowered accordingly (e.g. 5.9 -> 5.0, 0.06 -> 0.03) with comments explaining why.

Run: cd java && ./mvnw test -pl core

Was this patch authored or co-authored using generative AI tooling?

Yes. co-authored-by: Claude (Anthropic)

@cxzl25
cxzl25 requested a balanced review from Copilot October 13, 2026 10:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open findings
What changed in this PR

Eliminates redundant StripeFooter reads during Java ORC scans by reusing the already-read footer when planning stripe reads, and updates tests to reflect reduced I/O.

Changes:

  • Added an overloaded StripePlanner.parseStripe that accepts a pre-read OrcProto.StripeFooter.
  • Updated RecordReaderImpl.readStripe() to pass the reused footer, avoiding a second readStripeFooter per stripe.
  • Updated/added tests: lowered read-percentage thresholds and introduced a regression test to assert exactly one footer read per stripe.
File Description
java/​core/​src/​java/​org/​apache/​orc/​impl/​reader/​StripePlanner.java Adds overload to parse stripes with a caller-provided footer to avoid re-reading.
java/​core/​src/​java/​org/​apache/​orc/​impl/​RecordReaderImpl.java Uses the reused footer when parsing stripes to eliminate duplicate footer reads.
java/​core/​src/​test/​org/​apache/​orc/​impl/​TestRecordReaderImpl.java Adds regression test verifying readStripeFooter is called once per stripe.
java/​core/​src/​test/​org/​apache/​orc/​TestRowFilteringIOSkip.java Lowers I/O thresholds to match reduced reads after footer dedup.
java/​core/​src/​test/​org/​apache/​orc/​TestRowFilteringComplexTypesNulls.java Lowers I/O thresholds to match reduced reads after footer dedup.
java/​core/​src/​test/​org/​apache/​orc/​TestMinSeekSize.java Lowers I/O threshold to match reduced reads after footer dedup.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

*/
public StripePlanner parseStripe(StripeInformation stripe,
boolean[] columnInclude,
OrcProto.StripeFooter footer) throws IOException {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion — I think we should keep the new three-arg overload declared with throws IOException.

Although the 3-arg overload doesn't itself call DataReader.read* today, it performs parsing work that can surface I/O-related failures (and may in future). Keeping the checked IOException:

  • preserves the existing error-handling contract and avoids surprising callers;
  • keeps the two-arg wrapper and callers simple (no extra catch/wrap logic required);
  • future-proofs the API in case the implementation later needs I/O or underlying helpers throw IOException.

If we want to remove the checked exception, we should do that deliberately and consistently (for example by converting IOExceptions to UncheckedIOException in one place and updating call sites and documentation). I don't recommend silently narrowing the overload's declared exceptions in this patch.

Comment on lines +2877 to +2882
// SArg scan: a predicate that evaluates to YES_NO for every row group so
// pickRowGroups is exercised but no stripe is skipped.
SearchArgument sarg = SearchArgumentFactory.newBuilder()
.startAnd()
.lessThan("x", PredicateLeaf.Type.LONG, (long) (stripeCount * rowsPerStripe + 1))
.end().build();
Comment on lines +2901 to +2905
DataReader spied = spy(RecordReaderUtils.createDefaultDataReader(properties));
// RecordReaderImpl clones the supplied DataReader before use, so make the
// spy observe all readStripeFooter calls by returning itself from clone().
when(spied.clone()).thenReturn(spied);
Reader.Options readerOptions = reader.options().dataReader(spied);
// drain
}
}
verify(spied, times(stripeCount)).readStripeFooter(any());
@BsoBird
BsoBird force-pushed the Fix-StripeFooter-read/parsed-twice-per-stripe branch from 9ff6fb5 to b266311 Compare October 18, 2026 11:23
…e read

- Add an overloaded StripePlanner.parseStripe that accepts an
  OrcProto.StripeFooter already read by the caller, keeping the
  existing two-arg parseStripe as a thin wrapper that reads the
  footer itself (java/core/src/java/org/apache/orc/impl/reader/
  StripePlanner.java).
- RecordReaderImpl.readStripe now passes the footer obtained in
  beginReadStripe to planner.parseStripe, eliminating one
  readStripeFooter call per stripe during scans
  (java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java).
- Lower read-percentage thresholds in TestMinSeekSize,
  TestRowFilteringComplexTypesNulls, and TestRowFilteringIOSkip to
  reflect the reduced bytes read per stripe.
- Add TestRecordReaderImpl.testNoDuplicateStripeFooterRead, a
  regression test asserting exactly one footer read per stripe for
  both plain and SArg-filtered scans.
@BsoBird
BsoBird force-pushed the Fix-StripeFooter-read/parsed-twice-per-stripe branch from b266311 to 08fde06 Compare October 18, 2026 11:33
@BsoBird

BsoBird commented Oct 19, 2026

Copy link
Copy Markdown
Author

@cxzl25 Hello sir, can u check again?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants