Skip to content

perf(common): avoid redundant V1 archived timeline loads in incremental queries - #19553

Open
fhan688 wants to merge 2 commits into
apache:masterfrom
fhan688:avoid-redundant-V1-archived-timeline-loads-in-incremental-queries
Open

perf(common): avoid redundant V1 archived timeline loads in incremental queries#19553
fhan688 wants to merge 2 commits into
apache:masterfrom
fhan688:avoid-redundant-V1-archived-timeline-loads-in-incremental-queries

Conversation

@fhan688

@fhan688 fhan688 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

This is a follow-up to #19338 and addresses the archived-timeline optimization suggested in #19338 (comment).

For timeline layout V1, CompletionTimeQueryViewV1 loaded archived instants to identify candidate requested times, after which IncrementalQueryAnalyzer discarded that timeline and loaded the archive again to construct QueryContext. Since V1 completion time is equivalent to requested time, the second scan is redundant.

Summary and Changelog

  • Add a dedicated V1 analysis path in IncrementalQueryAnalyzer.
  • Load the filtered archived timeline at most once when the requested range overlaps archived instants.
  • Apply InstantRange directly to HoodieInstant.requestedTime() and reuse the same archived timeline in QueryContext.
  • Avoid archive loading for active-only and snapshot ranges; for end-only ranges, check the filtered active timeline before loading the archive.
  • Keep the V2 completion-time query path unchanged.
  • Add tests covering single archive loading, timeline reuse, range boundaries, active-only and snapshot paths, limits, and V2 behavior.

Impact

Improves incremental-query performance for timeline layout V1 (table versions 5–7) by eliminating a redundant archived-timeline scan. There are no public API, configuration, storage-format, or V2 semantic changes. Query results and existing range semantics remain unchanged.

Risk Level

low

The change touches V1 incremental range selection but keeps V2 on the existing path. It is covered by TestIncrementalQueryAnalyzer and TestCompletionTimeQueryViewV1: 16 tests passed with no failures or errors. Reactor compilation, Checkstyle, RAT, and git diff --check also passed.

Documentation Update

none. This is an internal performance optimization with no new user-facing behavior or configuration.

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

@github-actions github-actions Bot added the size:L PR with lines of changes in (300, 1000] label Aug 7, 2026
@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.47059% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.12%. Comparing base (65cf7e8) to head (a82ff9c).
⚠️ Report is 88 commits behind head on master.

Files with missing lines Patch % Lines
...di/common/table/read/IncrementalQueryAnalyzer.java 96.47% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19553      +/-   ##
============================================
+ Coverage     76.84%   78.12%   +1.27%     
- Complexity    32379    33685    +1306     
============================================
  Files          2522     2540      +18     
  Lines        139106   141471    +2365     
  Branches      16713    18045    +1332     
============================================
+ Hits         106892   110520    +3628     
+ Misses        24621    23248    -1373     
- Partials       7593     7703     +110     
Components Coverage Δ
hudi-common 83.58% <96.47%> (+0.31%) ⬆️
hudi-client 83.13% <ø> (+1.13%) ⬆️
hudi-flink 85.65% <ø> (+0.95%) ⬆️
hudi-spark-datasource 72.54% <ø> (+1.94%) ⬆️
hudi-utilities 74.50% <ø> (+0.86%) ⬆️
hudi-cli 15.06% <ø> (-0.27%) ⬇️
hudi-hadoop 70.10% <ø> (+6.59%) ⬆️
hudi-sync 75.54% <ø> (+0.42%) ⬆️
hudi-io 79.76% <ø> (+0.30%) ⬆️
hudi-timeline-service 83.44% <ø> (ø)
hudi-cloud 65.81% <ø> (+1.74%) ⬆️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 51.48% <92.94%> (+1.34%) ⬆️
flink-integration-tests 48.84% <40.00%> (-0.11%) ⬇️
hadoop-mr-java-client 43.98% <0.00%> (+0.25%) ⬆️
integration-tests 13.51% <0.00%> (-0.06%) ⬇️
spark-client-hadoop-common 50.34% <0.00%> (+0.71%) ⬆️
spark-java-tests 52.14% <91.76%> (+0.56%) ⬆️
spark-scala-tests 46.72% <24.70%> (+0.74%) ⬆️
utilities 36.29% <38.82%> (-0.30%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...di/common/table/read/IncrementalQueryAnalyzer.java 96.55% <96.47%> (-0.87%) ⬇️

... and 315 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for the contribution! This PR splits IncrementalQueryAnalyzer.analyze into dedicated V1 and V2 paths so that V1 resolves incremental ranges by applying InstantRange directly to requestedTime and loads the filtered archived timeline at most once, avoiding the redundant second archive scan. I traced the new V1 branches against the V2 reference semantics in CompletionTimeQueryViewV2.getInstantTimes and the shared createQueryContext, and the behavior appears preserved. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. - a Hudi committer or PMC member can take it from here for a final review.
. A few naming and structural suggestions below, but overall the code is clean and well-organized.

cc @yihua

}

@Test
void testV1ActiveOnlyAndSnapshotRangesDoNotLoadArchive() {

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.

🤖 nit: this test covers two distinct cases (active-only range and earliest-snapshot range) in one method, which makes it harder to pinpoint which scenario caused a failure. Could you split it into testV1ActiveOnlyRangeDoesNotLoadArchive and testV1EarliestSnapshotDoesNotLoadArchive?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@fhan688

fhan688 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

this is the potential improvement mentioned in #19338, please help review, thanks! @danny0405

@cshuo cshuo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two correctness issues in the V1 archived-timeline path.

if (startCompletionTime.isEmpty() && endCompletionTime.isPresent()) {
// (_, end] returns the last eligible instant at or before end. Check the filtered active
// timeline first and only load the archive when there is no active match.
activeInstants = getLastInstantAtOrBefore(completedTimeline, endCompletionTime.get());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This shortcut assumes that any eligible active instant is newer than every archived instant, which is not guaranteed with hoodie.archive.beyond.savepoint=true. A savepointed commit can remain active while later commits are archived; for example, active [101, 105] and archived [102, 103, 104]. For an end-only query ending at 104, this returns active instant 101 without checking the archive, omitting the actual latest eligible instant 104.

Please only use the active-only shortcut when the selected active instant is on or after the archive boundary; otherwise compare the best active and archived candidates. A regression test covering this savepoint-hole scenario would also be helpful.

// streaming read speed limit, limits the maximum number of active commits allowed per run
activeInstants = activeInstants.subList(0, limit);
}
List<String> instants = Stream.concat(archivedInstants.stream(), activeInstants.stream())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This concatenation does not guarantee global requested-time ordering. With hoodie.archive.beyond.savepoint=true, an old savepointed commit may remain active while later commits are archived. For example, archived [102, 103, 104] and active [101] produces [102, 103, 104, 101]. Since lastInstant is taken from the final list element, the resulting end boundary regresses to 101, and downstream readers can omit 102104.

Please globally deduplicate and sort the combined requested-time list before deriving lastInstant. The previous V1 candidate path explicitly applied both distinct() and sorted(), so preserving those properties would also handle overlap during concurrent archival.

@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

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

Labels

size:L PR with lines of changes in (300, 1000]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants