Skip to content

[feature](ivm) Track the refresh baseline per MV partition, not by a rebuild barrier - #68390

Open
yujun777 wants to merge 11 commits into
apache:masterfrom
yujun777:ivm-pr3-upstream-master
Open

yujun777 wants to merge 11 commits into
apache:masterfrom
yujun777:ivm-pr3-upstream-master

Conversation

@yujun777

@yujun777 yujun777 commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: N/A

Related PR: #68170, #68180, #68193

Trace issue: #65418

Problem Summary:

An IVM MV keeps rows that a metadata-only base-table change (DROP / TRUNCATE / REPLACE / RECOVER PARTITION) has made unusable, because such a change emits no row binlog and nothing incremental can remove those rows. Today the invalidation is recorded at MV granularity: IvmInfo.completeBaselineRebuildRequired / pendingBaselineRebuildPartitions plus a schemaChangeVersion guard. That granularity is coarse -- one dirty partition drags the whole MV to a COMPLETE refresh, a task result produced before the invalidation is discarded, and a strict REFRESH ... INCREMENTAL is rejected until a COMPLETE refresh has run, even when the change touched nothing the MV reads.

This PR replaces the barrier with a per-MV-partition requirement:

  • MTMV.partitionStates maps each MV partition to {refreshEpoch, latestEpoch} (persisted as pst, journaled through ALTER_PARTITION_STATES). A partition is dirty iff latestEpoch > refreshEpoch && refreshEpoch != 0 -- refreshEpoch == 0 means it was never refreshed, so it holds no rows and its first refresh reads the current base tables anyway.
  • An invalidation that can be placed on the MV partitions reading the changed base partitions raises only their requirement and drops their refresh snapshot, in one journal record. Every other partition keeps catching up incrementally, and a task result that no invalidation reached is no longer discarded.
  • Alignment runs after partition sync and before any base table is read, so "the partition exists" and "the entry exists" are the same thing and a mark always has an entry to land on.
  • A refresh samples the requirement per batch before reading base tables, and writes back only refreshEpoch once that batch's data is committed, so an invalidation arriving mid-refresh is not swallowed.
  • The refresh routes on the criterion: dirty partitions are rebuilt by the partition executor (under a strict INCREMENTAL request as well), the rest are caught up incrementally or skipped. When every partition needs a rebuild, or the MV is in SCHEMA_CHANGE, the refresh runs as COMPLETE. A refresh that rebuilt partitions the request did not ask for reports how many in the new IvmRebuiltPartitions column of the mv task TVF.
  • A whole-MV invalidation -- the fallback that cannot place a change on any partition, and a property change that widens what the MV maintains -- now goes through the MV state instead of the barrier flag, and the barrier fields, the refresh-time guard and the pending-rebuild rejection are gone. A rename of an IVM MV's base table no longer moves it into that state.
  • An MV re-derives its schema from its query whenever a base table changes, and compares the result with its physical schema. That comparison matched the two column lists by position, which the two passes do not agree on for a chained IVM MV whose base tables carry row-id columns of their own -- the create pass lays the physical schema out from the identity key slots, the re-analysis runs the same layout from the stored keys, and the base tables' row-id columns end up in a different block. It now matches the columns by name, which is what the check is about: a column that disappeared or changed type. Without that, a chained IVM MV could not be refreshed once its base MVs had been completely refreshed, which this PR's invalidation routing newly exposes.

Behaviour changed: Yes

  • A strict REFRESH ... INCREMENTAL that meets an invalidated baseline no longer fails with "IVM baseline rebuild is pending"; it rebuilds (the whole MV for a schema-level invalidation, the invalidated partitions otherwise) and reports the count in IvmRebuiltPartitions.
  • An MV in SCHEMA_CHANGE refreshes as COMPLETE without first attempting the incremental rewrite, so IvmFallbackReason stays unset where it previously reported the barrier's label.
  • A base-table rename no longer moves an IVM MV out of NORMAL.

Release note

None

Check List (For Author)

  • Test: Unit Test / Regression test
    • FE unit tests: the classes this branch touches pass -- IvmBaselineRebuildTest, MTMVTaskTest, MTMVTest, MTMVPlanUtilTest, CreateMTMVCommandTest, MTMVRelationManagerTest, AlterMTMVTest, IvmInfoTest, MTMVRefreshSnapshotTest, MTMVPartitionUtilTest, MetaLockUtilsTest -- 147 tests re-run for the last commit, all pass. Every new case was checked to fail when the change it covers is reverted, including the schema comparison above.
    • Regression: mtmv_p0/ivm -- all 97 suites pass with the stored expectations compared, not regenerated. That includes test_ivm_use_full_keys_6, which is the case that reproduces the chained-MV schema comparison, and test_ivm_partition_epoch_rebuild (new), test_ivm_baseline_marker_scope, test_ivm_partition_baseline_rebuild, test_ivm_partition_baseline_rebuild_dup_keys, test_ivm_partition_sync_limit, test_ivm_chained_mtmv_1, test_ivm_chained_mtmv_2, test_ivm_partition_drop_live_delta, test_ivm_partition_window_remove, test_ivm_drop_referenced_column_baseline_rebuild and test_ivm_strict_incremental_rebuilds_invalidated_partitions (renamed from test_ivm_strict_failure_partition_atomicity), whose expectations this PR updates.
  • Behavior changed: Yes (see above)
  • Does this need documentation: No (the user-facing notes and the design doc update are part of a follow-up)

…te-back

MTMV.partitionStates landed without a producer: the per-partition criterion it exists for is being added
now, starting with the state machine that keeps it true. Alignment makes an entry and its MV partition the
same thing, a refresh captures the requirement in force before it reads a base table and writes it back
afterwards, and the write-back carries only the refreshed side of the state so a requirement raised while
the task ran cannot be swallowed.

Nothing reads the criterion yet, so this changes no refresh behaviour.

Key changes:
- MTMV gains alignPartitionStates (create {0, 1} for a partition without an entry, drop entries whose partition is gone, journal the difference -- the entry has to be durable before the rows it describes), getLatestEpochs (the capture) and the write-back, applied from addTaskResult under the MV write lock
- MTMVPartitionState gains initial(), isDirty() (latestEpoch > refreshEpoch && refreshEpoch != 0) and isNeverRefreshed()
- MTMVTask captures each batch's partitions before that batch reads a base table and records them only once the batch has committed, so a batch that did not write cannot claim data
- Alignment runs after partition sync, and after the sync of the MV_PARTITION_NOT_FOUND retry: before anything reads a base table, which is what makes an invalidation always have an entry to land on
- Create partitionStates and ivmInfo with the MV so that no reader needs a null case, and keep the one load case that remains: an image carrying either member as null, which gsonPostProcess() fills

Unit Test:
- MTMVTest covers the criterion, alignment (never rewrites an existing entry, no journal when nothing changed, no-op for a non-IVM MV), the capture, the write-back recording refreshEpoch alone, a replay applying the payload's states rather than the task's captured epochs, and both load paths of the state map
- testAlignPartitionStatesCreatesAndDropsEntries, testTaskResultRecordsTheCapturedEpochWithoutTouchingTheRequirement and testPartitionStatesImageThatCarriesTheFieldAsNullLoadsAsAnEmptyMap fail when the corresponding change is reverted
The criterion the previous commit keeps now decides what a refresh does with each partition: one that
holds rows read before a base-table change is rebuilt, one that is merely behind is caught up
incrementally, and one that is current is left alone. Nothing invalidates a partition yet, so the dirty
set is empty in a running system and this changes no refresh behaviour -- the routing is the place the
invalidation will land.

Key changes:
- MTMV.getDirtyPartitions gives the partitions that have to be rebuilt, intersected with the partitions the MV has, and keeps the allocation and the name snapshot outside the read lock
- The incremental attempt rebuilds them first, through the partition executor, and leaves them out of its own scope: the delta path can only append, so treating one as current would record it in the epoch while its rows are exactly what the rebuild replaces
- The rebuild's snapshots and completed partitions are merged back after the incremental attempt reset the accumulators, so the partitions it rebuilt are not refreshed again on every following round
- Escalate to COMPLETE when every partition either needs a rebuild or was never filled, and at least one needs a rebuild: COMPLETE then does nothing the routing would not, in one read of the MV
- Decide the attempts after partition sync and alignment, which is what makes the partition set the escalation reads final

Unit Test:
- MTMVTaskTest covers the escalation, the chain it keeps when a partition is already filled, the absence of an escalation without an invalidated partition, and that the incremental attempt leaves a rebuilt partition out of its scope
- MTMVTest.testDirtyPartitionsAreTheRefreshedOnesBehindTheirRequirement covers the selection, including a partition the MV no longer has
- Each of the three assertions fails when the corresponding change is reverted
…ild barrier

An invalidation that can be placed on the partitions reading the changed base partition now raises their
requirement instead of recording a barrier the next refresh has to consume: those partitions are rebuilt,
every other partition keeps catching up incrementally, and no task result is discarded for a change that
never touched it. The partitions it marks also lose their refresh snapshot, which is what keeps transparent
rewrite away from rows the rebuild has to replace.

An invalidation that cannot be placed still takes the whole-MV route, and the barrier is still what carries
it there.

Key changes:
- MTMV.invalidateIvmBaseline marks the selected partitions: latestEpoch raised under the MV write lock, plus the removal of their snapshots, in one journal record -- AlterMTMV gains removedSnapshotPartitions, and the ALTER_PARTITION_STATES replay applies both halves in one lock acquisition so no reader sees the new requirement while the snapshot is still there
- MTMVRefreshSnapshot.removeSnapshots drops the named partitions and keeps the rest of the map, which the write-back side cannot express
- A task result writes back the snapshot only for the partitions that are clean after its epochs were applied: an invalidation that reached a partition while the task ran must not have its removal undone
- The partition-level invalidation no longer bumps schemaChangeVersion: a partial invalidation does not discard a task result, and the requirement it raises survives the write-back by construction

Unit Test:
- IvmBaselineRebuildTest pins the raised requirement per partition, that no other partition is marked, and the dropped snapshot
- MTMVTest.testTaskResultLeavesTheSnapshotOfADirtyPartitionOut pins the write-back filter; MTMVRefreshSnapshotTest and AlterMTMVTest pin removeSnapshots and its replay
- Each assertion fails when the corresponding change is reverted
… a barrier

A rebuild requirement now has exactly two carriers, both of them already read by the refresh: a partition
that reads the changed base partition carries it as a raised latestEpoch, and a change that cannot be placed
on any partition carries it as the MV's SCHEMA_CHANGE state. The persisted baseline barrier
(IvmInfo.completeBaselineRebuildRequired / pendingBaselineRebuildPartitions) and the handshake that consumed
it (the task's pending-baseline rejection) have nothing left to carry, and the partition-level invalidation
no longer bumps schemaChangeVersion either: a partial invalidation does not discard a task result, and the
requirement it raises survives the write-back by construction.

Key changes:
- IvmInfo loses completeBaselineRebuildRequired and pendingBaselineRebuildPartitions with their accessors; the
  requirement lives on MTMV, as a partition's latestEpoch or as the MV state
- MTMV.invalidateWholeMv is the state-machine route: the fallback that cannot place a change, and a property
  change that invalidates the baseline, both put the MV into SCHEMA_CHANGE, which the refresh reads
- An IVM MV in SCHEMA_CHANGE refreshes as [COMPLETE] whatever the request asked for -- a schema-level
  invalidation is not a set of dirty partitions, it covers the partitions partition sync has not created yet.
  Non-IVM MVs keep the chain they had. A rename of an IVM MV's base table no longer puts it into that state,
  which would have it rebuild everything for nothing after the table is renamed back: the rename's own
  failure is already reported by the refresh, which resolves the base tables from the query first. A column
  change still moves the state, since telling a referenced column from an unreferenced one is the shared
  hook's criterion and is left as it is
- MTMVTask drops the pending-baseline handshake (handlePendingIvmBaselineRebuild,
  validateIvmBaselineBeforePartitionSync) and MTMV drops persistIvmBaselineGuard / releaseIvmBaselineRebuild;
  validateIvmRefreshStart keeps only the schemaChangeVersion check
- The task records how many partitions a refresh rebuilt although the request did not ask for them
  (ivmRebuiltPartitions, the IvmRebuiltPartitions column of the mv task TVF), so a strict INCREMENTAL that had
  to rebuild the whole MV reports it instead of reporting the rows as current

Unit Test:
- MTMVTaskTest pins C2's four boundaries: the IVM escalation, a non-IVM MV keeping its chain, an explicit
  partition list never widened, and a strict INCREMENTAL rebuilt and reported
- IvmBaselineRebuildTest asserts each invalidation on the carrier that now exists -- the raised requirement
  where it can be placed, the MV state where it cannot -- and that a renamed base table leaves an IVM MV's
  requirement and state alone, while a non-IVM MV still gets the state
- Each of the four C2 cases fails when the branch, or one of its two guards, is removed; each rename case
  fails when the exclusion is removed, and the non-IVM one fails when the exclusion stops being IVM-only
…nvalidation changes

An IVM refresh that meets an invalidated baseline no longer refuses to run a strict INCREMENTAL request: the
partitions the base-table change left behind are rebuilt as the refresh's own work, a whole-MV invalidation
rebuilds the MV whole, and a refresh that rebuilt partitions the request did not ask for reports how many in
IvmRebuiltPartitions. The suites that pinned the refusal now pin the outcome, and one new suite pins the
routing end to end.

Key changes:
- test_ivm_partition_epoch_rebuild (new): one MV pins a COMPLETE baseline, an incremental refresh that
  rebuilds nothing and still applies its delta, a truncated base partition rebuilt by a strict INCREMENTAL
  while the other partition catches up incrementally and the rebuild is reported as 1, a rename of the base
  table that leaves both the requirement and the MV state alone, and a second truncation naming its own
  partition
- test_ivm_baseline_marker_scope, test_ivm_partition_baseline_rebuild_dup_keys, test_ivm_chained_mtmv_2: the
  task rows read SUCCESS with the rebuild reported instead of the pending-rebuild failure, and the MV rows
  are unchanged
- test_ivm_strict_failure_partition_atomicity is renamed to
  test_ivm_strict_incremental_rebuilds_invalidated_partitions, with its file, its expectation file and its
  table names: what it witnesses is a strict refresh that rebuilds, not one that fails, and its MV is
  asserted against the base tables' rows
- The queries that read IvmFallbackReason or RefreshMode fold the unset value: an unset column comes back as
  the literal two-character string "\N", which does not survive the .out round trip

Unit Test:
- The targeted suite set (8 suites: the new one, the two chained ones, the two baseline-rebuild ones, the
  marker-scope one, the partition-sync-limit one and the renamed one) passes with the stored expectations
  compared, not regenerated
- Every changed expectation is a task-route line; the MV row expectations are unchanged except where the
  base tables' rows are gone, which is what the rebuild is for
MTMV.snapshotsOfCleanPartitions keeps a task result from writing back the snapshot of a partition an
invalidation reached while the task ran. The javadoc explained that case but not the non-IVM one, where the
partition state map is never maintained: every entry has no state to be dirty in, which is why a non-IVM
MV's write-back is unchanged.

Key changes:
- The javadoc of snapshotsOfCleanPartitions says which MVs the narrowing applies to

Unit Test:
- Comment only, no behaviour change; checkstyle reports no violation on fe-core
@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?

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

1 similar comment
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28042 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit b57d2ce5497c38cb41399743874f608d92a0fad4, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17708	3846	3866	3846
q2	2199	391	317	317
q3	10026	1457	807	807
q4	4688	488	353	353
q5	7543	829	562	562
q6	184	170	141	141
q7	747	786	597	597
q8	9299	1535	1574	1535
q9	5448	4219	4244	4219
q10	6830	1329	1022	1022
q11	437	282	236	236
q12	632	422	297	297
q13	18080	2649	1987	1987
q14	266	265	232	232
q15	q16	729	723	653	653
q17	1690	1157	1103	1103
q18	6417	5618	5523	5523
q19	1310	1161	1072	1072
q20	493	408	261	261
q21	5807	3459	2963	2963
q22	451	383	316	316
Total cold run time: 100984 ms
Total hot run time: 28042 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4719	4693	4347	4347
q2	738	566	537	537
q3	4763	5153	4575	4575
q4	2254	2301	1476	1476
q5	4543	4408	4557	4408
q6	226	175	127	127
q7	1791	1711	1482	1482
q8	2365	2238	2002	2002
q9	7374	7225	7239	7225
q10	3687	3585	3079	3079
q11	508	377	348	348
q12	703	695	505	505
q13	2274	2589	1995	1995
q14	268	264	244	244
q15	q16	664	671	616	616
q17	7294	6720	6677	6677
q18	11857	11050	11699	11050
q19	1105	994	981	981
q20	2216	2181	1924	1924
q21	5012	4093	4257	4093
q22	500	451	396	396
Total cold run time: 64861 ms
Total hot run time: 58087 ms

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Sep 28th, 2026 2:03 AM.
Workflow run: https://github.com/apache/doris/actions/runs/35717680486

The selected account is excluded until 2026-09-28T02:03:00Z. Please trigger /review again; another configured account may be available.

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 153075 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit b57d2ce5497c38cb41399743874f608d92a0fad4, data reload: false

query5	4306	611	476	476
query6	444	210	193	193
query7	4826	548	313	313
query8	321	182	171	171
query9	8826	4044	3980	3980
query10	459	315	264	264
query11	5942	3543	3243	3243
query12	154	94	88	88
query13	1271	588	422	422
query14	6530	4510	4267	4267
query14_1	3966	3982	3948	3948
query15	205	201	190	190
query16	978	466	412	412
query17	930	667	551	551
query18	2482	480	328	328
query19	203	188	146	146
query20	87	83	81	81
query21	219	133	126	126
query22	13034	12944	12759	12759
query23	14059	12921	12406	12406
query23_1	12511	12490	12512	12490
query24	7238	1184	713	713
query24_1	700	749	737	737
query25	568	449	380	380
query26	1300	305	166	166
query27	2627	551	338	338
query28	4547	1980	1969	1969
query29	1616	756	537	537
query30	299	226	193	193
query31	889	768	642	642
query32	146	109	102	102
query33	541	322	252	252
query34	1192	1134	628	628
query35	716	738	647	647
query36	813	804	716	716
query37	161	108	96	96
query38	1843	1765	1708	1708
query39	711	699	664	664
query39_1	638	636	674	636
query40	222	132	106	106
query41	73	72	69	69
query42	98	95	98	95
query43	346	362	310	310
query44	1445	742	725	725
query45	191	179	173	173
query46	1021	1184	790	790
query47	1526	1497	1392	1392
query48	397	418	310	310
query49	574	421	303	303
query50	936	332	253	253
query51	10192	10690	10379	10379
query52	95	91	76	76
query53	241	255	181	181
query54	245	222	182	182
query55	77	76	76	76
query56	235	223	219	219
query57	1453	1428	1315	1315
query58	277	261	247	247
query59	1991	2100	1849	1849
query60	273	237	224	224
query61	149	138	146	138
query62	400	318	267	267
query63	222	182	177	177
query64	2774	1005	805	805
query65	3503	3414	3440	3414
query66	1792	416	312	312
query67	19831	20026	20066	20026
query68	3292	1445	971	971
query69	409	319	270	270
query70	933	825	830	825
query71	296	237	217	217
query72	2664	2570	2323	2323
query73	846	746	443	443
query74	4632	4508	4294	4294
query75	2290	2297	1941	1941
query76	2333	1161	734	734
query77	372	408	314	314
query78	9190	8992	8431	8431
query79	1351	1227	731	731
query80	573	472	364	364
query81	547	324	281	281
query82	622	168	127	127
query83	323	221	194	194
query84	335	144	115	115
query85	820	457	389	389
query86	340	256	230	230
query87	2005	1985	1853	1853
query88	3711	2749	2742	2742
query89	377	287	256	256
query90	1918	175	182	175
query91	173	160	131	131
query92	107	90	90	90
query93	1509	1420	848	848
query94	540	328	297	297
query95	658	370	329	329
query96	1018	749	360	360
query97	2412	2420	2334	2334
query98	161	151	146	146
query99	736	743	613	613
Total cold run time: 235984 ms
Total hot run time: 153075 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit b57d2ce5497c38cb41399743874f608d92a0fad4, data reload: false

query1	0.01	0.01	0.01
query2	0.10	0.05	0.05
query3	0.26	0.13	0.14
query4	1.61	0.14	0.14
query5	0.24	0.22	0.22
query6	1.16	0.95	0.96
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.39	0.34	0.33
query10	0.53	0.57	0.55
query11	0.21	0.14	0.14
query12	0.19	0.15	0.15
query13	0.46	0.47	0.47
query14	0.95	0.94	0.94
query15	0.60	0.58	0.59
query16	0.30	0.33	0.32
query17	1.04	1.07	1.11
query18	0.21	0.20	0.20
query19	1.96	1.96	1.89
query20	0.02	0.01	0.01
query21	15.50	0.22	0.14
query22	4.85	0.06	0.05
query23	16.12	0.31	0.12
query24	2.92	0.42	0.34
query25	0.12	0.06	0.04
query26	0.74	0.20	0.16
query27	0.04	0.04	0.04
query28	3.48	0.77	0.34
query29	12.54	4.00	3.21
query30	0.28	0.15	0.15
query31	2.77	0.54	0.31
query32	3.22	0.60	0.48
query33	3.12	3.18	3.23
query34	15.66	3.96	3.28
query35	3.23	3.22	3.21
query36	0.55	0.43	0.41
query37	0.10	0.07	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.14
query41	0.08	0.03	0.03
query42	0.04	0.02	0.02
query43	0.04	0.04	0.03
Total cold run time: 96 s
Total hot run time: 24 s

…hanges

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68390

Problem Summary: The refresh change in this PR replaces a refusal with an escalation, and three
suites pinned the refusal. A strict INCREMENTAL refresh that meets an invalidated baseline no longer
fails with "IVM baseline rebuild is pending": it rebuilds, so these cases have to assert the rebuild
and the data it produces rather than the error it used to report.

* test_ivm_partition_drop_live_delta: the strict refresh now succeeds and consumes the surviving
  partitions' delta in the same run, so the case pins the MV against the base table right after that
  refresh, not only after the FALLBACK refresh that used to perform the repair.
* test_ivm_partition_window_remove: removing ivm_partition_window_limit puts the MV in
  SCHEMA_CHANGE, so the strict refresh is escalated to a COMPLETE refresh and replays the p1 backlog
  itself; the tag that asserted the MV was left stale is now the one asserting it caught up.
* test_ivm_drop_referenced_column_baseline_rebuild: dropping a referenced column still fails, because
  the MV query can no longer be analysed, but the same-name re-add now makes the escalated refresh
  succeed. The schema-ABA step asserts SUCCESS with RefreshMode COMPLETE and the rebuilt rows,
  instead of a "baseline rebuild is pending" rejection -- rebuilding is what keeps that case from
  being accepted by the incremental path with rows computed under the old column semantics.

### Release note

None

### Check List (For Author)

- Test: Regression test
    - `mtmv_p0/ivm`: the three suites above pass with the stored expectations compared, not
      regenerated. `test_ivm_partition_window_remove` folds RefreshMode because an unset value comes
      back as `\N`, which does not survive the .out round trip.
- Behavior changed: No (test expectations only)
- Does this need documentation: No
…y position

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68390

Problem Summary: `MTMVPlanUtil#ensureMTMVQueryUsable` re-analyses the MV's query and compares the
result with the MV's physical schema (`checkColumnIfChange`), position by position. The two lists are
ordered by different passes: the physical schema is laid out when the MV is created, where
`applyIvmPhysicalKeyLayout` puts the final key columns first and the visible key prefix is derived from
the identity key slots, while the re-analysis runs that same layout with the **stored** key columns as
its input and so derives the prefix differently.

For a chained IVM MV whose base tables carry row-id columns of their own (`ivm_use_full_keys`), the
base tables' row-id columns land in a different block in the two lists, and every refresh of that MV
fails with a spurious error. Stored:

    k1, k2, __DORIS_IVM_0_ROW_ID_COL__, __DORIS_IVM_1_ROW_ID_COL__, __DORIS_IVM_ROW_ID_COL__, v1, v2

analysed:

    k1, k2, __DORIS_IVM_ROW_ID_COL__, v1, v2, __DORIS_IVM_0_ROW_ID_COL__, __DORIS_IVM_1_ROW_ID_COL__

Index 3 then compares `__DORIS_IVM_1_ROW_ID_COL__` (largeint) against `v1` (int) and reports
"column type not same ... original type is: largeint, current type is: int" -- a name held up against a
different column entirely.

What the check is for is a base-table change that makes a column disappear or change type, which is a
question about names and types rather than about where a column sits, and the refresh maps columns by
name. Comparing by name keeps that guard and drops the coupling to a layout the two passes are free to
disagree about.

Reproduction: `mtmv_p0/ivm/test_ivm_use_full_keys_6`. Its chained MV is built over two IVM MVs; a
complete refresh of those replaces their partitions without row binlog, which invalidates the chained
MV's baseline, and its own complete refresh then runs this check. The case passes now and fails when
this change is reverted.

### Release note

None

### Check List (For Author)

- Test: Regression test / Unit Test
    - `mtmv_p0/ivm`: all 97 suites pass, including `test_ivm_use_full_keys_6`, with the stored
      expectations compared rather than regenerated.
    - FE unit tests: `MTMVPlanUtilTest`, `MTMVTest`, `MTMVTaskTest`, `IvmBaselineRebuildTest` -- 147
      tests, all pass.
- Behavior changed: No (the check still rejects a column that disappears or changes type)
- Does this need documentation: No
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

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

Requesting changes on exact head d3f21b0. I found ten independent issues: five P1 correctness/availability or scale problems and five P2 routing, replay, signature, and diagnostic problems.

Checkpoint summary: the normal per-partition epoch path is coherent, but the task/DDL race, commit/publication cut, rename identity, and stream-recreation failover cut break the lifecycle proof. ADD_TASK replay is not equivalent to live state. The name-based schema comparison is safe under existing uniqueness/cardinality invariants, and no new configuration or FE/BE protocol incompatibility was found; the IVM metadata is pre-release. The change also introduces full-cardinality task journaling and inaccurate rebuild/progress observability. Changed tests cover normal invalidation, replay, routing, and successful reconciliation, but miss the reported adversarial interleavings, failover cuts, and scale bound. The focus file supplied no additional focus, so the complete authoritative diff and changed-file call chains were reviewed.

Validation was static only: the authoritative review prompt prohibited builds and tests, so CI/author results were not independently executed. Round 3 found a new independent issue at the configured three-round maximum; convergence is therefore capped/incomplete rather than a clean fixed point.

}
// The requirement these partitions are read under, captured before the read inside doRefresh and
// recorded only if the refresh commits; see captureLatestEpochs.
Map<String, Long> capturedEpochs = captureLatestEpochs(Sets.newHashSet(needRefreshPartitions));

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.

[P1] Recheck invalidation after choosing the rebuild set. A TRUNCATE can raise this partition's epoch and remove its snapshot after executeIvmAttempt sampled dirtyPartitions. It then enters incrementalScope, this call captures the raised epoch, and the row-delta refresh cannot delete the rows removed only through metadata. Success consequently writes refreshEpoch == latestEpoch and leaves those old MV rows permanently clean. Dirty selection and epoch capture need one generation decision (or an epoch advance here must abort/reroute the partition to rebuild); please add a latch test for the sample -> TRUNCATE -> capture ordering.

* exists to avoid, so the conservative reading wins.
*/
public boolean isDirty() {
return latestEpoch > refreshEpoch && refreshEpoch != 0;

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.

[P1] Do not equate refreshEpoch == 0 with durable emptiness. Alignment journals (0,1) before the first batch, but the MV DML commits before the task publishes its epoch/snapshot and before ADD_TASK is journaled. An FE crash in that cut (or cancel(true) after the executor callback clears executor but before batch publication) leaves committed rows behind replayed (0,1). A later TRUNCATE raises it to (0,2), this returns false, and strict incremental can accept an empty delta and mark the stale rows clean. Persist an in-progress/possibly-populated state before writing, or otherwise make batch commit and durable epoch publication recover as one lifecycle.

// that failure visible. What the state does to an IVM MV is make the next refresh rebuild the
// whole MV (MTMVTask#buildAttempts), which a rename back would have it repeat for nothing. A
// non-IVM MV keeps the state it has always got, which is what its own refresh reads.
continue;

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.

[P1] Preserve the dependency mapping across this rename. BaseTableInfo equality is name-based, but this branch neither invalidates the IVM nor re-keys tableMTMVsOneLevelAndFromView from oldTableInfo to newTableInfo. After t is renamed to tmp, metadata-only DDL such as TRUNCATE on tmp therefore finds no dependent MV; renaming tmp back also looks up only tmp and misses. The query is usable again, yet no dirty epoch exists and the delta stream cannot remove the truncated rows. Please move/alias the dependency entry on rename or keep a conservative invalidation, with a rename -> TRUNCATE -> rename-back regression.

// the ones partition sync has not created yet, and no per-partition requirement can express that.
// IVM only -- a non-IVM MV reaches the same effect through its cleared snapshot, which its own
// refresh already depends on.
if (mtmv.isIvm() && !request.explicitPartitions

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] Distinguish whole-IVM invalidation from generic SCHEMA_CHANGE here. processBaseTableChange sets this state even when re-analysis succeeds and no partition epoch was raised (for example DROP COLUMN spare, which the changed test says must not invalidate the baseline). This branch nevertheless converts the following strict INCREMENTAL request into a whole COMPLETE rebuild; the test checks only SUCCESS, so it misses that route. Use a dedicated whole-baseline marker or avoid setting this state for compatible IVM changes, and assert RefreshMode/IvmRebuiltPartitions.

this.refreshMode = generateRefreshMode(toRebuild);
executePartitionBasedRefresh(refreshContext, RefreshMode.PARTITIONS, ctx);
rebuiltSnapshots.putAll(partitionSnapshots);
recordRebuiltPartitions(request, dirtyPartitions.size());

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] Count rebuilt partitions as batches commit. If an early dirty-rebuild group commits and a later group fails, this line is never reached and IvmRebuiltPartitions stays 0 even though ADD_TASK preserves the committed group's epochs/data. The COMPLETE escalation sites have the inverse problem: they record every planned partition before the first batch, so an immediate failure reports all rebuilt. The new diagnostic should reflect successful groups, especially on failed tasks.

// dropped it, and transparent rewrite reads that map to decide what it may serve.
Map<String, MTMVRefreshPartitionSnapshot> snapshotsToWrite = partitionSnapshots;
if (!isReplay && ivmInfo.isEnableIvm()) {
snapshotsToWrite = snapshotsOfCleanPartitions(partitionSnapshots);

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] Journal the same filtered snapshot map that is applied live. When an invalidation lands during a task, the leader deliberately omits that dirty partition here, but alterMTMV still contains the raw task map. Replay restores the journaled dirty states, skips this !isReplay filter, and re-adds that snapshot, so restart does not reconstruct the leader's state or preserve the documented removal invariant. With all snapshot-bearing partitions dirty this also changes attempt selection from the no-snapshot COMPLETE branch (rebuilt count 0) to the all-dirty COMPLETE branch (count N). Put a detached snapshotsToWrite on the payload before submitAlterLog and cover invalidation-before-ADD_TASK replay order.

// here: its batches committed, and without them the partitions it rebuilt would look
// unsynced and be refreshed again on every following round.
this.partitionSnapshots.putAll(rebuiltSnapshots);
this.completedPartitions.addAll(dirtyPartitions);

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] Keep the progress denominator consistent when merging the rebuild phase. executeSingleIvmAttempt resets needRefreshPartitions to only the incremental scope and clears completedPartitions; this line then adds the dirty rebuilds only to the completed side. A task with one rebuilt and one incremental partition is recorded as 200% (2/1), while a dirty-only success reports null progress because the denominator is empty. Preserve the union of both phase scopes (and their completed sets) for task history.

LOG.info("IVM MV is in SCHEMA_CHANGE, rebuilding the whole MV, mv={}, taskId={}",
mtmv.getName(), getTaskId());
recordRebuiltPartitions(request, mtmv.getPartitionNames().size());
return Lists.newArrayList(RefreshAttemptType.COMPLETE);

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] Publish the rebuilt plan signature on this direct COMPLETE path. SCHEMA_CHANGE can come from altering a base view while keeping the MV output schema valid but changing its normalized join/layout plan. Because this branch skips the incremental attempt, ivmFallbackReason is never PLAN_SIGNATURE_MISMATCH; executePartitionBasedRefresh discards the signature produced by this successful rebuild and ADD_TASK keeps the old one. The next AUTO refresh then performs a second COMPLETE through mismatch fallback, while a next strict INCREMENTAL rejects the baseline it just rebuilt. Capture/persist the consistent full-refresh signature when this branch establishes the new baseline.

// this is the point where an entry and the partition it describes become the same thing.
// Doing it any later would let a partition that sync has just added be refreshed without an
// entry, and an invalidation arriving in between would have nothing to land on.
mtmv.alignPartitionStates(mtmv.getPartitionNames());

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.

[P1] Avoid making every later ADD_TASK carry this full aligned map. The existing task-result path deep-copies partitionStates under mvRwLock and serializes the whole map as JSON, but before this change the production map was never populated. This line now creates one entry per MV partition, so even a no-op scheduled refresh or a task advancing one partition emits O(total partitions) state. Doris already exercises 160,000 mapped MV partitions, making each periodic result a multi-megabyte record and lock-held copy. Persist only the task's detached epoch delta on ADD_TASK (and omit it when empty), keeping full maps for alignment/invalidation records.

// The barrier goes first: a stream this rebuild reconciles carries the base table's current
// rows as its initial snapshot, and a later incremental refresh that consumed it as a delta
// against data still built from the old baseline would double-count them.
writeIvmBaselineBarrier(RefreshMode.COMPLETE);

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.

[P1] Keep a durable rebuild requirement before recreating an IVM stream. A dropped/unusable stream sends a fallback refresh to COMPLETE, but reconcileIvmStreams durably creates its replacement with show_initial_rows=true before any MV rebuild batch. If FE crashes after that create and before the rebuild/ADD_TASK, restart has the old populated MV with clean nonzero epochs plus a usable stream whose historical rows are exposed as APPEND; the next AUTO/INCREMENTAL refresh can therefore add those rows to the old baseline again. This removed barrier protected exactly that cut. Persist a whole-MV/affected-partition requirement before reconciliation and clear it only after the rebuilt baseline is durably published, with a failover test at this boundary.

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68390

Problem Summary: `MTMVRelationManager` keys its dependency maps by `BaseTableInfo`, which
compares by name, and a rename was handled by putting the dependent MVs into `SCHEMA_CHANGE`
instead of keeping the lookup working. That left a hole as soon as the rename stopped setting
that state for an IVM MV: a rename leaves the MV query spelling the old name, so the query no
longer analyses and the MV's relation is never recomputed -- the maps keep the old name. A
metadata-only change to the table under its new name, a TRUNCATE for instance, then finds no
dependent MV to invalidate, and renaming the table back restores an analyzable query whose MV
still holds the rows that change removed, with nothing naming the partition to rebuild.

`alterTable` now moves the renamed table's entries in `tableMTMVs` and
`tableMTMVsOneLevelAndFromView` to the new name, registering them under the new name before
dropping the old one, so a concurrent base-table change either still finds the old name or
already finds the new one. The invalidation itself runs first, while the dependencies are still
registered under the name being left: the lookup is by the old name, so moving the entries first
would make it find nothing and the rename would stop invalidating anything at all -- for a
non-IVM MV as much as for an IVM one, which is the behaviour that has to stay as it was.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - `IvmBaselineRebuildTest` (38 tests) and the full set run for this branch --
      `MTMVPlanUtilTest, MTMVTest, MTMVTaskTest, IvmBaselineRebuildTest, AlterMTMVTest, IvmInfoTest,
      MTMVRefreshSnapshotTest` -- 185 tests, all pass. The ordering above is what
      `IvmBaselineRebuildTest#testRenameStillInvalidatesANonIvmMv` pins: it fails when the entries
      are moved before the invalidation and passes with this order.
- Behavior changed: No (a non-IVM MV keeps the state a rename has always given it; an IVM MV keeps
  the state this PR's earlier commit stopped giving it, and gains a lookup that keeps working)
- Does this need documentation: No
…an be interrupted at

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68390

Problem Summary: A code review of this PR found five correctness problems in how the per-partition
requirement the refresh routes on is read, published and recovered. They share the state contract, so
they are landed together; each is listed with the review item it answers.

* P1-2: `refreshEpoch == 0` was read as "the partition holds no rows". A refresh commits the MV data
  transaction before its task result is journaled, so a crash in between leaves rows behind a pair the
  MV loaded as "never refreshed"; a later invalidation raising `latestEpoch` then found nothing dirty
  and a strict INCREMENTAL kept the rows. `MTMVPartitionState` gains an `inProgress` flag, written
  before the data transaction, and `needsRebuild()` -- what the routing reads -- is wider than
  `isDirty()` by it. A payload written before the member existed reads as "not in progress".
* P1-9: a task result journaled the whole aligned map, so every periodic refresh emitted one entry per
  MV partition -- an O(partitions) record and a lock-held deep copy, on 160,000-partition MVs. It now
  journals only the partitions it published, and the ADD_TASK replay merges per entry instead of
  assigning, because the entries it omits belong to other records. The state-map channel
  (ALTER_PARTITION_STATES) still replaces: that one carries the whole map.
* P1-1: dirty selection and epoch capture were two reads. A TRUNCATE landing between them was captured
  by the incremental attempt, which then published an epoch that said the rows it could not remove were
  current. The plan now reads the states once and carries a per-partition ceiling, and the publish
  clamps to `min(captured, planned)`: a mark that lands mid-refresh leaves the partition dirty for the
  next refresh instead of being swallowed.
* P1-10: `reconcileIvmStreams` durably creates a replacement stream (its historical rows exposed as
  APPEND) before any rebuild batch. A crash in between leaves a populated MV with clean epochs and a
  usable stream, and the next refresh adds those rows to the old baseline again. The partitions are
  marked before reconciliation now -- the durable requirement the removed barrier used to write.
* P2-5 / P2-7: the rebuilt-partition count was recorded when the rebuild was planned, so a task that
  failed part way claimed partitions it never replaced; it is recorded from the committed batches
  instead, and the progress denominator is the union of the rebuild and incremental phases rather than
  the incremental scope alone.

Also covers P2-5 and P2-7, which are diagnostic only.

### Release note

None

### Check List (For Author)

- Test: Unit Test / Regression test
    - FE unit tests: `MTMVPlanUtilTest`, `MTMVTest`, `MTMVTaskTest`, `IvmBaselineRebuildTest`,
      `AlterMTMVTest`, `IvmInfoTest`, `MTMVRefreshSnapshotTest` -- 185 tests, all pass.
    - Regression: `mtmv_p0/ivm` -- all 97 suites pass with the stored expectations compared.
    - Positive controls: the in-progress criterion, the delta payload and the progress union each fail
      their case when reverted.
- Behavior changed: No (a partition being published is rebuilt rather than skipped, which is what the
  removed barrier asked for as well)
- Does this need documentation: No
…ll causes

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68390

Problem Summary: Dropping a column the MV does not read leaves the IVM baseline alone, but the shared
base-table change hook still moves the MV into SCHEMA_CHANGE, and this PR reads that state as "the whole
MV must be rebuilt". The case asserted only that the refresh succeeded, so the escalation it was
actually getting went unnoticed; it now pins RefreshMode and IvmRebuiltPartitions as well.

Narrowing the hook so a change that re-analyses cleanly leaves an IVM MV alone is a separate change; the
assertion is here so the escalation cannot change again unnoticed until then.

### Release note

None

### Check List (For Author)

- Test: Regression test
    - `mtmv_p0/ivm/test_ivm_drop_referenced_column_baseline_rebuild` passes with the stored expectations
      compared, not regenerated.
- Behavior changed: No (test only)
- Does this need documentation: No
@yujun777

Copy link
Copy Markdown
Contributor Author

P2-8 (publish the rebuilt plan signature on the direct COMPLETE path) — not taken, and here is the evidence.

I implemented it: widening capturePlanSignature to mtmv.isIvm() && refreshMode == COMPLETE. It is not a diagnostic change — it is a data-correctness regression. Three suites start writing their delta twice:

  • test_ivm_snapshotpartition_after_incr: 1 / 55.00 becomes 2 / 110.00
  • test_ivm_bitmap_agg_2scalar_delete_all_incremental
  • test_ivm_agg_array_1list_after_group_delete

Bisected and isolated: reverting every other change from this round keeps them red, and restoring everything while reverting only this one makes them green again.

The mechanism is the one the finding proposes, read the other way round: publishing the fresh signature lets the next refresh take the incremental path instead of the PLAN_SIGNATURE_MISMATCH -> COMPLETE fallback, and that incremental re-applies rows the COMPLETE had just rebuilt. The "second COMPLETE" is therefore load-bearing protection, not the wasted work the finding describes.

Fixing what the finding is really after needs the incremental path's offset/baseline handling understood first, so I have left the condition as it was rather than widen it. Recording it here so it is not lost.

@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor 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.

Requesting changes on exact head acad27a. I found five new distinct issues: three P1 rename-lifecycle correctness failures and two P2 lock/fallback efficiency problems.

Checkpoint summary: the per-partition epoch and replay paths are otherwise conservative, and previously reported direct-IVM durability, generic SCHEMA_CHANGE, signature, diagnostics, stream, snapshot, and scale issues were treated as duplicate fences rather than repeated. The new rename key move is not durable across replay/image reconstruction, can be undone by stale task publication, and is not atomic with catalog visibility. The name/type schema contract, chained hidden-column binding, replace/drop paths, compatibility, and error handling produced no additional distinct finding. The changed tests cover normal sequential rename and rebuild behavior but miss replay, stale-publication, catalog-to-hook interleavings, delayed journal completion, and rebuild-then-fallback execution count. No user review focus was specified, so the complete authoritative diff and all 31 changed files were reviewed.

Validation was static only: the authoritative review instructions prohibited builds and tests, so author/CI results were not independently executed. Three review rounds converged with no additional valuable findings in the final round.

* carry: a rename changes no rows, so there is nothing to rebuild, only a lookup that has to keep
* working.
*/
private void renameBaseTable(BaseTableInfo oldTableInfo, BaseTableInfo newTableInfo) {

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.

[P1] Make this dependency re-key survive replay and cache reconstruction. The move only changes these derived maps; every dependent MTMV still persists the old name in MTMV.relation, registerMTMV rebuilds the maps from that relation, and replayRenameTable never calls this hook. After rename t -> tmp and a restart/replay, TRUNCATE tmp therefore finds no dependent; renaming tmp back leaves the old MV rows with no dirty epoch. Persist/replay the relation rename (or use stable table identity), and cover rename -> restart/replay -> TRUNCATE -> rename-back.

// Registered under the new name before the old one is dropped: a concurrent base-table change
// either still finds the old name or already finds the new one, never neither. Merged rather than
// replaced, because a table dropped and re-created under this name registers its own dependents.
map.computeIfAbsent(newTableInfo, key -> Sets.newConcurrentHashSet()).addAll(dependents);

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.

[P1] Fence refresh relations captured before this rename. A task can finish its DML with relation keyed by t, then t -> tmp moves the live key here without advancing the IVM schema generation. The later accepted ADD_TASK calls refreshComplete with that stale relation; refreshMTMVCache re-adds t and prunes tmp. TRUNCATE tmp is then invisible, and renaming back exposes stale rows. Reject/translate pre-rename task relations with a dependency generation, and add a latch test for task return -> rename -> result publication -> TRUNCATE.

// Every partition has to be rebuilt, including the ones this MV does not have yet, so the
// MV goes into the state that says exactly that. Journaled on its own record, ahead of the
// property change below; a replay applies both in that order.
invalidateWholeMv("The MV's refresh baseline changed with its properties");

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 wait for this status journal while holding mvRwLock. invalidateWholeMv re-enters alterStatus and then processAlterMTMV calls synchronous logAlterMTMV; batch mode waits for the edit-log worker and direct mode performs the write here. A slow journal therefore blocks every reader, invalidation, and task-result publication for this MV, contrary to the submit-under-lock/await-after-unlock pattern used by the surrounding property record. Enqueue the ordered status and property records under the lock, then await both after releasing it.

this.completedPartitions.addAll(rebuildCompleted);
return AttemptResultType.SUCCESS;
}
if (ivmResult.getFailureReason() != IvmFailureReason.MV_PARTITION_NOT_FOUND) {

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] Preserve the dirty rebuild when IVM falls back to PARTITIONS. The rebuild above commits and saves its snapshots only in local rebuiltSnapshots, then executeSingleIvmAttempt resets the task accumulators. On any fallback-allowed result this return happens before those locals are merged, so the following PARTITIONS attempt replans against mtmv's still-missing invalidated snapshot and INSERT OVERWRITEs every just-rebuilt partition again. Carry the committed rebuilt set/snapshots into fallback planning or exclude that set, and add a forced-fallback test that asserts each dirty partition is rebuilt once.

if (CollectionUtils.isEmpty(dependents)) {
return;
}
// Registered under the new name before the old one is dropped: a concurrent base-table change

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.

[P1] Close the gaps in this rename transition. Env.renameTable exposes and journals tmp, then releases the database/table locks before Alter later invokes this hook, so TRUNCATE tmp or DROP PARTITION can mark while the map is still keyed only by t and commit with no rebuild epoch. Even after this method starts, computeIfAbsent publishes an empty tmp set before addAll fills it. This rename then skips IVM invalidation and only moves the key. Make catalog visibility and the populated dependency entry atomic (or conservatively invalidate across both gaps), and latch-test rename -> metadata DDL -> hook -> rename-back.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants