Skip to content

fix(lock): unify default FS lock path and make CLI lock config take effect - #19794

Open
Joy-2000 wants to merge 1 commit into
apache:masterfrom
Joy-2000:fix-default-fs-lock-config
Open

fix(lock): unify default FS lock path and make CLI lock config take effect#19794
Joy-2000 wants to merge 1 commit into
apache:masterfrom
Joy-2000:fix-default-fs-lock-config

Conversation

@Joy-2000

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

This PR fixes two related defects in the metadata-table auto-lock path that can cause silent lock failures for table-service writers.

1. Default lock-path inconsistency (correctness / data-safety)

FileSystemBasedLockProvider.getLockConfig(tablePath) derived the default lock path from the .hoodie/.aux folder, while the provider's own constructor fell back to the .hoodie meta folder when no path was configured. The two defaults could resolve to different directories for the same table, so two writers relying on different code paths would take locks on different files and never actually exclude each other — defeating the lock across engines/tasks.

2. CLI lock config never took effect (correctness)

RunClusteringProcedure / RunCompactionProcedure appended the auto-derived lock options to confs after the write client had already been built from confs, so the lock configuration was silently dropped and these table services could update the metadata table without the intended DFS lock.

Summary and Changelog

Table-service writers launched through the SQL procedures (compaction, clustering, and the other write procedures) now reliably acquire a filesystem-based lock on a single, consistent path under the table's metadata folder when the table has a metadata table and no lock provider is otherwise configured.

Changes:

  • FileSystemBasedLockProvider
    • Introduce/reuse a single defaultLockPath() helper and change the default lock path from the .hoodie/.aux folder to the table metadata path .hoodie (METAFOLDER_NAME).
    • The constructor's fallback now reuses defaultLockPath() so the constructor fallback and getLockConfig() default can never diverge again.
  • HoodieCLIUtils.createHoodieWriteClient
    • Move the metadata-table auto-lock injection here, applied after the final parameters are merged and before the client is built, so it actually takes effect.
    • Guard on the fully-merged parameters (!finalParameters.contains(LOCK_PROVIDER_CLASS_NAME)) so any explicitly-configured lock provider — at conf, table-config, or session level — is respected.
    • Refactor getLockOptions(tablePath, scheme, params) accordingly.
    • This extends lock coverage from just clustering/compaction to all write procedures that go through createHoodieWriteClient.
  • RunClusteringProcedure / RunCompactionProcedure: remove the now-dead post-build lock-injection blocks and unused imports.
  • Tests: extend TestFileSystemBasedLockProvider (verify the explicit getLockConfig path and the constructor BASE_PATH fallback resolve to the same .hoodie/lock file) and add cases to TestHoodieCLIUtils for getLockOptions (supported/null scheme → FS lock config under .hoodie, unsupported scheme → empty, custom hoodie.fs.atomic_creation.support → FS lock config).

Impact

  • Behavior change: table-service writes via the SQL procedures now auto-configure a filesystem lock on .hoodie (previously the config was dropped, or could point at .aux). Users who already set a lock provider at any layer are unaffected — their provider is preserved.
  • No public API signature changes; getLockOptions is an internal utility whose signature changed.
  • No performance impact.

Risk Level

low

  • The auto-injection only triggers when the table has a metadata table and no lock provider is configured at any layer, so existing explicit configurations are untouched.
  • HoodieWriteConfig.AUTO_ADJUST_LOCK_CONFIGS defaults to false, so the injected FS lock provider is not downgraded to in-process.
  • Verified by TestFileSystemBasedLockProvider (17/17 pass) and TestHoodieCLIUtils (13/13 pass), including the new path-consistency and getLockOptions cases.

Documentation Update

Contributor's checklist

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

Two related defects in the metadata-table auto-lock path:

1. Lock-path inconsistency: FileSystemBasedLockProvider.getLockConfig used
   the .aux folder as the default lock path, while the constructor fell back
   to the .hoodie meta folder. The two defaults could diverge, silently
   placing the lock file at different paths and breaking mutual exclusion
   across engines/tasks. Unify both on the table metadata path (.hoodie) by
   reusing a single defaultLockPath() helper.

2. Lock config never applied: RunClustering/RunCompaction appended lock
   options to `confs` AFTER the write client was already built, so they had
   no effect. Move the auto-lock injection down into
   HoodieCLIUtils.createHoodieWriteClient, applied after the final parameters
   are merged and before the client is built, guarded on the fully-merged
   params so any explicitly-configured lock provider is respected. This also
   extends the coverage from just clustering/compaction to all write
   procedures that go through createHoodieWriteClient.

Remove the now-dead lock-injection blocks and unused imports from the two
procedures. Add/extend tests covering the unified path and getLockOptions.
@Joy-2000
Joy-2000 force-pushed the fix-default-fs-lock-config branch from c600fdd to 4ddd55c Compare August 30, 2026 06:58
@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Aug 30, 2026
@codecov-commenter

codecov-commenter commented Aug 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 78.23%. Comparing base (816bd96) to head (4ddd55c).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
...rc/main/scala/org/apache/hudi/HoodieCLIUtils.scala 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19794      +/-   ##
============================================
+ Coverage     78.14%   78.23%   +0.08%     
- Complexity    33692    33787      +95     
============================================
  Files          2540     2541       +1     
  Lines        141413   141570     +157     
  Branches      17123    17180      +57     
============================================
+ Hits         110513   110760     +247     
+ Misses        23200    23091     -109     
- Partials       7700     7719      +19     
Components Coverage Δ
hudi-common 83.59% <ø> (+0.01%) ⬆️
hudi-client 83.14% <100.00%> (+<0.01%) ⬆️
hudi-flink 85.65% <ø> (-0.01%) ⬇️
hudi-spark-datasource 73.19% <88.88%> (+0.49%) ⬆️
hudi-utilities 74.54% <ø> (+0.02%) ⬆️
hudi-cli 15.06% <ø> (ø)
hudi-hadoop 70.13% <ø> (+0.02%) ⬆️
hudi-sync 75.56% <ø> (+0.02%) ⬆️
hudi-io 79.90% <ø> (+0.14%) ⬆️
hudi-timeline-service 83.44% <ø> (-0.10%) ⬇️
hudi-cloud 65.81% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 51.43% <18.18%> (-0.03%) ⬇️
flink-integration-tests 48.86% <50.00%> (-0.01%) ⬇️
hadoop-mr-java-client 44.03% <100.00%> (+0.01%) ⬆️
integration-tests 13.52% <0.00%> (+<0.01%) ⬆️
spark-client-hadoop-common 50.40% <0.00%> (+0.02%) ⬆️
spark-java-tests 52.18% <63.63%> (+0.02%) ⬆️
spark-scala-tests 46.85% <90.90%> (+0.08%) ⬆️
utilities 36.58% <9.09%> (+0.28%) ⬆️

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

Files with missing lines Coverage Δ
.../transaction/lock/FileSystemBasedLockProvider.java 90.72% <100.00%> (ø)
...di/command/procedures/RunClusteringProcedure.scala 91.36% <ø> (+0.52%) ⬆️
...di/command/procedures/RunCompactionProcedure.scala 82.71% <ø> (+1.76%) ⬆️
...rc/main/scala/org/apache/hudi/HoodieCLIUtils.scala 80.26% <88.88%> (+3.12%) ⬆️

... and 43 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
*/
private static String defaultLockPath(String tablePath) {
return tablePath + StoragePath.SEPARATOR + AUXILIARYFOLDER_NAME;
return tablePath + StoragePath.SEPARATOR + HoodieTableMetaClient.METAFOLDER_NAME;

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.

let's use AUXILIARYFOLDER_NAME instead.

// clustering, clean, TTL, restore, rollback, savepoint, ...) can update the metadata table, so
// they must be mutually exclusive with concurrent writers on the shared lock path. This must be
// applied before building the client so the lock config actually takes effect.
val finalParameters =

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.

do we need lock for all the procedures, if not, keeping it in separate procedure looks fine.

@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 fixes two real defects: the divergent default FS-lock path between getLockConfig() (.aux) and the constructor fallback (.hoodie), and the CLI procedures appending lock options to confs after the write client was already built (so they were silently dropped). Unifying the path and moving the auto-lock config into createHoodieWriteClient before client construction both look correct. One cross-engine/upgrade consideration is worth double-checking in the inline comment. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. One verbose inline comment in HoodieCLIUtils.scala that could be trimmed to just its non-obvious constraint; everything else looks clean.

*/
private static String defaultLockPath(String tablePath) {
return tablePath + StoragePath.SEPARATOR + AUXILIARYFOLDER_NAME;
return tablePath + StoragePath.SEPARATOR + HoodieTableMetaClient.METAFOLDER_NAME;

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.

🤖 Changing defaultLockPath from .aux to .hoodie also moves the lock location for the other getLockConfig() callers — hudi-flink (StreamerUtil.getLockConfig) and hudi-utilities (UtilHelpers), which previously resolved to .aux. During a rolling upgrade, an old-version writer holding .aux/lock and a new-version writer holding .hoodie/lock would no longer mutually exclude and could update the MDT concurrently. Have you considered the mixed-version window here, and is a migration note warranted? @nsivabalan could you weigh in on whether this cross-engine lock-path move needs a compatibility step?

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

(catalogProps ++
metaClient.getTableConfig.getProps.asScala.toMap ++
filterHoodieConfigs(sparkSession.sqlContext.getAllConfs) ++
conf).toMap

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: the 5-line comment mostly restates what the surrounding code already shows — could you trim it to just the non-obvious constraint, something like // Must be applied before building the client; see getLockOptions for why FS atomic-creation support is required.?

⚠️ 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:M PR with lines of changes in (100, 300]

5 participants