Skip to content

HDDS-16463. Sync Recon derived DB before cursor advance - #11262

Open
HesandaLiyanage wants to merge 10 commits into
apache:masterfrom
HesandaLiyanage:HDDS-16463
Open

HesandaLiyanage wants to merge 10 commits into
apache:masterfrom
HesandaLiyanage:HDDS-16463

Conversation

@HesandaLiyanage

@HesandaLiyanage HesandaLiyanage commented Sep 18, 2026

Copy link
Copy Markdown

What changes were proposed in this pull request?

During Recon delta sync, ReconTaskControllerImpl.processTasks() executes derived tasks (e.g. ContainerKeyMapperTask, NSSummaryTask), which write derived data to Recon's local RocksDB (recon.db). By default, RocksDB writes are not synced (sync = false) and remain in the OS page cache. Immediately following task success, recordRunCompletion() commits the task cursor (lastUpdatedSeqNumber and lastTaskRunStatus = 0) to Derby, which issues an fsync-durable commit.

If a host power loss occurs in that window, the un-synced RocksDB WAL tail at sequence $N$ is lost while the durable Derby cursor survives at sequence $N$. On restart, OzoneManagerServiceProviderImpl.start() compares the task cursor with the delta cursor; because both are at sequence $N$, it skips reprocessing. The next delta sync starts from sequence $N$, permanently dropping the applied update from Recon's derived tables.

This pull request introduces a write-time durability barrier:

  1. Adds syncReconDbLog() in ReconTaskControllerImpl to flush and sync the derived RocksDB WAL via DBStore.flushLog(true) before advancing and committing task status cursors.
  2. Coalesces the barrier to execute once per batch after all task futures complete, avoiding redundant fsync calls and thread contention on the database across parallel tasks.
  3. Fixes concurrent modification risks by accumulating task execution failures in a thread-safe synchronized collection before sequentially transferring to failedTasks, and ensures processOMUpdateBatch uses synchronized collections.
  4. If syncReconDbLog() fails (e.g., RocksDatabaseException), leaves the cursors unadvanced, marks lastTaskRunStatus as -1, and registers the tasks in failedTasks for immediate retry/reprocessing.
  5. In TestReconTaskControllerImpl:
    • testDerivedDbSyncedBeforeCursorAdvanceOnSuccess: asserts that flushLog(true) is called strictly before Derby cursors advance to sequence 100L, coalescing multiple task writes into a single sync per batch.
    • testDerivedDbSyncFailureLeavesCursorUnadvancedAndRetries: asserts that if flushLog(true) throws RocksDatabaseException, cursors remain unadvanced at 0L, run status is marked -1, and the batch retry path is exercised.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-16463

How was this patch tested?

  1. Unit tests:
    mvn -pl :ozone-recon -am test -Dtest=TestReconTaskControllerImpl -DskipShade -DskipRecon -DskipDocs
    All 23 tests passed (0 failures, 0 errors, 1 skipped).
  2. Checkstyle:
    ./hadoop-ozone/dev-support/checks/checkstyle.sh
    0 violations across all 58 modules.

During delta sync, ReconTaskControllerImpl processes tasks that write
derived data to RocksDB (with sync=false by default) and then commits
the task cursor to Derby (which is fsync-durable). A power loss in this
window can lose the un-synced RocksDB WAL tail while Derby records
completion at sequence N. On startup, reconciliation sees equal sequence
numbers and skips reprocessing, causing permanent loss of the update.

This patch introduces a write-time durability barrier by syncing the
Recon DB WAL (DBStore.flushLog(true)) before advancing the task status
cursor in Derby. If the sync fails, the cursor remains unadvanced and
the task is marked for retry/reprocessing.
Copilot AI lite review requested due to automatic review settings September 18, 2026 19:29

Copilot AI 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.

🟡 Changes recommended

The retry list has a concurrency race, and tests do not verify sync ordering or failure handling.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a RocksDB WAL sync barrier so Recon derived data is durable before task cursors advance.

Changes:

  • Syncs recon.db WAL before recording successful task completion.
  • Retries tasks when WAL syncing fails.
  • Adds durability-related unit coverage.
File summaries
File Description
ReconTaskControllerImpl.java Implements WAL synchronization and failure handling.
TestReconTaskControllerImpl.java Adds sync-barrier test coverage.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 4
  • Review effort level: Lite

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

Coalesce the RocksDB WAL flushLog(true) durability barrier to execute
once per batch after all tasks complete their execution, instead of
syncing per successful task. This avoids multiple redundant fsync calls
and thread contention on the same database while preserving the guarantee
that derived data is durable before task cursors are committed to Derby.
Ensure that failed task tracking during parallel execution is thread-safe.
Collect task execution failures in a thread-safe synchronized list during
asynchronous execution, sequentially transferring them to failedTasks after
futures complete. Additionally, initialize failedTasks and retryFailedTasks
using synchronizedList in processOMUpdateBatch to prevent race conditions on
concurrent updates.
Add testDerivedDbSyncFailureLeavesCursorUnadvancedAndRetries to verify
the durability barrier failure behavior when DBStore.flushLog(true) throws
RocksDatabaseException. Asserts that the task cursor remains unadvanced,
the task status is marked as failed (-1), and the task is retried through
the batch retry path.
… commit

Use Mockito doAnswer on reconDbStore.flushLog(true) to verify that at
the exact moment the WAL sync is invoked, the task cursors in Derby have
not yet advanced to the batch sequence number. Asserts causal ordering
of the durability barrier rather than just eventual invocation.

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

Thanks for working on this! @HesandaLiyanage 👍 I left three inline notes. Also, could we trim the rationale to one place (the syncReconDbLog javadoc)? It appears three times now, and the test javadoc is the only one in that file.

- Signal task reinitialization on sync failure instead of retrying process() to avoid double counting
- Record delta processing failure in taskMetrics on sync failure
- Return false when dbStore is null in syncReconDbLog()
- Trim rationale comments to canonical syncReconDbLog() javadoc
- Update unit tests to verify reinitialization signaling without process() retry and null dbStore handling
@HesandaLiyanage

Copy link
Copy Markdown
Author

Hi @chihsuan , thank you so much for the review!

I have addressed all the feedback in the latest commit:

  • Signaled task reinitialization (tasksFailed) directly on sync failure instead of retrying process(), preventing double-counting in incremental counting tasks.
  • Recorded failures in taskMetrics when sync fails.
  • Handled dbStore == null by returning false so cursors never advance without durability guarantees.
  • Trimmed the rationale comments down to the canonical syncReconDbLog() javadoc.
  • Updated the unit tests to verify that process() is not retried on sync failure, and added a test for the null dbStore path.

Please let me know if there's anything else!

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

Thanks @HesandaLiyanage! Left two small nits inline, but overall looks good.

Both failedTasks and retryFailedTasks are only accessed on the calling thread after joining parallel task execution, so synchronization is unnecessary.
Fold the separate success and failure loops into a single loop keyed on the syncReconDbLog result, reducing duplicated iteration and updater boilerplate.
@HesandaLiyanage

Copy link
Copy Markdown
Author

@chihsuan Fixed the nit comments

@HesandaLiyanage

Copy link
Copy Markdown
Author

@ivandika3 @chihsuan ping

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants