Skip to content

fix(common): filter logs by latest commit time in LSM reader - #19984

Open
cshuo wants to merge 1 commit into
apache:masterfrom
cshuo:fix_lsm_fg_reader
Open

cshuo wants to merge 1 commit into
apache:masterfrom
cshuo:fix_lsm_fg_reader

Conversation

@cshuo

@cshuo cshuo commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #19983.

HoodieLsmFileGroupReader can read native data/delete logs newer than latestCommitTime, causing time-travel reads to apply future updates or deletions. Selecting a file slice by its base instant does not exclude later logs within that slice. For example, a delete at 004 must not remove a record when reading as of 002.

Summary and Changelog

  • Filter native logs by deltaCommitTime <= latestCommitTime before constructing the input split, then apply the existing optional InstantRange.
  • Add regression coverage for older logs, data/delete logs at the inclusive boundary, excluded future logs, and an explicit instant range extending past the boundary.
  • Verify that filtering out all logs preserves the base-file-only path and duplicate base records.

Impact

LSM reads exclude updates and deletes after the query instant. Excluded log files are not opened. No public API, storage format, or configuration changes.

Risk Level

Low. The change applies the existing classic scanner's upper-bound semantics at native-log file level. Regression tests reproduce the failure before the fix. All 24 tests across TestHoodieLsmFileGroupReader, TestLsmFileGroupRecordIterator, TestLsmFileIterators, TestSpillableLsmRecordIterator, and TestLsmReaderUtils pass after the fix. Maven Checkstyle and git diff --check also pass.

Documentation Update

None.

Contributor's checklist

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

// filter log files by instant range.
if (logFiles != null && readerContext.getInstantRange().isPresent()) {
InstantRange instantRange = readerContext.getInstantRange().get();
logFiles = logFiles.filter(logFile -> instantRange.isInRange(logFile.getDeltaCommitTime()));

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.

logFiles.filter(logFile -> instantRange.isInRange(logFile.getDeltaCommitTime())); this already works, the logFile.getDeltaCommitTime is the delta comit time of the instant.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The existing filter works when InstantRange is present, but Spark time-travel queries leave it empty and pass the as-of timestamp through latestCommitTime. A selected slice can still contain later logs, so we need this cutoff.
Replacing latestCommitTime with InstantRange in the reader context could be handled in a follow-up refactoring PR.

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.

🤖 For what it's worth, this matches what the legacy log path already does independently of InstantRange: BaseHoodieLogRecordReader (around L296) skips any data/delete block whose INSTANT_TIME header is greater than readerContext.getLatestCommitTime(), and only applies InstantRange on top of that. The Spark time-travel path sets latestCommitTime without an InstantRange, so the LSM reader had no upper bound at all — this change just brings it to parity at file granularity.

@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Sep 17, 2026
@codecov-commenter

codecov-commenter commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.39%. Comparing base (e71e6cc) to head (3827a32).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19984      +/-   ##
============================================
+ Coverage     80.33%   80.39%   +0.05%     
- Complexity    34752    34941     +189     
============================================
  Files          2543     2544       +1     
  Lines        142478   143038     +560     
  Branches      17308    17462     +154     
============================================
+ Hits         114464   114995     +531     
- Misses        20119    20130      +11     
- Partials       7895     7913      +18     
Components Coverage Δ
hudi-common 83.97% <100.00%> (+0.12%) ⬆️
hudi-client 83.38% <ø> (+<0.01%) ⬆️
hudi-flink 85.78% <ø> (+0.07%) ⬆️
hudi-spark-datasource 73.77% <ø> (+<0.01%) ⬆️
hudi-utilities 78.18% <ø> (-0.02%) ⬇️
hudi-cli 69.99% <ø> (ø)
hudi-hadoop 70.90% <ø> (-0.02%) ⬇️
hudi-sync 76.02% <ø> (ø)
hudi-io 81.61% <ø> (ø)
hudi-timeline-service 83.34% <ø> (ø)
hudi-cloud 81.03% <ø> (+0.04%) ⬆️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 52.23% <100.00%> (+0.15%) ⬆️
flink-integration-tests 49.19% <100.00%> (+0.04%) ⬆️
hadoop-mr-java-client 44.00% <0.00%> (+0.09%) ⬆️
integration-tests 13.48% <0.00%> (+0.01%) ⬆️
spark-client-hadoop-common 38.55% <0.00%> (-0.01%) ⬇️
spark-java-tests 52.28% <100.00%> (+<0.01%) ⬆️
spark-scala-tests 46.95% <0.00%> (+0.01%) ⬆️
utilities 36.87% <0.00%> (+0.04%) ⬆️

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

Files with missing lines Coverage Δ
...ommon/table/read/lsm/HoodieLsmFileGroupReader.java 89.21% <100.00%> (+0.21%) ⬆️

... and 23 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-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

@cshuo cshuo closed this Sep 17, 2026
@cshuo cshuo reopened this Sep 18, 2026

@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 makes HoodieLsmFileGroupReader drop native data/delete log files whose delta commit time is newer than latestCommitTime, mirroring the block-level upper-bound check the classic log scanner applies, so time-travel and bounded reads no longer see future updates/deletes. I traced the callers (Spark file format / MOR RDD, Flink FormatUtils incl. streaming/CDC/bootstrap/clustering splits, MDT reader, and the LSM merge handle for upsert/compaction) and the latestCommitTime each passes is already the intended upper bound for the equivalent classic path, so the new unconditional filter is consistent with existing semantics. No issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review.

cc @yihua

// filter log files by instant range.
if (logFiles != null && readerContext.getInstantRange().isPresent()) {
InstantRange instantRange = readerContext.getInstantRange().get();
logFiles = logFiles.filter(logFile -> instantRange.isInRange(logFile.getDeltaCommitTime()));

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.

🤖 For what it's worth, this matches what the legacy log path already does independently of InstantRange: BaseHoodieLogRecordReader (around L296) skips any data/delete block whose INSTANT_TIME header is greater than readerContext.getLatestCommitTime(), and only applies InstantRange on top of that. The Spark time-travel path sets latestCommitTime without an InstantRange, so the LSM reader had no upper bound at all — this change just brings it to parity at file granularity.

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

Labels

size:M PR with lines of changes in (100, 300]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] LSM reader includes logs newer than the query instant

5 participants