Skip to content

[feature](ivm) Add the per-partition refresh state and its journal channel - #68193

Open
yujun777 wants to merge 1 commit into
apache:masterfrom
yujun777:ivm-pr2-upstream-master
Open

yujun777 wants to merge 1 commit into
apache:masterfrom
yujun777:ivm-pr2-upstream-master

Conversation

@yujun777

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Trace issue: #65418

This PR adds no behaviour of its own. It adds the state that the following PRs need, and the channel
that persists it, so that they can be reviewed as logic alone.

An IVM materialized view has to invalidate the MV partitions that a base-table change really affected.
A partition drop / truncate / replace / recover changes the base table through metadata and emits no
row binlog, so the affected MV partitions must be rebuilt; today the only answer the MV has is "rebuild
all of them", which throws away partitions that are still correct.

Deciding per partition needs a per-partition answer to two questions:

  • which generation of data does this MV partition currently hold?
  • which generation must it hold?

That pair is MTMVPartitionState { refreshEpoch, latestEpoch }, one entry per MV partition, keyed by
partition name. latestEpoch is the requirement, refreshEpoch is the reality, and a partition whose
requirement is ahead of its reality is dirty: it holds rows read before a change that left no binlog,
so it can no longer be maintained incrementally and has to be rebuilt. The requirement has to survive a
restart, because an invalidation that only lives in memory is lost the moment the FE restarts, and a
partition that is then refreshed incrementally keeps the stale rows forever with no error anywhere.

So this PR adds

  1. the state itself (MTMVPartitionState, plus a copy helper for taking a detached snapshot), and
  2. the channel that carries it into the journal and back: a field on the MV, its own field on the alter
    record, a dedicated alter op with its replay branch, and the replay handling of the task result.

Nothing in the FE decides anything from the state yet, and nothing but a replay ever writes it, so every
MV behaves exactly as before. That is deliberate: it makes this step independently mergeable and
independently testable, which is what the PR that starts using the state needs underneath it.

Scope

Adds MTMVPartitionState, the partitionStates field, the alter record field, the new alter op and its replay branch
Does not touch any criterion, routing or invalidation decision; IvmInfo; the refresh path
Field is shared, behaviour is not the field sits on the MV, so both kinds of MV carry it; only an IVM MV ever populates it, and the journal of a non-IVM MV stays byte-for-byte what it was
Compatibility an image written before this PR has no such field and loads as an empty map; an ADD_TASK journal written before it applies nothing on replay instead of clearing what is there

The state is on the MV rather than inside IvmInfo, and the alter op is its own rather than riding on
ALTER_IVM_INFO, whose branch only swaps the IvmInfo object. Both are structural: the same state is
meant to serve a non-IVM MV later, and its journal payload must not be reconstructed as a side effect of
replaying some other op.

Key changes

  • Add MTMVPartitionState, a persisted refreshEpoch / latestEpoch pair keyed by MV partition name, and MTMVPartitionState.copyOf for taking a detached snapshot of a state map. A partition gets a new id on every refresh, so the name is the only identity it can have.
  • Add MTMV.partitionStates with its getter and its replay setter; gsonPostProcess initializes it, so an image written before the field existed and a non-IVM MV both load as an empty map.
  • Carry the state in the ADD_TASK payload under the same condition as ivmInfo, which keeps the journal of a non-IVM MV byte-for-byte unchanged, and apply it on replay only when the field is present, so an old journal applies nothing rather than clearing the state.
  • Add MTMVAlterOpType.ALTER_PARTITION_STATES and its Alter.processAlterMTMV branch.

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:
  • Behavior changed:

    • No.
  • Does this need documentation?

    • No.

🤖 Generated with Claude Code

…annel

Rebuilding only the MV partitions that a silent base-table change really invalidated needs a
per-partition state: which generation of data the partition holds and which generation it must hold,
persisted so that it survives a restart. This adds that state and the journal channel that carries
it. Nothing decides anything from it yet and nothing but a replay writes it, so the behaviour of
every MV is unchanged.

Key changes:
- Add MTMVPartitionState, a persisted refreshEpoch / latestEpoch pair keyed by MV partition name, and MTMVPartitionState.copyOf for taking a detached snapshot of a state map
- Add MTMV.partitionStates with its getter and its replay setter; gsonPostProcess initializes it, so an image written before the field existed and a non-IVM MV both load as an empty map
- Carry the state in the ADD_TASK payload under the same condition as ivmInfo, which keeps the journal of a non-IVM MV byte-for-byte what it was, and apply it on replay only when the field is present
- Add MTMVAlterOpType.ALTER_PARTITION_STATES and its Alter.processAlterMTMV branch, so a live state change has an op of its own instead of riding on ALTER_IVM_INFO, whose branch only swaps the IvmInfo object

Unit Test:
- MTMVTest: an image round trip of a state, an image without the field, the getter before any state exists, the detached journal payload, the ADD_TASK carry for an IVM MV and its absence for a non-IVM MV, and both replay directions
- AlterMTMVTest: replay of ALTER_PARTITION_STATES through Alter.processAlterMTMV on a real MV
@yujun777
yujun777 marked this pull request as draft September 18, 2026 09:36
@yujun777
yujun777 marked this pull request as ready for review September 18, 2026 09:36
@yujun777
yujun777 marked this pull request as draft September 18, 2026 09:37
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@yujun777
yujun777 marked this pull request as ready for review September 18, 2026 09:37
@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.

Static review conclusion: request changes for two P2 issues: the new partition-state API exposes mutable owned state after releasing its lock, and the new journal channel is not tested across its actual AlterMTMV.write/read representation.

Critical checkpoint conclusions:

  • Goal and proof: the change is focused on adding inert per-partition epoch state plus image, ADD_TASK, and dedicated alter replay channels. The production serialization path is structurally present, but the journal wire/restart contract lacks a round-trip oracle.
  • Scope and clarity: the seven changed FE files are cohesive and do not add refresh/invalidation policy. The additive DTO and replay branches are otherwise small and clear.
  • Concurrency and locking: task completion snapshots and enqueues under mvRwLock, the FIFO edit-log queue preserves order, and await() is correctly outside the lock. No new lock-order or deadlock issue was found. However, getPartitionStates() returns the live map and mutable values after unlocking, bypassing that ownership invariant for the follow-up callers this API is meant to support.
  • Lifecycle: old images initialize the missing field to empty; old ADD_TASK records with no member preserve current state; an explicit empty map clears it; replay applies detached copies. INSERT OVERWRITE keeps the formal partition name while changing its id, and no current production logic populates or consumes the map. Future sync drop/add logic must own state removal/reinitialization atomically.
  • Configuration and initialization: no configuration item, static-initialization dependency, or new thread is introduced.
  • Compatibility: the image and ADD_TASK fields are additive Gson members; non-IVM ADD_TASK payloads remain unchanged; the dedicated enum operation is not emitted by live code in this PR. No FE-BE protocol or storage-format change applies.
  • Parallel paths and conditions: IVM/non-IVM, image, ADD_TASK success/failure replay, and dedicated alter replay were traced. The IVM-only live carry condition is consistent with this PR's stated scope.
  • Tests and results: the added unit tests cover image compatibility, detached copies, absent ADD_TASK state, IVM/non-IVM carry, and in-memory replay, but not the actual alter-journal bytes or the absent-versus-empty distinction after deserialization. No result files changed. Per the runner instruction, this review was static-only: I did not run builds or tests. CheckStyle had passed externally; FE UT and compile were still pending when inspected, so they are not treated as independent validation here.
  • Observability: because the state is intentionally inert in this PR, no new logging or metric is required yet.
  • Persistence/failover and data correctness: detached copying before asynchronous serialization, fatal edit-log failure behavior, dispatch, and replay ordering are otherwise correct. There is no direct user-data write or transaction-protocol change.
  • Performance: full-map copying is linear but currently empty/inert and is necessary for a detached payload; no separate confirmed performance defect was found.
  • User focus and completion: no additional user focus was supplied. Two complete review rounds were performed; all normal and risk-focused reviewers returned NO_NEW_VALUABLE_FINDINGS in the convergence round, and every candidate was accepted, deduplicated, or dismissed with evidence.

if (partitionStates == null) {
partitionStates = Maps.newLinkedHashMap();
}
return partitionStates;

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 partition-state ownership inside mvRwLock

This returns the live map after the finally releases mvRwLock, and each value is mutable too. A caller using the pattern in the new tests (getPartitionStates().put(...)) can therefore add/remove entries or change an epoch while addTaskResult() is copying the same LinkedHashMap for the journal, producing a ConcurrentModificationException or a mixed snapshot. If replay replaces the field first, the retained reference instead accepts a silently lost update. Since this API is the persistence foundation for the follow-up invalidation/alignment code, please return a deep detached/unmodifiable snapshot for reads and add lock-owning MTMV mutation methods that mutate and enqueue ALTER_PARTITION_STATES under the same write lock.

// The MV starts without any state, so only the replayed payload can put it there.
mtmv.alterPartitionStates(Map.of());

Env.getCurrentEnv().getAlterInstance().processAlterMTMV(replayAlter, true);

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] Exercise the journal wire round trip before replay

This calls processAlterMTMV with the same in-memory object created above, and the ADD_TASK tests likewise inspect an object captured by a mocked submitEdit. As a result, removing or mis-serializing the new op/pst member—or collapsing the required absent-versus-empty distinction—would leave every new replay test green even though restart/failover is this PR's main deliverable. Please round-trip AlterMTMV through write/read (or JournalEntity) before replay and cover present nonempty, present empty (clear), and absent old payload (preserve).

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17718	4202	4177	4177
q2	2291	336	289	289
q3	10004	1460	830	830
q4	4680	487	350	350
q5	7491	849	573	573
q6	192	179	143	143
q7	834	839	626	626
q8	9322	1688	1576	1576
q9	5500	4241	4223	4223
q10	6749	1668	1351	1351
q11	436	280	256	256
q12	642	425	308	308
q13	18113	2631	2009	2009
q14	270	273	242	242
q15	q16	728	724	669	669
q17	1829	1206	1094	1094
q18	6476	5635	5527	5527
q19	1381	1351	1110	1110
q20	477	407	276	276
q21	5838	2587	2382	2382
q22	420	357	298	298
Total cold run time: 101391 ms
Total hot run time: 28309 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4312	4226	4295	4226
q2	785	614	588	588
q3	4531	4888	4415	4415
q4	2296	2362	1483	1483
q5	4285	4148	4194	4148
q6	233	185	137	137
q7	1706	1630	1781	1630
q8	2763	2217	2208	2208
q9	7656	7705	7655	7655
q10	4310	4339	3957	3957
q11	603	428	396	396
q12	744	777	549	549
q13	2438	2809	2171	2171
q14	296	312	264	264
q15	q16	709	730	635	635
q17	7949	7228	7367	7228
q18	11897	11048	11814	11048
q19	1157	1076	1081	1076
q20	2274	2246	1923	1923
q21	5987	5047	5113	5047
q22	562	494	410	410
Total cold run time: 67493 ms
Total hot run time: 61194 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 153758 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 35f9b15844a5df0875acb354b16390910bddee3a, data reload: false

query5	4328	641	473	473
query6	436	189	171	171
query7	4859	543	301	301
query8	319	183	173	173
query9	8817	4024	4012	4012
query10	449	320	259	259
query11	5656	2198	2052	2052
query12	175	101	95	95
query13	1246	560	409	409
query14	6535	4621	4262	4262
query14_1	4048	4018	3989	3989
query15	204	198	176	176
query16	997	458	437	437
query17	906	679	514	514
query18	2421	450	326	326
query19	193	179	135	135
query20	101	99	102	99
query21	213	138	118	118
query22	13110	13110	12795	12795
query23	15674	14607	13884	13884
query23_1	14097	14065	14015	14015
query24	7603	1753	1237	1237
query24_1	1231	1237	1240	1237
query25	532	413	344	344
query26	1270	316	162	162
query27	2688	534	338	338
query28	4542	1960	2004	1960
query29	1035	597	471	471
query30	311	239	205	205
query31	907	792	648	648
query32	143	110	94	94
query33	521	340	248	248
query34	1164	1142	638	638
query35	756	784	663	663
query36	806	824	723	723
query37	164	120	94	94
query38	1848	1780	1718	1718
query39	710	706	653	653
query39_1	687	643	669	643
query40	235	140	105	105
query41	75	70	68	68
query42	94	93	91	91
query43	360	359	318	318
query44	1419	682	702	682
query45	192	174	174	174
query46	1105	1236	688	688
query47	1482	1496	1388	1388
query48	390	429	292	292
query49	597	432	300	300
query50	1014	362	241	241
query51	10560	10507	10411	10411
query52	88	91	77	77
query53	253	262	179	179
query54	303	216	196	196
query55	76	78	68	68
query56	225	218	204	204
query57	1375	1392	1422	1392
query58	255	219	208	208
query59	2023	2094	1789	1789
query60	279	249	225	225
query61	145	138	147	138
query62	410	325	289	289
query63	217	182	173	173
query64	2832	996	813	813
query65	4011	3979	3936	3936
query66	1830	445	321	321
query67	20158	20036	20087	20036
query68	3578	1525	869	869
query69	411	306	271	271
query70	996	883	870	870
query71	300	239	209	209
query72	3132	2539	2186	2186
query73	850	748	418	418
query74	4629	4480	4307	4307
query75	2384	2315	1926	1926
query76	2388	1138	735	735
query77	368	400	304	304
query78	9324	9030	8541	8541
query79	1222	1161	737	737
query80	525	457	356	356
query81	471	285	235	235
query82	266	167	126	126
query83	270	273	246	246
query84	296	145	113	113
query85	789	457	376	376
query86	303	243	243	243
query87	2001	1981	1838	1838
query88	3685	2714	2698	2698
query89	332	291	245	245
query90	2123	180	179	179
query91	165	151	122	122
query92	102	82	86	82
query93	1567	1506	862	862
query94	527	339	292	292
query95	671	380	334	334
query96	1112	780	329	329
query97	2468	2416	2310	2310
query98	199	186	185	185
query99	734	727	612	612
Total cold run time: 241941 ms
Total hot run time: 153758 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.08	0.05	0.05
query3	0.27	0.13	0.14
query4	1.60	0.14	0.13
query5	0.23	0.22	0.23
query6	1.15	0.92	0.92
query7	0.04	0.01	0.00
query8	0.05	0.03	0.04
query9	0.40	0.34	0.34
query10	0.57	0.58	0.59
query11	0.20	0.14	0.14
query12	0.18	0.15	0.15
query13	0.46	0.47	0.46
query14	0.98	0.93	0.95
query15	0.60	0.58	0.58
query16	0.31	0.32	0.33
query17	1.12	1.07	1.12
query18	0.21	0.20	0.20
query19	2.08	2.00	1.95
query20	0.02	0.02	0.01
query21	15.50	0.21	0.13
query22	4.83	0.06	0.05
query23	16.15	0.29	0.12
query24	2.88	0.40	0.32
query25	0.11	0.05	0.04
query26	0.73	0.20	0.14
query27	0.05	0.03	0.02
query28	3.57	0.76	0.35
query29	12.51	4.05	3.24
query30	0.28	0.16	0.15
query31	2.77	0.57	0.31
query32	3.24	0.59	0.49
query33	3.15	3.12	3.23
query34	15.59	3.96	3.29
query35	3.20	3.22	3.22
query36	0.56	0.43	0.43
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.04
query40	0.17	0.16	0.14
query41	0.08	0.03	0.02
query42	0.04	0.03	0.03
query43	0.05	0.04	0.03
Total cold run time: 96.19 s
Total hot run time: 23.98 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 95.45% (42/44) 🎉
Increment coverage report
Complete coverage report

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