Skip to content

[opt](mtmv) speed up registerMTMV during FE restart via reverse index - #68038

Open
xy720 wants to merge 1 commit into
apache:masterfrom
xy720:opt-mv-restart
Open

xy720 wants to merge 1 commit into
apache:masterfrom
xy720:opt-mv-restart

Conversation

@xy720

@xy720 xy720 commented Sep 15, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

We found that when the number of mtmv in the cluster reaches a large number (e.g. 80,000+) , the restart of fe becomes very slow.

The jstack of a fe that is currently being restarting is captured as follows:

Clipboard_Screenshot_1789501873

And there are amount of FE logs like this:

2026-09-14 21:27:12,249 INFO (leaderCheckpointer|33718) [MTMVRelationManager.refreshMTMVCache():174] refreshMTMVCache,relation: org.apache.doris.mtmv.MTMVRelation@200613dd, mtmvInfo: BaseTableInfo{tableName='xxx', dbName='xxx', ctlName='internal'}
2026-09-14 21:27:12,268 INFO (leaderCheckpointer|33718) [MTMVService.unregisterMTMV():88] deregisterMTMV: xxx
2026-09-14 21:27:12,286 INFO (leaderCheckpointer|33718) [MTMVService.registerMTMV():80] registerMTMV: xxx
2026-09-14 21:27:12,286 INFO (leaderCheckpointer|33718) [MTMVRelationManager.refreshMTMVCache():174] refreshMTMVCache,relation: org.apache.doris.mtmv.MTMVRelation@200613dd, mtmvInfo: BaseTableInfo{tableName='xxx', dbName='xxx', ctlName='internal'}
2026-09-14 21:27:12,304 INFO (leaderCheckpointer|33718) [MTMVService.refreshComplete():144] refreshComplete: xxx

or

2026-09-14 21:11:04,926 INFO (leaderCheckpointer|33718) [Env.loadDb():2407] start loading db from image
2026-09-14 21:11:04,967 INFO (leaderCheckpointer|33718) [MTMVService.registerMTMV():80] registerMTMV: xxx
2026-09-14 21:11:04,967 INFO (leaderCheckpointer|33718) [MTMVRelationManager.refreshMTMVCache():174] refreshMTMVCache,relation: org.apache.doris.mtmv.MTMVRelation@510324a6, mtmvInfo: BaseTableInfo{tableName='xxx', dbName='xxx', ctlName='internal'}
2026-09-14 21:11:04,984 INFO (leaderCheckpointer|33718) [MTMVService.registerMTMV():80] registerMTMV: xxx
2026-09-14 21:11:04,984 INFO (leaderCheckpointer|33718) [MTMVRelationManager.refreshMTMVCache():174] refreshMTMVCache,relation: org.apache.doris.mtmv.MTMVRelation@45a603c1, mtmvInfo: BaseTableInfo{tableName='xxx', dbName='xxx', ctlName='internal'}

Each invocation of the registerMTMV() function takes about 20ms (about 5 base tables per mtmv, most spent in removeMTMV()), the Env.loadDb() and ENV.postProcessAfterMetadataReplayed() methods of the checkpoint thread invoke the registerMTMV() function once for each mv,and the transferToMaster() will also invoke the registerMTMV() function once for each mv.

Therefore, the total time of transfering to master + restarting fe with 80000+ mtmv is 4800+s(about 1.5h), this will have a serious impact on the checkpoint/transfer-master/restart duration of fe.

This commit:
1、Skip doing registerMTMV() in MTMV compatible when there is nothing was actually migrated.
2、Add reverse index reduce the time complex of registerMTMV()/unregisterMTMV() from O(N^2) to O(k) in the mtmv's own base count

Problem Summary:

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@xy720

xy720 commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

run buildall

@xy720

xy720 commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 51.76% (44/85) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 78.82% (67/85) 🎉
Increment coverage report
Complete coverage report

@xy720

xy720 commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

/review

@github-actions github-actions Bot 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.

I found two P2 regressions and one P3 reverse-index retention issue. The O(k) direction is sound, but the current implementation does not yet preserve complete cleanup across legacy migration and follower replay.

Critical checkpoints

  • Goal and scope: The patch is focused and reduces ordinary unregister/stale-prune work from global forward-map scans to work proportional to the MTMV's prior dependencies. The modern no-change compatibility fast path is correctly signaled.
  • Concurrency: Ordinary refresh completion and unregister share the MTMV reentrant write lock. Follower compatibility registration is the exception: post-processing overlaps the live replayer, and the unlocked re-register can publish concurrently with replay, leaving a forward edge outside the surviving reverse snapshot.
  • Lifecycle and compatibility: Image registration precedes ID-to-name compatibility. Because BaseTableInfo.compatible mutates fields used by hashCode, targeted lookup cannot reach the legacy forward-map bucket, so the new cleanup retains dependency descriptors that the parent full scan removed. No wire-format or FE-BE compatibility change was introduced.
  • Parallel paths and conditions: All three forward/reverse categories were traced. Empty category transitions prune forward membership but retain the prior reverse snapshot; null relations and stable nonempty replacements otherwise behave consistently.
  • Persistence and data correctness: Reverse indexes are in-memory only. Image load, create replay, ADD_TASK replay, checkpoint isolation, and promotion after replayer shutdown were traced; no separate journal, transaction, visible-version, or data-write issue was found.
  • Tests: Static review only; the runner contract forbids local builds/tests. The changed tests cover stable-key replacement/idempotence and modern false compatibility, but the named unregister test actually exercises empty stale-pruning. Legacy true conversion, public reverse-backed unregister with production equality, and the follower replay interleaving remain uncovered. GitHub currently reports COMPILE, CheckStyle, and FE UT passing; the Doris Performance status is failing with only an opaque TeamCity result.
  • Configuration, protocol, observability: No new configuration, FE-BE variable, storage format, metric, or additional observability obligation applies.

User focus

No additional user-provided focus was specified; the complete PR was reviewed.

removeMTMV(mtmvInfo);
return;
}
Set<BaseTableInfo> staleTables = mtmvToBaseTables.get(mtmvInfo);

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.

[P2] Serialize compatibility registration with replay refreshes

On a follower, transferToNonMaster leaves the replayer running while postProcessAfterMetadataReplayed calls MTMV.compatible. That method releases the MV write lock after unregister and performs registerMTMV unlocked. If compatibility captures relation R0 while an ADD_TASK replay publishes R1, both refreshes can read the same prior reverse snapshot, add different forward edges, and leave only the last reverse value; the new reverse-only unregister then cannot remove the uncovered edge. Please hold the MV write lock across the compatibility unregister/register replacement or make the per-MV forward/reverse replacement atomic, and cover this interleaving with latches.

@@ -240,7 +249,9 @@ private void addMTMVTables(Set<BaseTableInfo> baseTables, BaseTableInfo mtmvInfo
if (CollectionUtils.isEmpty(baseTables)) {

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.

[P3] Clear the reverse entry when this category becomes empty

When an MV refresh changes this category from nonempty to empty, this return leaves mtmvToBaseTables pointing at the old bases. removeMTMVFromStaleRelations removes the forward memberships, but nothing removes the reverse snapshot, so each affected long-lived MV retains its last nonempty dependency set until another nonempty refresh or unregister. The view and one-level adders have the same branch. Please remove or replace the reverse entry on the empty transition and cover the actual reverse-backed unregister path; the new empty-relation test only exercises stale pruning.

for (Set<BaseTableInfo> sets : tableMTMVsOneLevelAndFromView.values()) {
sets.remove(mtmvInfo);
for (BaseTableInfo base : bases) {
Set<BaseTableInfo> mtmvs = forwardMap.get(base);

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.

[P2] Do not clean legacy entries through their mutated hash

Image loading registers legacy ID-only BaseTableInfo objects before compatible() fills ctlName/dbName/tableName, and those same mutable fields define hashCode(). The reverse snapshot keeps the same objects, so forwardMap.get(base) probes their new hash and cannot reach the old ConcurrentHashMap bucket; removing the reverse entry then makes those old value sets permanently uncleanable, while re-registration adds a second name-hashed entry. The previous full value-set scan at least removed the MTMV values. Please rebuild before mutation, use stable immutable keys, or retain a compatibility-only cleanup fallback, and add an ID-only upgrade test.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants