Skip to content

fix: harden async indexing and improve coverage - #19537

Open
danny0405 wants to merge 2 commits into
apache:masterfrom
danny0405:index-coverage
Open

fix: harden async indexing and improve coverage#19537
danny0405 wants to merge 2 commits into
apache:masterfrom
danny0405:index-coverage

Conversation

@danny0405

@danny0405 danny0405 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

Several table upgrade/downgrade handlers, async-index actions, and bucket-index utilities in hudi-client-common had significant line-coverage gaps. Critical paths such as legacy table-version transitions, index catch-up with concurrent commits, abort cleanup, bucket assignment, and consistent-hashing metadata recovery were not exercised directly.

The added async-index coverage also exposed two correctness issues: catch-up progress was not propagated back to the executor, so index commit metadata could retain the original base instant after later commits were processed; and the metadata writer used while initializing the FILES partition was not closed.

Summary and Changelog

  • Propagate the last successfully processed instant from the production catch-up task to RunIndexActionExecutor and record it in HoodieIndexCommitMetadata.
  • Close the metadata writer used by the FILES initialization branch on both success and failure.
  • Add a regression test that processes a real completed commit through the production catch-up task and asserts the returned indexUptoInstant.
  • Assert metadata-writer closure after successful FILES initialization and timeline-completion failure.
  • Add legacy handler tests for ZeroToOneUpgradeHandler, TwoToOneDowngradeHandler, FourToFiveUpgradeHandler, and FiveToSixUpgradeHandler, including config and file-layout transitions and failure paths.
  • Extend TestUpgradeDowngrade with fixture-backed v4-to-v5 and v5-to-v6 upgrades and verify data consistency after each transition.
  • Exercise UpgradeDowngrade version-hop orchestration, rollback/compaction selection, property changes, no-op validation, and failure handling.
  • Exercise async index scheduling, execution, concurrent-commit catch-up, metadata partition initialization, and partial-index cleanup.
  • Add direct coverage for simple bucket assignment/lookup and consistent-hashing metadata load, save, marker repair, and concurrent-creation recovery.
  • No code was copied.

Per-class line coverage:

Class Before (Codecov) After (focused JaCoCo)
ZeroToOneUpgradeHandler 0% 87.8%
TwoToOneDowngradeHandler 0% 88.6%
FourToFiveUpgradeHandler 0% 100.0%
FiveToSixUpgradeHandler 0% 100.0%
UpgradeDowngrade 76% 75.1%*
AbstractIndexingCatchupTask 42% 88.7%
RunIndexActionExecutor 72% 94.0%
ScheduleIndexActionExecutor 69% 86.4%
HoodieSimpleBucketIndex 46% 100.0%
ConsistentBucketIndexUtils 76% 78.8%

* The focused report excludes pre-existing suites; merged Codecov coverage retains their existing contribution.

Validation:

  • mvn -pl hudi-client/hudi-client-common -Punit-tests -Drat.skip=true -Dtest=TestLegacyUpgradeDowngradeHandlers,TestIndexingCatchupTask,TestIndexActionExecutors,TestHoodieSimpleBucketIndex,TestConsistentBucketIndexUtils,TestUpgradeDowngradeOrchestration test — 46 tests passed.
  • mvn -pl hudi-spark-datasource/hudi-spark -Dcheckstyle.skip -Drat.skip=true -Djacoco.skip=true -Dtest=TestUpgradeDowngrade#testLegacyUpgradeHandlersWithFixtureTables surefire:test — 2 tests passed.
  • mvn -pl hudi-client/hudi-client-common checkstyle:check — 0 violations.
  • mvn -pl hudi-spark-datasource/hudi-spark checkstyle:check — 0 violations.
  • git diff --check — passed.

Impact

Async index commits now report the latest successfully caught-up data instant, and FILES metadata initialization releases its writer resources deterministically. The catch-up task exposes that internal progress to the executor. There are no configuration, storage-format, user-facing API, or performance changes.

Risk Level

low

The production changes are localized to async-index completion and resource cleanup. The focused suite exercises completed-commit catch-up, writer closure on success and failure, abort cleanup, and index commit metadata generation.

Documentation Update

none — there are no new features, configurations, storage formats, or user-facing behavior to document.

Contributor's checklist

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

@codecov-commenter

codecov-commenter commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.78%. Comparing base (4a9cef8) to head (d788769).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
...udi/table/action/index/RunIndexActionExecutor.java 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19537      +/-   ##
============================================
+ Coverage     76.40%   76.78%   +0.37%     
+ Complexity    32398    32316      -82     
============================================
  Files          2520     2520              
  Lines        138985   139058      +73     
  Branches      16695    16748      +53     
============================================
+ Hits         106189   106771     +582     
+ Misses        25166    24675     -491     
+ Partials       7630     7612      -18     
Components Coverage Δ
hudi-common 83.25% <ø> (+0.89%) ⬆️
hudi-client 82.67% <77.77%> (+0.68%) ⬆️
hudi-flink 84.00% <ø> (+0.02%) ⬆️
hudi-spark-datasource 70.61% <ø> (-0.01%) ⬇️
hudi-utilities 73.66% <ø> (-0.01%) ⬇️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.50% <ø> (ø)
hudi-sync 70.92% <ø> (-0.08%) ⬇️
hudi-io 79.36% <ø> (-0.10%) ⬇️
hudi-timeline-service 83.44% <ø> (ø)
hudi-cloud 64.06% <ø> (ø)
hudi-kafka-connect 53.96% <ø> (+0.76%) ⬆️
Flag Coverage Δ
common-and-other-modules 50.19% <77.77%> (+0.63%) ⬆️
flink-integration-tests 48.77% <0.00%> (-0.04%) ⬇️
hadoop-mr-java-client 43.73% <0.00%> (-0.04%) ⬇️
integration-tests 13.57% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 49.62% <0.00%> (-0.01%) ⬇️
spark-java-tests 51.57% <44.44%> (+0.32%) ⬆️
spark-scala-tests 45.96% <44.44%> (-0.05%) ⬇️
utilities 36.59% <66.66%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
...able/action/index/AbstractIndexingCatchupTask.java 81.69% <100.00%> (+36.76%) ⬆️
...udi/table/action/index/RunIndexActionExecutor.java 88.74% <71.42%> (+15.95%) ⬆️

... and 55 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 working on this! This PR is test-only, adding focused unit and fixture-backed coverage for legacy upgrade/downgrade handlers, the upgrade orchestration flow, async-index scheduling/execution/catch-up, and the simple/consistent bucket-index utilities in hudi-client-common. I traced each new test through the production code it exercises (mock chains, assertion values, and exception/abort paths) and the tests genuinely drive the intended paths without passing vacuously or asserting wrong behavior. 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. One small static-import inconsistency in TestLegacyUpgradeDowngradeHandlers; everything else looks clean.

cc @yihua

when(config.getMarkersDeleteParallelism()).thenReturn(3);

ZeroToOneUpgradeHandler handler = org.mockito.Mockito.spy(new ZeroToOneUpgradeHandler());
org.mockito.Mockito.doNothing().when(handler).recreateMarkers(anyString(), eq(table), eq(context), anyInt());

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: org.mockito.Mockito.spy and org.mockito.Mockito.doNothing() are used fully-qualified on these two lines while the file already statically imports mock, verify, doReturn, etc. — could you add spy and doNothing to the static imports for consistency?

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in d788769: added static imports for spy and doNothing and removed the fully qualified Mockito calls. The updated module passes Checkstyle.

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

I found two gaps in the new async-index coverage: the catch-up tests mask stale progress propagation, and the FILES initialization path leaves its metadata writer unclosed. Details are inline.

@Override
public void updateIndexForWriteAction(HoodieInstant instant) {
writeActionsUpdated.incrementAndGet();
currentCaughtupInstant = instant.requestedTime();

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.

P1: This test double advances currentCaughtupInstant itself, but neither production catch-up implementation does so. The executor test also supplies empty timelines and mocks IndexingCatchupTask, so it proves only that run() was called. In production, RunIndexActionExecutor passes the initial instant as a String and later builds HoodieIndexCommitMetadata from its unchanged field; after processing completed instant 002, it still reports 001. Please exercise a production catch-up task with a real completed instant, assert the returned indexUptoInstant, and propagate the last successfully processed instant back to the executor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in d788769. AbstractIndexingCatchupTask now advances progress after every successfully processed completed action, and RunIndexActionExecutor reads that progress after Future.get() before building HoodieIndexCommitMetadata. The executor regression now runs the production write-stat catch-up task against a real completed 002 commit and asserts indexUptoInstant is 002. The full PR-focused suite passes all 46 tests.

1, MetadataPartitionType.FILES.getPartitionPath(), "001", Collections.emptyMap());
when(activeTimeline.readIndexPlan(requested)).thenReturn(new HoodieIndexPlan(1, Collections.singletonList(info)));
HoodieTableMetadataWriter writer = mock(HoodieTableMetadataWriter.class);
when(table.getIndexingMetadataWriter("002")).thenReturn(Option.of(writer));

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.

P2: This mock represents an AutoCloseable metadata writer, but the FILES initialization branch obtains the writer and discards it without closing it. Unlike the non-FILES branch, there is no try-with-resources; the real writer's close() releases its metadata reader, write client, and view. A focused verify(writer).close() assertion fails. Please close the writer in the production FILES branch and assert closure on both successful initialization and timeline-completion failure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in d788769. The FILES initialization branch now owns the metadata writer with try-with-resources, and the tests verify close() after both successful initialization and a saveAsComplete timeline failure. The full PR-focused suite passes all 46 tests.

@github-actions github-actions Bot added the size:XL PR with lines of changes > 1000 label Aug 7, 2026
@hudi-bot

hudi-bot commented Aug 7, 2026

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

@danny0405 danny0405 changed the title test: improve upgrade and async index coverage fix: harden async indexing and improve coverage Aug 7, 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 working on this! The PR propagates the last caught-up instant from the async-index catch-up task back to RunIndexActionExecutor (so index commit metadata no longer retains the stale base instant), closes the metadata writer used during FILES-partition initialization, and adds substantial coverage for legacy upgrade/downgrade handlers, upgrade orchestration, async-index execution/catch-up, and bucket-index utilities. I traced the propagation path (the final indexUptoInstant cannot regress below the base instant; advancing past no-heartbeat instants is semantically sound; the value is safely published via Future.get()) and confirmed both concrete catch-up subclasses inherit the new interface method without overriding run(). 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.
. One minor readability suggestion on the empty try-with-resources block; the rest of the changes (mostly new test coverage) look clean.

cc @yihua

// save index commit metadata and update table config
// instantiation of metadata writer will automatically instantiate the partitions.
table.getIndexingMetadataWriter(instantTime)
try (HoodieTableMetadataWriter metadataWriter = table.getIndexingMetadataWriter(instantTime)

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: an empty try-with-resources body that relies on the writer's construction side-effect reads as unusual. Could you make the intent more obvious, e.g. assign to a clearly-named local (initializingWriter) or keep the explanatory comment but consider whether an explicit .get()/no-op would read better?

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

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

Labels

size:XL PR with lines of changes > 1000

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants