Skip to content

[fix](insert) Finish or fail a cancelled INSERT OVERWRITE instead of reporting success - #68662

Open
yujun777 wants to merge 9 commits into
apache:masterfrom
yujun777:fix-insert-overwrite-cancel
Open

yujun777 wants to merge 9 commits into
apache:masterfrom
yujun777:fix-insert-overwrite-cancel

Conversation

@yujun777

@yujun777 yujun777 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Related PR: #68390

Problem Summary:

An INSERT OVERWRITE is two-phase: the rows are committed into temporary partitions first, and a later swap publishes them. A cancellation that lands between the two halves cannot take anything back -- the rows are durable, and everything the read consumed, the base table stream offsets among it, was committed with that same transaction (InsertIntoTableCommand hangs the offsets on the transaction, DatabaseTransactionMgr.updateCatalogAfterCommitted applies them on commit and replays them). The command answered that window by dropping the temporary partitions and returning normally:

  • the client was told the overwrite succeeded, while the table still held the rows it had;
  • the offsets had advanced past rows no target holds, so a re-run reads from the advanced offset and that range is silently missing. This is the general shape of INSERT OVERWRITE t SELECT * FROM stream(...), and also of an IVM partition refresh, which resets the offsets of exactly the partitions it replaces.

The cancellations that land before the rows are committed were answered the same way: success for a statement that did nothing.

Fix: decide by whether the rows are already committed.

  • Before they are -- including where the insert committed nothing at all, which is the path an empty plan takes
    (it begins no transaction), so the temporary partitions hold no rows and there is no offset to keep -- nothing
    durable happened, so the statement fails, like the cancellation the inner insert already reports for itself, and a
    re-run reads the same rows. The auto-detect route takes the same decision.
  • After they are: the cancellation is too late, so the swap runs, the offset advance and the publication stay aligned, and the success the statement reports is the outcome it is.

A debug point (stage=beforeTheInsert|afterTheInsert, scoped by table name) injects the cancellation at either side, in the same shape as the existing failBetweenTheTwoHalvesOfAnOverwrite.

Scope: this closes the cancellation, which is the one entry into the window that reports success and therefore cannot be recovered by any later refresh. #68390's per-partition rebuild requirement, raised before a refresh reads, covers the failure and master-switch halves of the same window -- but a cancellation defeats it, precisely because the statement says it succeeded and the refresh records its epochs as met. The crash window and a replacePartition that throws are unchanged and still need the swap to become a committed action of the insert transaction.

Release note

KILL/cancellation of an INSERT OVERWRITE that lands after its rows were committed now completes the overwrite instead of silently reporting a success that published nothing. A cancellation before that point -- including one that lands where the insert committed nothing at all, such as an overwrite whose plan folded to an empty relation -- now reports an error and leaves the target untouched, instead of reporting success for a statement that did nothing or emptying the table for a statement that was cancelled.

Check List (For Author)

  • Test: Regression test. New suite insert_overwrite_p0/test_insert_overwrite_cancel covers both sides; insert_overwrite_p0 in full (14 suites) and mtmv_p0/ivm/test_ivm_overwrite_failure_between_the_halves pass locally. The new suite cannot fail on the pre-change code: the injection point it needs is introduced by this change, the same way [feature](ivm) Track the refresh baseline per MV partition, not by a rebuild barrier #68390 added the point its suite uses.
  • Behavior changed: Yes. A cancelled INSERT OVERWRITE reports failure when it committed nothing, and success only after completing the swap, instead of reporting success in both cases.
  • Does this need documentation: No

…at lost rows

An INSERT OVERWRITE commits its rows into temporary partitions and publishes them
with a swap afterwards, so a cancellation that lands between the two halves is too
late to take anything back: the rows are durable, and everything the read consumed
-- the base table stream offsets among it -- was committed with that same
transaction. The window was answered by dropping the temporary partitions and
returning normally, which published nothing while telling the client the overwrite
had succeeded, and left the consumed offsets advanced past rows no target will ever
hold (a re-run reads from the advanced offset and silently misses that range). The
cancellations that land before the rows are committed were answered the same way,
reporting success for an overwrite that never ran.

Key changes:
- A cancellation that lands before the rows are committed now fails the statement,
  like the cancellation the inner insert already reports for itself. Nothing
  durable happened, so the offsets are where they were and a re-run reads the same
  rows.
- A cancellation that lands after the rows are committed completes the swap
  instead: the rows the read consumed are the ones the table holds, the offset
  advance and the publication stay aligned, and the success the statement reports
  is the outcome it is.
- The failure path no longer asks the overwrite manager to fail the task of a
  cancellation that landed before any task was registered.
- A debug point (stage=beforeTheInsert|afterTheInsert, scoped by table name)
  injects the cancellation at either side of the insert.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy
  covers both sides: a cancelled statement leaves the table untouched, and a
  cancellation inside the window is published rather than dropped.
- insert_overwrite_p0 (14 suites, including the new one) and
  mtmv_p0/ivm/test_ivm_overwrite_failure_between_the_halves pass.
@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

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@yujun777 yujun777 changed the title [fix](fe) Finish or fail a cancelled INSERT OVERWRITE instead of reporting success [fix](insert) Finish or fail a cancelled INSERT OVERWRITE instead of reporting success Sep 30, 2026

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

Review outcome

Complete static review of PR #68662 at 85c4ae452e17feccaa9b297fdf2a2a11a69d5b9a. Request changes for the P1 cancellation/data-loss case. Four distinct new inline findings are attached (one P1, two P2, one P3). There were no existing inline P0/P1 findings to reconfirm and no additional user focus points.

Critical checkpoints

  • Goal and proof: The explicit-partition path now fails before task registration/insert and completes the swap after a committed nonempty insert. The goal is incomplete for a physical empty-relation input: no transaction commits, yet a cancelled explicit overwrite can replace the table with empty partitions (P1). The new post-insert test can pass without firing its cancellation point (P2).
  • Scope and structure: Changes are localized to the overwrite command and one regression suite. The conditional assumption that every returned inner insert committed rows is false for the empty-plan fast path. PARTITION(*) remains a parallel false-success path (P2).
  • Concurrency and lifecycle: StmtExecutor.cancel runs on another thread, sets the outer command's atomic flag and a pending coordinator reason, and may cancel a published coordinator. The inner insert is a separate command. A nonempty insert publishes a coordinator and fences cancellation before commit; an empty insert returns before that handoff. Task registration, temporary partitions, swap, and cleanup were traced. No new locks, lock-order issue, static-initialization dependency, or resource cycle was found.
  • Transactions, persistence, and writes: Before insert, task failure drops temporary partitions; after a committed nonempty insert, the swap preserves its rows and consumed offsets. The empty-source case violates the before-commit cancellation rule and can erase previously visible rows. The task manager's existing edit-log/replay path and the pre-existing crash/swap window are unchanged; this review did not claim failover validation.
  • Parallel paths and compatibility: Auto-detect, local/remote OLAP, and connector paths were examined. The auto-detect empty-source cancellation gap is distinct from the explicit-partition data loss. No new configuration, FE/BE transmitted variable, function symbol, protocol, or storage-format change was introduced; rolling-version handling does not change.
  • Tests and expected output: The .out rows match the fixture, and the suite uses deterministic ordered queries, an expected-error assertion, and debug-point cleanup. It lacks an assertion that the later cancellation fired, and it does not exercise real StmtExecutor.cancel, a table-stream offset, or the empty-source case. No builds or tests were run because the review instructions prohibit them; author-reported test passes were not treated as independent verification.
  • Observability and performance: Cancellation logs include query IDs; no additional production metric or hot-path performance issue was substantiated. The one-shot debug point is broken because each parameter lookup consumes its execution budget (P3). No other substantiated changed-line issue remained after the final sweep.

@@ -266,32 +283,37 @@ public void run(ConnectContext ctx, StmtExecutor executor) throws Exception {
} else {
// it's overwrite table(as all partitions) or specific partition(s)
List<String> tempPartitionNames = InsertOverwriteUtil.generateTempPartitionNames(partitionNames);

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] Apply the pre-commit cancel rule to PARTITION(*) as well. This check is only in the explicit-partition branch. For INSERT OVERWRITE t PARTITION(*) SELECT ... LIMIT 0, the inner insert returns through its empty-plan fast path without a transaction or coordinator, so a real KILL leaves the outer cancellation flag set but the auto-detect branch still calls taskGroupSuccess and reports success. Check cancellation in that branch when no insert committed, so this form does not retain the false-success behavior the PR is fixing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the same boundary to this branch: insertIntoAutoDetect now hands its context back, and a cancellation there is honoured when the load committed nothing (hasCommittedNothing()), the same way the explicit-partition branch does.

One correction to the premise, from probing the plans on a local cluster: a PARTITION(*) target is partitioned, and an empty query over a partitioned target plans an exchange, so the sink's child is not the PhysicalEmptyRelation and requiresTransaction() is true -- the load does begin and commit an (empty) transaction rather than returning through the no-transaction path. The empty-plan shape reproduces on an unpartitioned target, which is what the new suite case uses (INSERT OVERWRITE TABLE flat_dst SELECT ... WHERE 1 = 0). The branch is kept so both routes take the same decision.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, and my earlier reply's premise was wrong. I re-probed with the session variable set: with enable_strict_consistency_dml = false, INSERT OVERWRITE TABLE t PARTITION(*) SELECT ... WHERE 1 = 0 plans 0:VEMPTYSET as the OLAP TABLE SINK's direct child -- no exchange -- so that shape does take the insert's no-transaction path and the marker stays false for it.

Fixed on this head: both routes now publish through one place. publishTheOverwrite takes the publication as an action, so the explicit-partition swap and taskGroupSuccess share the same last-chance decision under the target table's write lock (and the same failure for a target dropped under the swap). A cancellation landing there with nothing committed now fails the statement instead of being acknowledged.

For the record, what that residual was in this shape: the group has no registered pairs, so taskGroupSuccess reaches the utility with two empty lists and returns without replacing anything -- no rows were destroyed, the problem was the false success. The suite drives it: SET enable_strict_consistency_dml = false, arm the lock-wait point on a partitioned target, INSERT OVERWRITE TABLE dst PARTITION(*) SELECT ... WHERE 1 = 0 has to fail with cancelled while the swap waited for the table lock and the table has to keep its rows (dst_after_the_cancelled_auto_detect_swap).

LOG.info("insert overwrite is cancelled before replacePartition, queryId: {}",
ctx.getQueryIdentifier());
insertOverwriteManager.taskFail(taskId);
return;

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 cancellation when the inner insert has no transaction. INSERT OVERWRITE dst SELECT ... LIMIT 0 becomes a PhysicalEmptyRelation; InsertIntoTableCommand.runInternal returns at !requiresTransaction() without committing or publishing a coordinator. If KILL lands after the earlier check (or this after-insert debug point fires), isCancelled is true here, yet the command swaps the empty temporary partitions over dst and reports success. This deletes the prior rows even though cancellation occurred before any insert commit. Fail and roll back this no-transaction case; complete the swap only after a committed insert.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The insert now tells its caller that nothing was committed: InsertIntoTableCommand.runInternal marks the context at the !requiresTransaction() return (InsertCommandContext#setCommittedNothing), the overwrite reads that context back from insertIntoPartitions, and the window check fails the statement -- rolling the empty temporary partitions back through the existing catch -- instead of completing the swap.

Verified end to end on an unpartitioned empty-plan overwrite: with the cancellation injected in the window, INSERT OVERWRITE TABLE dst SELECT ... WHERE 1 = 0 now returns insert overwrite is cancelled after an insert that committed nothing and the target keeps its rows. Before the change the statement succeeded and the table was emptied (FE log cancelled after its rows were committed, completing it, count(*) 1 -> 0). The new suite case pins it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on this head, and you are right that the interval matters: the swap waits for the target table's write lock, and that wait lasts as long as whoever holds it, so a cancellation landing in it is not a microsecond race.

The flag is now read once more under that lock, immediately before the swap, and when a cancellation found nothing committed the overwrite refuses to swap: the temporary partitions are rolled back by the existing catch, the statement fails, and the table keeps its rows. Where the rows are committed the cancellation is still too late and the swap runs, which is the same boundary as everywhere else -- the check only changes the outcome for a cancellation with nothing durable to publish. Only a local table is wrapped (a remote table swaps on the frontend that owns it, where this lock says nothing), and the lock is reentrant, so the util's own acquisition inside it is free.

A debug point (InsertOverwriteTableCommand.cancelWhileTheSwapWaitsForTheTableLock) injects the cancellation at exactly that point, and the suite's dst_after_the_cancelled_locked_swap case asserts the statement fails with cancelled while the swap waited for the table lock and that the table still holds the row it had.

*/
private void cancelTheOverwriteAt(String stage, TableIf targetTable) {
if (!stage.equals(DebugPointUtil.getDebugParamOrDefault(
DEBUG_POINT_CANCEL_AN_OVERWRITE, "stage", ""))) {

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] Make one-shot cancellation points usable at both stages. getDebugParamOrDefault consumes the point's execute budget on every lookup. With execute=1 for beforeTheInsert, the stage read uses the one allowance and this table_name read expires the point; for afterTheInsert, the earlier nonmatching before-stage check consumes it. Both silently skip cancel(). Read both parameters from one point instance and avoid consuming a stage's one-shot budget at the other stage (for example, use stage-specific point names).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: two point names instead of one, and one lookup per site. cancelBeforeTheInsertOfAnOverwrite and cancelBetweenTheTwoHalvesOfAnOverwrite each read their single table_name parameter from one getDebugPoint call, so no site can spend the other's execute allowance; the stage parameter and the second lookup are gone. The comment on the constants records why a shared name was wrong.

// partitions, and the swap is what publishes them. The statement reports the success it now is, and the
// rows it read are the ones the table holds.
try {
GetDebugPoint().enableDebugPointForAllFEs(cancelPoint,

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] Make the post-insert cancellation case observable. If the afterTheInsert call is removed or its stage stops matching, this SQL still succeeds and the final query still returns rows 3 and 4, so the suite passes without exercising the behavior it was added to protect. Assert that this stage actually cancelled the command (and preferably exercise a real StmtExecutor.cancel/stream-offset case) before treating the row result as a regression guard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by making the window's injection observable, in the way the earlier failure-half suite does it. Alongside the case that asserts the rows are published, the same injection point now drives an empty-plan overwrite (... WHERE 1 = 0) where the cancelled statement has to fail: that assertion holds only if the injection reached the window, so deleting the call, renaming the point, or breaking its lookup makes the suite fail. The empty-plan case also pins its own behaviour (the table has to keep its rows, so the swap must not have run) rather than passing on the rows either way.

A real StmtExecutor.cancel is not used for the same reason the failure half is injected: the window is a couple of metadata operations wide, so a KILL would have to land inside it by luck. The stream-offset variant of this scenario is the one that matters for IVM refreshes; it needs the crash half of the window (swap as a committed action of the insert transaction) to be worth testing end to end.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17667	3883	3901	3883
q2	2142	341	335	335
q3	10080	1385	782	782
q4	4687	475	359	359
q5	7459	816	551	551
q6	177	171	136	136
q7	732	773	604	604
q8	9323	1452	1467	1452
q9	5420	4162	4154	4154
q10	6926	1320	1003	1003
q11	422	276	251	251
q12	628	418	308	308
q13	18058	2602	1987	1987
q14	252	252	233	233
q15	q16	733	715	658	658
q17	1876	1122	1045	1045
q18	6345	5581	5564	5564
q19	1173	1128	1037	1037
q20	470	386	258	258
q21	5416	2995	2952	2952
q22	439	352	301	301
Total cold run time: 100425 ms
Total hot run time: 27853 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4536	4436	4509	4436
q2	714	585	517	517
q3	4755	5241	4584	4584
q4	2227	2318	1441	1441
q5	4586	4483	4404	4404
q6	229	171	124	124
q7	1932	1820	1478	1478
q8	2323	2026	1993	1993
q9	7502	7139	6863	6863
q10	3612	3550	3069	3069
q11	536	371	335	335
q12	696	702	496	496
q13	2276	2590	1967	1967
q14	268	271	249	249
q15	q16	665	682	597	597
q17	7257	6677	6603	6603
q18	11874	11070	11676	11070
q19	1100	997	988	988
q20	2196	2163	1908	1908
q21	4889	4052	4247	4052
q22	535	460	385	385
Total cold run time: 64708 ms
Total hot run time: 57559 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152313 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 85c4ae452e17feccaa9b297fdf2a2a11a69d5b9a, data reload: false

query5	4323	597	457	457
query6	442	209	193	193
query7	4822	567	298	298
query8	325	174	159	159
query9	8789	3996	4012	3996
query10	437	305	249	249
query11	5849	3527	3216	3216
query12	144	91	83	83
query13	1271	536	424	424
query14	6523	4530	4219	4219
query14_1	4015	4020	3937	3937
query15	203	192	176	176
query16	1036	486	425	425
query17	905	686	524	524
query18	2423	456	326	326
query19	192	169	136	136
query20	83	78	74	74
query21	217	131	113	113
query22	13220	13093	12870	12870
query23	13935	12811	12373	12373
query23_1	12523	12425	12546	12425
query24	7264	1166	682	682
query24_1	644	697	726	697
query25	527	414	351	351
query26	1263	298	159	159
query27	2691	544	332	332
query28	4582	1987	2002	1987
query29	1607	710	506	506
query30	280	217	184	184
query31	884	753	645	645
query32	141	93	92	92
query33	517	308	228	228
query34	1193	1137	631	631
query35	710	741	639	639
query36	810	779	700	700
query37	140	119	89	89
query38	1822	1753	1686	1686
query39	666	709	666	666
query39_1	661	667	666	666
query40	224	130	106	106
query41	73	67	67	67
query42	100	98	97	97
query43	337	350	294	294
query44	1422	721	723	721
query45	189	181	162	162
query46	1064	1195	738	738
query47	1490	1487	1404	1404
query48	423	423	303	303
query49	597	395	299	299
query50	953	343	252	252
query51	10660	10270	10226	10226
query52	90	91	78	78
query53	237	258	191	191
query54	259	223	193	193
query55	84	76	70	70
query56	236	236	223	223
query57	1513	1467	1351	1351
query58	292	269	263	263
query59	1970	2061	1865	1865
query60	298	251	235	235
query61	170	161	170	161
query62	408	322	267	267
query63	218	174	184	174
query64	2924	1073	929	929
query65	3475	3385	3406	3385
query66	1786	437	311	311
query67	19968	19799	19771	19771
query68	3109	1504	898	898
query69	407	303	266	266
query70	891	827	819	819
query71	295	224	217	217
query72	2616	2535	2223	2223
query73	811	799	442	442
query74	4645	4473	4298	4298
query75	2308	2267	1917	1917
query76	2310	1087	747	747
query77	361	401	284	284
query78	9158	9154	8518	8518
query79	1340	1200	742	742
query80	601	458	372	372
query81	549	320	278	278
query82	634	154	131	131
query83	301	232	196	196
query84	317	150	114	114
query85	841	472	383	383
query86	334	244	231	231
query87	2000	1953	1820	1820
query88	3760	2814	2772	2772
query89	366	292	249	249
query90	1924	195	181	181
query91	173	155	138	138
query92	96	87	89	87
query93	1466	1392	866	866
query94	532	338	299	299
query95	667	464	334	334
query96	1060	800	376	376
query97	2432	2420	2274	2274
query98	156	152	152	152
query99	710	725	617	617
Total cold run time: 236358 ms
Total hot run time: 152313 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.82 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 85c4ae452e17feccaa9b297fdf2a2a11a69d5b9a, 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.15	0.97	0.90
query7	0.04	0.01	0.01
query8	0.06	0.04	0.03
query9	0.39	0.33	0.33
query10	0.57	0.56	0.54
query11	0.20	0.15	0.14
query12	0.18	0.15	0.15
query13	0.45	0.47	0.46
query14	0.96	0.96	0.94
query15	0.59	0.58	0.59
query16	0.31	0.32	0.34
query17	1.10	1.14	1.12
query18	0.21	0.20	0.20
query19	2.07	1.91	1.87
query20	0.02	0.01	0.01
query21	15.49	0.21	0.14
query22	4.92	0.06	0.05
query23	16.15	0.31	0.12
query24	2.95	0.41	0.30
query25	0.11	0.04	0.05
query26	0.72	0.20	0.15
query27	0.05	0.04	0.04
query28	3.59	0.80	0.34
query29	12.49	4.00	3.17
query30	0.28	0.15	0.16
query31	2.77	0.56	0.30
query32	3.22	0.58	0.49
query33	3.30	3.15	3.23
query34	15.65	3.89	3.25
query35	3.22	3.21	3.22
query36	0.55	0.45	0.42
query37	0.08	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.15	0.15
query41	0.10	0.03	0.02
query42	0.04	0.02	0.02
query43	0.04	0.04	0.03
Total cold run time: 96.51 s
Total hot run time: 23.82 s

The window between an overwrite's two halves treated every cancellation that landed
in it as too late, and completed the swap. That is right where the insert committed
the rows it read, but an insert whose plan folded to an empty relation takes the
path that begins no transaction at all: it commits nothing, so completing the swap
published an empty table for a statement the client had cancelled, and reported
success for it. The auto-detect route left the same boundary unhandled, and the two
cancel points shared one debug point and two lookups, which let a point armed with
execute=1 be spent by the other site's lookup and silently not fire.

Key changes:
- The insert marks the context it ran under when it takes the path that begins no
  transaction, and the overwrite reads that back: a cancellation landing in the
  window now fails the statement and drops the empty temporary partitions, while
  one that lands after committed rows still completes the swap.
- The auto-detect route takes the same decision.
- One debug point per cancel site, each reading its single table_name parameter
  from one lookup.
- The suite covers the empty-plan window as well: the cancelled statement has to
  fail and the table has to keep its rows, which also pins that the injection
  reached the window.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy
- insert_overwrite_p0 (14 suites) and
  mtmv_p0/ivm/test_ivm_overwrite_failure_between_the_halves pass.
@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.

Review outcome

Complete static review of PR #68662 at 215bcf07bd59d9981ed5549ee53b61e39083b232. Request changes for one new P1 inline finding: a committed insert can return a visibility-timeout error, bypass the new cancellation decision, and have its temporary partitions dropped. The earlier P1 thread 4140574895 is fixed on this head by the no-transaction context flag and failure path, so there are no still-applicable existing P0/P1 inline findings. The existing P2 test-observability thread is not duplicated. There was no additional user-provided focus.

Critical checkpoints

  • Goal and proof: Before-commit cancellation now fails; a normal committed insert completes the partition swap. The goal is incomplete for the committed-but-error visibility-timeout path in the inline finding. The new suite exercises before-insert cancellation, a rowful post-insert swap, and a cancelled empty-plan overwrite, but not that error path.
  • Scope and clarity: The changes are localized to the insert context, the inner and outer commands, and one regression suite with its expected output. The new hasCommittedNothing condition is appropriate for the no-transaction fast path, but an error response cannot establish that the inner insert was uncommitted.
  • Concurrency and locks: KILL runs on another FE thread. StmtExecutor.cancel sets the outer atomic cancellation flag and retains a pending coordinator reason; the execution thread publishes the coordinator and checks its status before commit. The added checks take no new locks or heavy work inside locks. No new lock-order or shared-container issue was found.
  • Lifecycle: Each inner insert has a fresh context; task registration and cleanup remain paired by the outer catch/finally path. The new early failure skips task cleanup only when no task was registered. No new static initialization or ownership cycle was found.
  • Configuration and compatibility: No new configuration, FE/BE transmitted variable, protocol, function symbol, or storage format is added. The existing insert_visible_timeout_return_mode=error setting exposes the P1 path. No rolling-upgrade change is required by these hunks.
  • Parallel paths and conditions: Explicit/whole-table and PARTITION(*) routes both use the new context; the latter shares the P1 cause through taskGroupFail. Remote OLAP and supported plugin overwrite paths yielded no separate substantiated defect. Separate one-lookup debug points address the earlier one-shot thread.
  • Tests and expected results: The .out rows match the seeded rows and ordered queries. Expected errors use test { sql; exception }, and table setup and debug-point cleanup follow the suite conventions. The rowful case alone can pass without cancellation, a limitation already raised in thread 4140574902; a real KILL, stream-offset case, and the visibility-timeout error path are not covered here. No build or test was run under the review instructions; the author's reported runs and output-generation provenance were not independently verified.
  • Observability and performance: Cancellation/error logs include query IDs; no additional metric or material hot-path cost was found. The production additions are a small context flag and guarded debug-point lookups.
  • Transactions, persistence, and writes: The task manager retains its existing edit-log and cleanup lifecycle. Ordinary postcommit cancellation swaps durable rows and their consumed offsets, but the P1 path drops committed temporary partitions while offsets may advance. The pre-existing crash, master-failover, and metadata-error gap between commit and swap is outside this patch and was not treated as newly fixed; this static review does not claim runtime or failover validation.

Two review rounds converged: both full-review passes and the separate risk-focused pass returned NO_NEW_VALUABLE_FINDINGS in round two. The final sweep covered all five changed files and every candidate; only the P1 below remains for a new inline comment.

throw cancelledBeforeTheRowsWereCommitted("before insertInto", ctx);
}
// todo: need to refresh remote target table after add temp partitions
insertIntoPartitions(ctx, executor, tempPartitionNames, wholeTable);

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] Complete the swap after a committed visibility-timeout error. With insert_visible_timeout_return_mode=error, OlapInsertExecutor.onComplete can commit rows into the temporary partitions and then setReturnInfo sets the connection state to ERR when publication times out. runInsertCommand throws on that ERR before this new post-insert cancellation decision runs; the outer catch calls taskFail (or taskGroupFail for PARTITION(*)), which drops the committed temporary partitions. A KILL during that postcommit wait still loses the rows and any committed stream offsets the change is meant to preserve. Carry the committed outcome separately from the response state and finish the swap for this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, on the same boundary the previous commit drew. The insert context now records that its transaction committed (set by each executor where its own commit happens -- OLAP, remote OLAP, external/plugin -- via AbstractInsertExecutor#markCommitted), separately from the response state the insert leaves behind. The overwrite reads it in both places it decides:

  • the window's cancellation is honoured only when nothing was committed;
  • runInsertCommand no longer stops the overwrite on an error state whose rows are committed: the swap publishes them, and the client keeps the error the session asked for (insert_visible_timeout_return_mode=error), which is a statement about visibility rather than about whether the overwrite ran.

Verified with the publish daemon blocked (PublishVersionDaemon.stop_publish, 10s) and insert_visible_timeout_ms=1000: the statement returns the visibility-timeout error, the FE log records insert overwrite continues over an error state whose rows are committed, and the row the overwrite read becomes visible once publish resumes. The suite's new dst_after_the_publish_timeout case asserts exactly that -- with the previous code the table keeps its old rows and the wait fails.

One boundary worth naming, since the response stays the error the session asked for: a client that retries the overwrite after that error gets a partial overwrite, because the re-run reads from the offset the first attempt advanced. That is the same contract insert_visible_timeout_return_mode=error has for a plain INSERT, and making the retry safe belongs to the durable fix (the swap as a committed action of the insert transaction) rather than to this PR.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17826	3981	3942	3942
q2	2189	363	319	319
q3	10066	1371	784	784
q4	4687	478	344	344
q5	7471	829	566	566
q6	178	172	132	132
q7	743	770	603	603
q8	9303	1515	1483	1483
q9	5439	4182	4173	4173
q10	6816	1356	1010	1010
q11	434	264	237	237
q12	641	406	294	294
q13	18042	2612	1993	1993
q14	259	250	237	237
q15	q16	727	721	671	671
q17	1728	1127	1004	1004
q18	6522	5608	5528	5528
q19	1356	1226	1027	1027
q20	490	385	254	254
q21	5762	3407	3043	3043
q22	455	380	305	305
Total cold run time: 101134 ms
Total hot run time: 27949 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4647	4696	4388	4388
q2	749	586	541	541
q3	4964	5181	4613	4613
q4	2190	2327	1470	1470
q5	4576	4587	4384	4384
q6	220	174	127	127
q7	1823	1703	1475	1475
q8	2389	2021	1995	1995
q9	7248	7337	7161	7161
q10	3592	3556	3079	3079
q11	510	372	338	338
q12	705	701	497	497
q13	2267	2570	1983	1983
q14	272	277	245	245
q15	q16	672	679	604	604
q17	7259	6701	6728	6701
q18	11824	11029	11653	11029
q19	1085	1019	975	975
q20	2207	2191	1890	1890
q21	4971	4046	4285	4046
q22	525	461	389	389
Total cold run time: 64695 ms
Total hot run time: 57930 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152296 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 215bcf07bd59d9981ed5549ee53b61e39083b232, data reload: false

query5	4312	589	448	448
query6	428	222	193	193
query7	4901	553	278	278
query8	328	174	168	168
query9	8809	3945	3938	3938
query10	459	309	277	277
query11	5906	3538	3227	3227
query12	146	89	86	86
query13	1257	607	409	409
query14	6514	4508	4236	4236
query14_1	3938	3932	3946	3932
query15	203	204	183	183
query16	984	485	428	428
query17	906	667	538	538
query18	2428	477	347	347
query19	207	180	144	144
query20	83	82	83	82
query21	227	144	115	115
query22	13024	13058	12711	12711
query23	14009	12996	12600	12600
query23_1	12766	12635	12565	12565
query24	7139	1119	641	641
query24_1	670	678	708	678
query25	562	433	369	369
query26	1253	320	173	173
query27	2667	556	339	339
query28	4565	1955	1935	1935
query29	1662	729	523	523
query30	310	213	190	190
query31	892	757	639	639
query32	144	94	98	94
query33	517	319	259	259
query34	1150	1094	651	651
query35	734	754	664	664
query36	801	817	737	737
query37	142	104	99	99
query38	1824	1768	1757	1757
query39	711	710	646	646
query39_1	648	644	653	644
query40	220	125	99	99
query41	75	69	69	69
query42	99	93	91	91
query43	335	338	295	295
query44	1378	711	708	708
query45	189	182	169	169
query46	1052	1169	717	717
query47	1499	1535	1427	1427
query48	384	393	280	280
query49	587	389	289	289
query50	963	342	265	265
query51	10583	10290	10424	10290
query52	83	88	75	75
query53	236	247	180	180
query54	250	200	193	193
query55	79	73	67	67
query56	222	203	198	198
query57	1318	1447	1407	1407
query58	271	262	256	256
query59	1953	2039	1855	1855
query60	282	222	225	222
query61	143	151	141	141
query62	383	314	273	273
query63	217	176	169	169
query64	2800	1057	816	816
query65	3478	3394	3441	3394
query66	1801	415	296	296
query67	19896	19758	20013	19758
query68	3348	1432	925	925
query69	397	302	252	252
query70	891	810	795	795
query71	289	226	208	208
query72	2675	2524	2191	2191
query73	847	758	389	389
query74	4602	4499	4301	4301
query75	2284	2289	1926	1926
query76	2325	1082	745	745
query77	361	391	298	298
query78	9008	9175	8470	8470
query79	1394	1269	762	762
query80	581	467	360	360
query81	562	318	276	276
query82	645	161	127	127
query83	311	218	190	190
query84	330	144	115	115
query85	900	487	381	381
query86	325	240	231	231
query87	1992	1965	1814	1814
query88	3613	2762	2701	2701
query89	367	286	243	243
query90	1917	178	177	177
query91	167	155	129	129
query92	103	88	85	85
query93	1497	1475	833	833
query94	523	361	303	303
query95	666	382	327	327
query96	1088	756	356	356
query97	2436	2427	2312	2312
query98	159	150	143	143
query99	717	726	626	626
Total cold run time: 236507 ms
Total hot run time: 152296 ms

@hello-stephen

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

query1	0.00	0.01	0.01
query2	0.09	0.04	0.05
query3	0.25	0.14	0.13
query4	1.61	0.14	0.14
query5	0.24	0.22	0.21
query6	1.15	0.91	0.90
query7	0.04	0.00	0.00
query8	0.06	0.04	0.04
query9	0.39	0.34	0.33
query10	0.55	0.57	0.54
query11	0.20	0.14	0.14
query12	0.18	0.15	0.14
query13	0.47	0.45	0.46
query14	0.96	0.94	0.94
query15	0.60	0.58	0.58
query16	0.32	0.32	0.31
query17	1.04	1.03	1.07
query18	0.21	0.20	0.20
query19	2.03	1.84	1.95
query20	0.02	0.01	0.01
query21	15.49	0.21	0.13
query22	4.91	0.05	0.05
query23	16.17	0.32	0.12
query24	2.91	0.41	0.31
query25	0.09	0.05	0.04
query26	0.74	0.20	0.14
query27	0.05	0.04	0.04
query28	3.55	0.83	0.36
query29	12.50	4.06	3.17
query30	0.28	0.16	0.16
query31	2.78	0.57	0.33
query32	3.23	0.59	0.50
query33	3.20	3.20	3.15
query34	15.58	3.92	3.26
query35	3.18	3.20	3.20
query36	0.57	0.43	0.42
query37	0.09	0.07	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.14	0.14
query41	0.09	0.03	0.02
query42	0.03	0.02	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.16 s
Total hot run time: 23.71 s

…an error

The overwrite stopped at any error state the insert left behind, and dropped the
temporary partitions it had written. A load whose publication times out after its
commit is committed, though, and the session's visibility-timeout mode
(insert_visible_timeout_return_mode=error) reports that timeout as an error: the rows
existed, the response said otherwise, and the overwrite discarded them together with
the stream offsets the read had consumed. The question that decides a cancellation in
the same window -- did the insert commit -- answers this one as well.

Key changes:
- The insert context records that the transaction committed, set by each executor
  where its own commit happens (OLAP, remote OLAP, external/plugin). That is a
  different question from the response state the insert leaves behind.
- The overwrite reads it wherever it decides: a cancellation landing in the window is
  honoured only when nothing was committed, and an error state no longer stops the
  swap when the rows are durable -- the swap publishes them, and the client keeps the
  error the session asked for.
- The suite reaches the publication timeout by blocking the publish daemon, as
  test_insert_visible_timeout_return_mode does, and asserts that the rows the error
  reported as committed become visible once publish resumes.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy
- insert_overwrite_p0 (14 suites), insert_p0/test_insert_visible_timeout_return_mode
  and mtmv_p0/ivm/test_ivm_overwrite_failure_between_the_halves pass.
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17882	3978	3892	3892
q2	2210	371	298	298
q3	10072	1334	785	785
q4	4684	472	352	352
q5	7499	835	549	549
q6	172	164	134	134
q7	740	775	603	603
q8	9305	1510	1469	1469
q9	5460	4164	4144	4144
q10	6812	1320	1025	1025
q11	449	278	242	242
q12	627	422	288	288
q13	18041	2618	1971	1971
q14	257	248	228	228
q15	q16	721	715	656	656
q17	1754	1182	1041	1041
q18	6432	5574	5524	5524
q19	1190	1159	991	991
q20	485	376	251	251
q21	5514	2950	2959	2950
q22	425	360	308	308
Total cold run time: 100731 ms
Total hot run time: 27701 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4545	4416	4469	4416
q2	715	563	526	526
q3	4727	5347	4561	4561
q4	2161	2287	1472	1472
q5	4576	4456	4409	4409
q6	229	174	129	129
q7	1845	1727	1487	1487
q8	2268	1955	1997	1955
q9	7316	7075	6784	6784
q10	3619	3539	3054	3054
q11	519	377	345	345
q12	713	712	505	505
q13	2271	2581	1987	1987
q14	277	266	246	246
q15	q16	672	683	601	601
q17	7230	6677	6567	6567
q18	11881	11007	11730	11007
q19	1116	982	999	982
q20	2208	2170	1909	1909
q21	4941	4098	4252	4098
q22	507	443	404	404
Total cold run time: 64336 ms
Total hot run time: 57444 ms

@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 complete for PR #68662 at 83bac904ca8555873d587f328c33d9e3eb50f697 after two full review rounds and a final nine-file sweep. One new P2 inline finding concerns the cloud regression case. Existing P1 comment 4140574895 still applies to a narrower late-cancellation window; I confirmed it and have not reposted it. The prior P1 visibility-timeout concern is addressed on this head. There was no additional user-provided review focus.

Goal and data correctness. The request-local committed flag is set after the reachable local OLAP, remote OLAP, and external connector commits. The local publication-timeout path now keeps committed temporary partitions for the swap while retaining the configured error response. A no-transaction empty insert leaves the flag false, and the explicit and PARTITION(*) branches check it. The outstanding existing P1 remains: if KILL arrives after the explicit branch's last no-commit check while partition replacement waits for the table write lock, the empty partitions can still replace the original rows and the statement can report success.

Concurrency, lifecycle, and transaction checkpoints. StmtExecutor.cancel sets the outer command's atomic cancellation flag from another thread; the insert and overwrite run on the statement thread. No new shared lock, lock ordering, static lifetime, FE/BE field, configuration item, wire format, or persisted format was added. Task and task-group failure paths roll back registered temporary partitions before commit; committed rows proceed to replacement. The commit marker is request-local, while transaction and stream-offset persistence remains in the existing transaction managers. The swap remains a separate metadata action, so preexisting crash, swap-error, and uncertain commit-response windows are unchanged; no distinct new issue in those paths was substantiated.

Parallel paths, tests, and operational impact. Executor selection excludes transaction-model overwrite and ordinary group commit; the reviewed local, remote, external, explicit-partition, and auto-detect paths use the applicable marker. The added debug points are separately named and the empty-plan error assertion observes its injection. The .out rows agree with the fixture's ordered queries. The unconditional publish-timeout assertion cannot pass in cloud mode, where commit has no FE publication wait; that is the new P2 inline finding. Existing logs identify the committed cancellation and error-state paths, and the added checks have constant cost. This review was static only: the review contract prohibited builds and tests, and author or CI results were not treated as independent runtime validation.

Existing P0/P1 findings confirmed for this head: #68662 (comment)

GetDebugPoint().enableDebugPointForAllFEs(stopPublishPoint, [timeout: "10"])
sql """SET insert_visible_timeout_ms = 1000"""
sql """SET insert_visible_timeout_return_mode = 'error'"""
test {

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] Skip this publish-timeout case in cloud mode. CloudGlobalTransactionMgr.commitAndPublishTransaction returns true after commit because cloud mode has no FE publish phase, so OlapInsertExecutor reports success even when PublishVersionDaemon.stop_publish is armed. This unconditional test { exception ... } fails when the suite runs in cloud mode. Guard the timeout case and its final publish assertion with !isCloudMode() (as test_insert_visible_timeout_return_mode does); keep the cancellation cases active in cloud mode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guarded. The publication-timeout case is now inside if (!isCloudMode()), with the reason recorded in a comment (cloud's commit has no FE publication wait to time out, since CloudGlobalTransactionMgr commits straight into the meta service), and the final publish assertion moved inside it. The cancellation cases stay unconditional, so cloud still runs those.

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152411 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 83bac904ca8555873d587f328c33d9e3eb50f697, data reload: false

query5	4313	615	453	453
query6	429	205	187	187
query7	4803	514	302	302
query8	315	171	176	171
query9	8829	3995	3986	3986
query10	460	307	245	245
query11	5746	3555	3223	3223
query12	153	92	82	82
query13	1263	585	424	424
query14	6493	4496	4211	4211
query14_1	4012	3928	3948	3928
query15	206	192	180	180
query16	972	451	382	382
query17	898	668	557	557
query18	2421	442	337	337
query19	214	180	150	150
query20	94	80	79	79
query21	222	134	112	112
query22	13085	13008	12756	12756
query23	13923	12982	12481	12481
query23_1	12507	12497	12572	12497
query24	7286	1109	683	683
query24_1	683	697	737	697
query25	567	441	376	376
query26	1277	313	171	171
query27	2661	588	350	350
query28	4528	2027	2022	2022
query29	1639	745	536	536
query30	295	214	182	182
query31	898	763	636	636
query32	146	99	105	99
query33	518	314	262	262
query34	1176	1135	636	636
query35	710	743	641	641
query36	808	790	680	680
query37	146	107	91	91
query38	1832	1744	1721	1721
query39	677	686	653	653
query39_1	652	673	657	657
query40	225	127	107	107
query41	76	68	67	67
query42	106	94	101	94
query43	335	342	298	298
query44	1420	726	730	726
query45	183	178	164	164
query46	1116	1242	737	737
query47	1479	1495	1407	1407
query48	405	406	306	306
query49	595	419	291	291
query50	1012	340	241	241
query51	10324	10329	10211	10211
query52	83	88	79	79
query53	238	253	182	182
query54	264	216	206	206
query55	80	74	69	69
query56	239	217	207	207
query57	1543	1371	1362	1362
query58	281	258	249	249
query59	1988	2063	1848	1848
query60	278	241	230	230
query61	144	149	150	149
query62	395	314	263	263
query63	214	175	175	175
query64	2773	990	807	807
query65	3495	3421	3405	3405
query66	1811	407	305	305
query67	20061	19834	19735	19735
query68	3158	1458	812	812
query69	415	293	257	257
query70	892	843	795	795
query71	325	262	210	210
query72	2631	2543	2317	2317
query73	812	810	447	447
query74	4686	4487	4314	4314
query75	2362	2304	1930	1930
query76	2289	1120	775	775
query77	378	400	299	299
query78	8919	8974	8424	8424
query79	1390	1282	791	791
query80	726	455	375	375
query81	549	319	283	283
query82	634	164	127	127
query83	302	243	204	204
query84	312	151	111	111
query85	867	468	392	392
query86	360	237	233	233
query87	1966	1939	1823	1823
query88	3724	2787	2763	2763
query89	374	288	248	248
query90	1814	185	184	184
query91	172	162	134	134
query92	104	91	89	89
query93	1489	1551	914	914
query94	574	317	314	314
query95	661	387	408	387
query96	1031	911	373	373
query97	2434	2419	2315	2315
query98	157	149	143	143
query99	725	728	611	611
Total cold run time: 235838 ms
Total hot run time: 152411 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.06	0.05
query3	0.27	0.13	0.14
query4	1.61	0.14	0.14
query5	0.25	0.22	0.22
query6	1.16	0.93	0.94
query7	0.04	0.01	0.00
query8	0.05	0.04	0.04
query9	0.38	0.33	0.34
query10	0.56	0.55	0.54
query11	0.21	0.15	0.14
query12	0.18	0.15	0.14
query13	0.46	0.48	0.47
query14	0.95	0.95	0.94
query15	0.60	0.58	0.60
query16	0.33	0.32	0.33
query17	1.04	1.09	1.08
query18	0.21	0.19	0.20
query19	1.98	1.88	1.88
query20	0.02	0.01	0.01
query21	15.48	0.22	0.13
query22	4.79	0.06	0.05
query23	16.13	0.30	0.13
query24	2.98	0.42	0.35
query25	0.12	0.05	0.04
query26	0.73	0.20	0.14
query27	0.04	0.04	0.03
query28	3.56	0.75	0.35
query29	12.47	4.00	3.17
query30	0.28	0.15	0.15
query31	2.77	0.56	0.30
query32	3.22	0.61	0.49
query33	3.09	3.24	3.22
query34	15.81	3.99	3.27
query35	3.21	3.19	3.22
query36	0.55	0.44	0.41
query37	0.09	0.07	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.18	0.16	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.02
query43	0.04	0.03	0.04
Total cold run time: 96.15 s
Total hot run time: 23.89 s

…for the lock

The last read of the cancellation flag happens before the swap is issued, and the swap
then waits for the target table's write lock -- a wait that lasts as long as whoever
holds it. A cancellation that found nothing committed could therefore still be followed
by a swap publishing empty temporary partitions over the rows the client asked to keep.

Key changes:
- The flag is read once more under that lock, where the wait happens, and the swap is
  refused when a cancellation found nothing committed -- there is nothing durable to
  publish, so refusing costs the statement and leaves the rows. Only a local table is
  wrapped: a remote table swaps on the frontend that owns it.
- A debug point injects the cancellation there, and the suite asserts the table keeps
  its rows.
- The publication-timeout case is skipped in cloud mode, where the commit has no FE
  publication wait to time out, as test_insert_visible_timeout_return_mode does. The
  cancellation cases stay active there.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy
- insert_overwrite_p0 (14 suites), insert_p0/test_insert_visible_timeout_return_mode
  and mtmv_p0/ivm/test_ivm_overwrite_failure_between_the_halves pass.
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

The comment on the window's "too late to cancel" branch still described the insert as
having published the rows by the time it returns. Since the publication-timeout case is
handled there too, the rows can be committed with their publication still pending, which
is what the comment now says.
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review recovery stopped: No final review submission was declared
Workflow run: https://github.com/apache/doris/actions/runs/36683545173

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17802	3921	3909	3909
q2	2171	370	301	301
q3	10060	1349	790	790
q4	4684	477	345	345
q5	7463	814	543	543
q6	173	167	136	136
q7	722	774	584	584
q8	9298	1456	1518	1456
q9	5363	4182	4141	4141
q10	6834	1315	1029	1029
q11	441	264	236	236
q12	634	407	300	300
q13	18046	2588	1995	1995
q14	258	254	229	229
q15	q16	733	709	662	662
q17	1648	1135	988	988
q18	6452	5583	5539	5539
q19	1194	1160	1003	1003
q20	485	382	275	275
q21	5487	2954	2996	2954
q22	434	368	302	302
Total cold run time: 100382 ms
Total hot run time: 27717 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4584	4453	4504	4453
q2	722	544	525	525
q3	4701	5291	4563	4563
q4	2261	2293	1437	1437
q5	4627	4399	4492	4399
q6	231	191	132	132
q7	1841	1710	1519	1519
q8	2264	2054	1966	1966
q9	7266	6751	6750	6750
q10	3619	3534	3062	3062
q11	509	368	337	337
q12	703	705	498	498
q13	2261	2576	1980	1980
q14	263	276	267	267
q15	q16	653	694	599	599
q17	7285	6657	6648	6648
q18	11869	11036	11717	11036
q19	1109	988	984	984
q20	2197	2204	1904	1904
q21	4916	4036	4227	4036
q22	497	449	389	389
Total cold run time: 64378 ms
Total hot run time: 57484 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152404 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 5eee943f3d313befe4c815a58178ea4f0e7610b6, data reload: false

query5	4341	622	483	483
query6	437	212	196	196
query7	4880	541	296	296
query8	335	173	160	160
query9	8794	4038	4032	4032
query10	457	315	254	254
query11	5836	3530	3235	3235
query12	147	92	89	89
query13	1277	617	420	420
query14	6507	4486	4223	4223
query14_1	3971	4016	3912	3912
query15	199	198	184	184
query16	1010	492	443	443
query17	902	656	534	534
query18	2466	488	345	345
query19	208	177	144	144
query20	90	91	78	78
query21	221	134	116	116
query22	12982	13005	12771	12771
query23	14008	13052	12332	12332
query23_1	12482	12514	12486	12486
query24	7339	1184	724	724
query24_1	666	684	686	684
query25	557	441	373	373
query26	1262	302	169	169
query27	2692	571	354	354
query28	4525	1976	1963	1963
query29	1579	713	533	533
query30	302	220	192	192
query31	890	758	644	644
query32	164	93	92	92
query33	537	318	254	254
query34	1196	1117	630	630
query35	724	741	648	648
query36	813	793	721	721
query37	140	110	102	102
query38	1839	1767	1689	1689
query39	693	709	675	675
query39_1	650	655	660	655
query40	231	127	105	105
query41	74	71	69	69
query42	103	97	96	96
query43	336	342	304	304
query44	1367	714	720	714
query45	184	191	167	167
query46	1042	1182	736	736
query47	1507	1505	1409	1409
query48	415	430	288	288
query49	576	399	310	310
query50	937	332	261	261
query51	10598	10445	10275	10275
query52	96	85	73	73
query53	244	243	184	184
query54	267	207	186	186
query55	84	76	70	70
query56	236	226	209	209
query57	1411	1409	1341	1341
query58	281	257	247	247
query59	2001	2091	1874	1874
query60	285	260	223	223
query61	146	156	146	146
query62	399	314	264	264
query63	232	180	175	175
query64	2850	1065	813	813
query65	3538	3452	3484	3452
query66	1821	427	315	315
query67	20167	20269	19962	19962
query68	3331	1528	896	896
query69	414	301	263	263
query70	893	827	837	827
query71	311	236	209	209
query72	2691	2705	2181	2181
query73	818	830	418	418
query74	4669	4504	4311	4311
query75	2318	2295	1939	1939
query76	2309	1125	733	733
query77	364	387	300	300
query78	9161	9055	8421	8421
query79	1329	1215	736	736
query80	609	447	372	372
query81	539	322	288	288
query82	626	166	132	132
query83	299	219	193	193
query84	324	144	119	119
query85	868	446	375	375
query86	333	235	226	226
query87	1976	1978	1841	1841
query88	3613	2721	2726	2721
query89	364	281	246	246
query90	1932	179	174	174
query91	171	158	123	123
query92	100	91	92	91
query93	1426	1538	871	871
query94	518	338	291	291
query95	662	471	335	335
query96	1075	808	333	333
query97	2430	2424	2297	2297
query98	159	151	144	144
query99	768	726	608	608
Total cold run time: 236657 ms
Total hot run time: 152404 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.04	0.04
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.94	0.92
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.40	0.34	0.32
query10	0.55	0.55	0.55
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.45	0.47	0.48
query14	0.95	0.96	0.94
query15	0.60	0.59	0.58
query16	0.32	0.32	0.31
query17	1.07	1.12	1.06
query18	0.21	0.20	0.19
query19	2.01	1.94	1.94
query20	0.02	0.01	0.01
query21	15.48	0.21	0.13
query22	4.93	0.05	0.05
query23	16.11	0.32	0.11
query24	3.04	0.40	0.30
query25	0.11	0.05	0.05
query26	0.76	0.20	0.14
query27	0.06	0.04	0.03
query28	3.36	0.81	0.36
query29	12.48	4.10	3.18
query30	0.27	0.16	0.15
query31	2.77	0.55	0.30
query32	3.23	0.59	0.48
query33	3.23	3.17	3.17
query34	15.64	3.90	3.33
query35	3.24	3.24	3.24
query36	0.55	0.44	0.42
query37	0.09	0.06	0.06
query38	0.05	0.03	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.10	0.03	0.02
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.2 s
Total hot run time: 23.94 s

@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 complete for head 5eee943f3d313befe4c815a58178ea4f0e7610b6 after two full-review rounds, a separate risk scan in each round, candidate deduplication, and a final sweep of all nine changed files. I found four distinct issues (three P1, one P2), all inline. The earlier P1 comments 4140574895 and 4140980598 were independently rechecked and are fixed for their local empty-plan/lock-wait and received visibility-timeout scenarios on this head; no existing P0/P1 comment still applies as written. The four new comments cover separate remote, cloud, and concurrent-DROP cases. There was no additional user-provided review focus.

Critical checkpoints:

  • Goal, scope, and tests: The patch makes precommit local cancellation fail and lets a known committed local insert finish its swap. The injected local regression cases and expected ordered rows cover those returned-result paths. The four inline cases remain outside that coverage. The change is focused on overwrite outcome handling, although the new boolean cannot represent an unknown commit result.
  • Concurrency and locking: isCancelled is atomic. The local final check holds the target table's reentrant write lock, with the existing replacement routine taking that lock again; no new lock-order deadlock was substantiated. The remote FE owns a different lock and receives no cancellation state (P1), and a concurrent local DROP can make the new wrapper skip replacement then report success (P2). The added lock covers only the existing metadata swap work; no separate hot-path locking regression was found.
  • Lifecycle and error handling: Each overwrite gets a fresh context, read on the same statement thread; there is no static initialization issue. Task registration and failure cleanup are persisted through the existing overwrite manager. Known committed results set the marker, but exhausted remote RPC retries and cloud maybe-committed errors can leave durable rows marked uncommitted, so cleanup can delete them (two P1 findings). A skipped swap after DROP is also incorrectly acknowledged. No new configuration item was added.
  • Parallel paths and compatibility: Local, cloud, remote OLAP, connector, empty-plan, explicit-partition, and PARTITION(*) paths were traced. Transaction-model overwrite is rejected before execution and group commit excludes the inner overwrite. No wire or storage format changed, and no FE-to-BE variable was added. Remote cancellation requires an owner-side decision that the current RPC does not convey.
  • Transaction, persistence, and data correctness: The existing visible-version and publish mechanisms are used, and the local swap remains under the table lock. The new context flag is ephemeral and does not itself change edit-log replay. The two-phase crash and replacement-failure window is acknowledged in the PR description; this review's new comments address incorrect rollback or success outcomes reachable while the FE stays running. No BE memory, delete-bitmap, or rowset code changed.
  • Test results, observability, and performance: The .out labels and rows match the suite's ordered queries, and its debug points are distinct and consumed once per site. The lock-point fixture checks the under-lock branch but does not simulate a concurrent wait; no remote RPC-loss, cloud ambiguous-commit, or concurrent-DROP test is present. New logs identify cancellation and committed ERR paths; no extra metric was required by this narrow change. One additional reentrant lock acquisition and debug lookup per local overwrite show no obvious performance issue. This was a static review only: the review instructions prohibited builds and tests, so the PR author's reported passes were not independently reproduced.

txnStatus = TransactionStatus.COMMITTED;
}
markCommitted();
LOG.info("commit remote txn success, catalog={}, dbId={}, txnId={}, status={}",

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] Reconcile a remote commit whose reply is lost before rolling back the overwrite. The owning FE commits before it waits for publication, while masterCallWithRetry can exhaust retries after a lost reply. In that case this marker stays false although the remote transaction is COMMITTED; onFail cannot abort it, and the overwrite treats its ERR state as uncommitted and calls taskFail, dropping the partitions containing those rows. Query the remote transaction outcome or preserve its temporary partitions while the result is unknown. This differs from the existing visibility-timeout thread, where the committed response reached this FE.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that the state is possible, and agreed that the caller cannot tell it apart from an aborted commit: after masterCallWithRetry loses the reply, this FE has no way to know whether the owner committed, and onFail's abort cannot take a committed remote transaction back.

I would not fix it inside this PR, for the reason the comment itself states as an alternative: "keeping the partitions while the result is unknown" is not a stable end state either. The temp partitions it would keep are unpublished, they belong to a task the overwrite manager still holds, and the next allTaskFail (a master transfer, a restart) drops them -- so the loss is deferred rather than prevented, with an extra uncertain state in between. What prevents it is the property this PR's scope note names: the publication being a committed action of the insert transaction (or the consumption position moving with the publication), so that "committed" and "published" cannot disagree. That is a change to the transaction and journal path, not to this command.

Both remote findings are worth tracking; I would file them as their own issue with the reproduction (a lost commit reply / a KILL during the owner's swap lock wait) rather than growing this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #68679, filed for exactly these paths (remote lost commit reply, remote swap lock wait, ambiguous cloud commit) together with the crash/swap-failure windows this PR's scope note names. It records why an uncertain commit has no correct local resolution -- dropping the partitions loses durable rows, keeping them leaves unpublished ones the next allTaskFail drops -- and the fix directions (publication as a committed action of the insert transaction, or the consumption position moving into the publication's record). This PR stays on the local cancellation and error-response handling.

* <p>Only a local table is wrapped: a remote table swaps on the frontend that owns it, where this lock
* says nothing.
*/
private void publishTheOverwrite(TableIf targetTable, List<String> partitionNames,

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] Carry precommit cancellation through the remote swap. For an unpartitioned remote target, SELECT ... WHERE 1 = 0 can return from the inner insert without a transaction. If KILL arrives after the check at line 332 while the owning FE waits for its table write lock in replacePartitionsImpl, this branch supplies no cancellation state or last check: the empty temp partition replaces existing rows and the statement reports success. The existing local lock-wait fix does not cover this remote path; make the owning FE decide under its swap lock before replacing an uncommitted empty result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real gap, but the decision it asks for is the owning frontend's, so I have not taken it here.

The cancellation lives on this FE (the client's session), and replacePartitions carries no cancellation state, so the local last-chance check added in this PR cannot see a KILL that arrives while the owner waits for its table write lock in replacePartitionsImpl -- the check on this side runs before the RPC, and the wait happens after it. Covering it means either conveying the verdict (cancelled with nothing committed) in the replacement RPC so the owner refuses under its own lock, or moving the whole decision to the owner; both are remote-Doris protocol changes that belong with that feature rather than with the local overwrite outcome handling this PR is about.

The local half of the same window is covered (publishTheOverwrite reads the flag under the lock), and the auto-detect route is not reachable with nothing committed on this side, as discussed in the earlier thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #68679, filed for exactly these paths (remote lost commit reply, remote swap lock wait, ambiguous cloud commit) together with the crash/swap-failure windows this PR's scope note names. It records why an uncertain commit has no correct local resolution -- dropping the partitions loses durable rows, keeping them leaves unpublished ones the next allTaskFail drops -- and the fix directions (publication as a committed action of the insert transaction, or the consumption position moving into the publication's record). This PR stays on the local cancellation and error-response handling.

InsertOverwriteUtil.replacePartition(targetTable, partitionNames, tempPartitionNames,
isForceDropPartition());
return;
}

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] Fail when the target was dropped before the swap. writeLockIfExist() returns false after a concurrent DROP, so this new early return skips replacePartition, but run still calls taskSuccess and reports the overwrite as successful. The previous direct utility path raised an exception in this case; preserve an error outcome and clean the task rather than acknowledging a swap that never occurred.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The wrapper's writeLockIfExist() early return skipped the swap while run went on to taskSuccess, and that was a change from what the utility did: for a dropped table the utility returned from inside a try whose finally { writeUnlock(); } then unlocked a lock that return never took, so the call raised and the statement failed. publishTheOverwrite now raises deliberately, with a message naming the table, so the outcome is the one a client whose swap never happened is owed -- the catch drops the temp partitions of the dropped table and takes the task back.

One note on the other route, which I left alone rather than widen this PR: the auto-detect swap goes through InsertOverwriteManager.taskGroupSuccess into the same utility, so a target dropped under it fails the statement the same way, with that same unlock-after-early-return shape.

// Committed, visible later: the rows are durable even though the session's visibility-timeout
// mode may report the timeout as an error. See InsertCommandContext#setCommitted.
markCommitted();
}

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] Resolve an ambiguous cloud commit before failing the overwrite. FoundationDB can report a maybe-committed result after applying the transaction; after finite meta-service retries this reaches FE as KV_TXN_COMMIT_ERR. commitAndPublishTransactionWithRetry then throws before either markCommitted() call, leaving this context false. The overwrite sees ERR and taskFail drops its temp partitions even if the cloud commit made their rows and stream offsets durable. Determine the cloud transaction state, or keep the partitions when commit remains uncertain, before permitting rollback. This is separate from the returned visibility timeout already discussed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same family as the remote-commit case above, and it needs the same kind of decision, which is why I have not taken it here.

Two things make it a cloud transaction-semantics question rather than an overwrite one. First, the outcome is not decidable locally: after KV_TXN_COMMIT_ERR the meta service may or may not have applied the commit -- CloudInternalCatalog already treats that code specially elsewhere -- so choosing between "publish" and "drop" needs a state query against the meta service, or a policy for uncertain commits, and it applies to every cloud load that reaches that path, not only to overwrites. Second, dropping the partitions on an uncertain commit is wrong in the loss direction and keeping them is wrong in the stuck direction: an unpublished leftover has no owner that will ever publish it, since the overwrite that would have is gone. The durable answer is the same one this PR's scope note names -- make the publication and the commit one event (or carry the consumption position with the publication) -- and until that exists, an uncertain commit has no correct local resolution.

Happy to file this alongside the remote findings as a separate issue if that is useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked in #68679, filed for exactly these paths (remote lost commit reply, remote swap lock wait, ambiguous cloud commit) together with the crash/swap-failure windows this PR's scope note names. It records why an uncertain commit has no correct local resolution -- dropping the partitions loses durable rows, keeping them leaves unpublished ones the next allTaskFail drops -- and the fix directions (publication as a committed action of the insert transaction, or the consumption position moving into the publication's record). This PR stays on the local cancellation and error-response handling.

The last-chance wrapper took the target table's write lock itself and returned silently
when the table no longer existed, after which run() acknowledged the overwrite with a
success. Before the wrapper, the utility's own early return for a dropped table ran into
a finally that unlocks a lock that return never took, so it raised and the statement
failed. That outcome is the one a client whose swap never happened is owed, and it is
now taken deliberately, with a message naming the table.

Key changes:
- publishTheOverwrite raises for a dropped target instead of skipping the swap. The
  existing catch drops the temp partitions of the dropped table and takes the task back.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy. The
  dropped-target path needs a concurrent DROP and has no injection point of its own, so
  it is reasoned rather than driven.
- test_insert_overwrite_cancel, test_iot_range_nereids, test_iot_auto_detect and
  mtmv_p0/ivm/test_ivm_basic_mtmv pass.
@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

publishTheOverwrite raises when it cannot take the target table's write lock, which is
the state a concurrent DROP leaves. The branch is driven directly, because reaching it
through a statement needs a DROP concurrent with the swap and the command has no
injection point for that; the condition the branch reads is what the mock supplies.

Unit Test:
- InsertOverwriteDroppedTargetTest#targetDroppedBeforeTheSwapFailsTheOverwrite
  (Tests run: 1, Failures: 0)
@yujun777

Copy link
Copy Markdown
Contributor Author

The remaining windows this PR does not fix -- the FE stopping between the two halves (crash, master switch), a replacePartition that throws, a remote commit whose reply is lost, and a cloud transaction that the meta service reports as failed after a maybe-commit -- are now tracked in #68679, together with the owner-side decision a cancellation needs for a remote target. The fix directions there are the ones this PR's scope note names: making the publication a committed action of the insert transaction (or moving the consumption position into the publication's record).

This PR stays on what it fixed: a cancellation that found nothing committed fails the statement, one that reaches the swap under its lock is still honoured, one over committed rows completes the swap, and an error response over committed rows (the session's visibility-timeout mode) publishes them.

@yujun777

Copy link
Copy Markdown
Contributor Author

run buildall

@yujun777

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review recovery stopped: No final review submission was declared
Workflow run: https://github.com/apache/doris/actions/runs/36688755282

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

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

Review conclusion

Complete static review; changes requested on the three existing P1 threads below. The live base/head still match 0a3595e61870ed302eb8b732f968e48823bc0cc4 / 9039a08b9caaa367c748d43cf682bdfb9f9d8e3c. I reviewed all 10 changed files, the insert and overwrite call chains, the test fixtures and expected output, the existing inline threads, and the local, cloud, remote, auto-detect, and connector paths. The normal full-review passes and separate risk-focused pass all returned NO_NEW_VALUABLE_FINDINGS; the final changed-file sweep found no distinct new issue. No builds or tests were run in this review.

The new committed-state marker and local lock-time cancellation check address the ordinary local cancellation, empty-plan, and returned publication-timeout paths. Three existing P1 comments remain valid on this head and are not repeated inline:

  • 4142311115 — lost remote commit reply: the owning FE may commit before FeServiceClient.masterCallWithRetry exhausts retries. RemoteOlapInsertExecutor then leaves the marker false, and overwrite cleanup can drop the committed temporary partitions.
  • 4142311125 — remote swap lock wait: the owning FE waits for its table write lock after this FE checks cancellation. The remote replacement RPC carries no cancellation verdict, so an empty uncommitted result can still replace prior rows after KILL.
  • 4142311141 — ambiguous cloud commit: a maybe-committed meta-service result can surface as KV_TXN_COMMIT_ERR before OlapInsertExecutor.markCommitted(). Cleanup may drop partitions whose rows and stream offsets were committed.

The existing P2 auto-detect cancellation thread 4140574889 already covers the false-success concern for an empty no-transaction PARTITION(*) plan. With enable_strict_consistency_dml=false, its plan can avoid the exchange assumed in the author's reply; a cancellation after the branch's check can still be acknowledged while taskGroupSuccess waits for a lock. That path has no BE-created group tasks or partition replacement, and I have not duplicated the thread.

Required checkpoints

  1. Goal and proof: The change aims to fail cancellation before durable work and finish publication after durable work. The code and added regression cases cover the ordinary local cases and returned visibility timeout; the three existing P1 paths above keep the full data-correctness goal unmet.
  2. Scope and clarity: The change is confined to insert outcome propagation, overwrite decisions, and targeted tests. The marker is passed through the existing insert context without a new protocol field.
  3. Concurrency and locks: Client cancellation sets the outer command's atomic flag and cancels its coordinator. The explicit local path rereads that flag under the target table's reentrant write lock, immediately before replacement. The nested utility lock is balanced. The remote owner has a separate lock and the unresolved gap above. The wrapper adds no new lock order or new journal wait beyond the utility's existing replacement path.
  4. Lifecycle and cleanup: Before registration, a cancel creates no task; after registration, exceptions enter task cleanup. A dropped local target now raises instead of acknowledging a skipped swap. The remaining uncertain-commit paths can still clean up durable temporary partitions. No static initialization or ownership cycle was added.
  5. Configuration: No new configuration item was added. Existing visibility-timeout return mode and strict-DML-consistency settings affect the branches described above; no dynamic configuration propagation is introduced.
  6. Compatibility: No persisted format, function symbol, FE/BE message, or rolling-upgrade contract changed. The remote cancellation gap would require an owner-side decision or protocol change and remains open.
  7. Parallel paths and conditions: Local, remote, cloud, connector, explicit-partition, and auto-detect paths were traced. Transaction-model overwrite is rejected by StmtExecutor; group commit is excluded by the overwrite context. Local returned commit outcomes set the marker, but remote and cloud uncertain outcomes remain unresolved. The new conditions have comments, with the auto-detect edge already raised in its existing thread.
  8. Tests and results: The suite checks precommit failure, committed-row swap, empty-plan and lock-check failures, and local publication timeout; the .out rows match those scenarios. The cloud timeout case is gated while cancellation cases remain enabled. The dropped-target unit test exercises the false lock return. No test here establishes the remote or ambiguous cloud outcomes, and this review did not execute tests or independently validate the author's test-run claims.
  9. Observability: The new path logs committed cancellation, continued publication over an error response, and dropped-target failure with table/query context. It adds no metric; existing transaction and task logs remain the primary tracing route.
  10. Transactions, persistence, and failover: The marker is transient, as intended for one statement. The existing transaction manager commits and journals data and stream offsets; the later partition swap remains a separate operation. The PR does not make that pair atomic across failover or ambiguous commit, and the existing P1 threads identify two concrete loss paths.
  11. Data writes and crashes: Local returned publication timeout preserves committed rows through the swap. The remote lost-reply and cloud ambiguous-commit cases can still discard committed rows and advanced stream offsets. A crash between commit and swap remains outside this PR's stated scope and is not claimed as fixed.
  12. FE/BE variables and performance: No new transmitted variable or significant hot-path allocation was added. The added checks are constant-time; the local wrapper holds the table lock only across the precheck and the replacement work that already required that lock.
  13. Other issues and focus: No additional user-provided focus was supplied. Every new suspicion was either dismissed with code evidence or matched to an existing inline thread. No new inline finding remains unresolved or needs reposting.

Existing P0/P1 findings confirmed for this head: #68662 (comment), #68662 (comment), #68662 (comment)

…route too

An auto-detect overwrite (PARTITION(*)) publishes through the overwrite manager, so it
bypassed the check the explicit-partition route got. With enable_strict_consistency_dml
off, an empty-plan auto-detect overwrite has no exchange in its plan -- VEMPTYSET is the
sink's direct child -- so it takes the insert's no-transaction path, and a cancellation
landing while the swap waits for the table lock was answered with a success for a
statement that did nothing. Both routes now publish through one place that holds the
target table's write lock and reads the flag under it.

Key changes:
- publishTheOverwrite takes the publication as an action, so the explicit-partition swap
  and the auto-detect task group share the same last-chance decision, and the same
  failure for a target that was dropped.
- The suite drives the auto-detect shape: with strict DML consistency off, a cancelled
  PARTITION(*) overwrite of an empty plan fails and the table keeps its rows.

Unit Test:
- regression-test/suites/insert_overwrite_p0/test_insert_overwrite_cancel.groovy
- test_insert_overwrite_cancel, test_iot_auto_detect, test_iot_auto_detect_fail,
  test_iot_auto_detect_concurrent, test_iot_overwrite_and_create and
  mtmv_p0/ivm/test_ivm_basic_mtmv pass.
@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.

Static review complete for PR #68662 at b2f346c. Changes requested: two new inline findings (P1 test failure, P2 table-lock regression). Three earlier P1 findings still apply and are referenced by ID without duplicate comments.

Critical checkpoints:

  • Goal and proof: The local overwrite path now distinguishes a cancelled insert that committed nothing from one whose rows or stream offsets committed, and retains a committed visibility-timeout response through the swap. The regression suite covers the explicit, auto-detect, empty-plan, lock-wait, and local visibility-timeout cases by static inspection. Its new dropped-target unit test cannot run its intended assertion (new P1).
  • Scope and clarity: The context marker and shared publication helper are focused on the stated cancellation boundary. The invalid reflective test call and the overly broad auto-detect lock scope need correction.
  • Concurrency and locks: The statement thread reads its insert context after the executor returns; cancellation may set an atomic flag from another thread. The local swap rechecks that flag under the target table's reentrant write lock. The new wrapper also holds that lock through two synchronous edit-log writes per auto-detect task (new P2). The BE task-group RPC takes the group lock before a table lock, but publication does not take the group lock; no additional lock cycle was established. Remote swap locking remains the previously reported P1.
  • Lifecycle and cleanup: Normal local/cloud, remote OK, and external commit paths set the marker; the no-transaction empty-plan path leaves it false. Task cleanup follows publication failure after the local table lock is released. A remote lost commit reply and an ambiguous cloud commit can still leave durable rows with a false marker (existing P1s).
  • Configuration and compatibility: No new runtime configuration, persisted format, FE/BE field, or cross-version protocol is introduced. The tests set and restore existing session variables; the cloud-only visibility behavior is guarded.
  • Parallel paths and conditions: Explicit partitions and PARTITION(*) share the local last-chance cancellation decision. Remote publication intentionally bypasses the local lock, leaving the previously reported remote owner-side wait gap. The committed-state condition and separate one-shot debug points are coherent on the normal paths.
  • Tests and results: Ordered expected rows in the new .out file match the six regression snapshots, and expected errors use the test exception form. The dropped-target JUnit call has the wrong arity and will fail before testing its branch. Builds and tests were prohibited by this review contract; these conclusions are static only, not an independent runtime validation of author-reported runs.
  • Observability: The added cancellation, committed-error, and task logs carry query or task context; no separate metric requirement was identified.
  • Transactions, persistence, and data writes: No new journal state or replay format was added. The normal committed local result proceeds to the swap, while an uncommitted cancellation rolls back temporary partitions. The remote lost-reply, remote lock-wait, and ambiguous cloud-commit paths remain unresolved as earlier P1 threads 4142311115, 4142311125, and 4142311141; they are also tracked in #68679. Crash/swap-failure atomicity remains outside this change's implemented boundary. The new auto-detect wrapper adds journal waits inside the table lock (new P2).
  • Performance, memory, and other risks: The per-task journal waits can block readers and writers for large PARTITION(*) overwrites. No new material memory ownership issue or other distinct substantiated finding was found after two complete review rounds and the final changed-file sweep.

User focus: no additional focus was provided. All ten changed files were swept; no candidate remains unresolved. Existing P1 threads 4140574895 and 4140980598 are addressed on this head and are excluded from the blocking-ID list.

Existing P0/P1 findings confirmed for this head: #68662 (comment), #68662 (comment), #68662 (comment)

Mockito.when(ctx.getQueryIdentifier()).thenReturn("stmt[1, query-id]");

UserException thrown = Assertions.assertThrows(UserException.class,
() -> Deencapsulation.invoke(command, "publishTheOverwrite", droppedTarget,

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] Call the current publication method in this test. This invocation passes five arguments (target, two name lists, context, connection), but the only publishTheOverwrite overload takes four (target, insert context, connection, publication callback). Deencapsulation requires equal arity and throws IllegalArgumentException before the dropped-target branch; assertThrows(UserException.class) therefore fails whenever this test runs. Pass a publication callback and assert it was not invoked after writeLockIfExist() returns false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My slip, and you are right. The previous commit's refactor changed publishTheOverwrite's signature and I did not re-run the unit test afterwards. Reproduced exactly as described: IllegalArgumentException: No compatible method found: publishTheOverwrite(OlapTable, ArrayList, ArrayList, OlapInsertCommandContext, ConnectContext), so assertThrows(UserException.class) failed before reaching the branch.

Fixed the way you suggest: the test invokes the current method with a publication callback and asserts the callback was not reached (assertFalse(published.get())). The callback interface is package-private so a test in the same package can name it. Tests run: 1, Failures: 0, Errors: 0 on this head.

throw cancelledBeforeTheRowsWereCommitted("after a load that committed nothing", ctx);
}
publishTheOverwrite(targetTable, insertCtx, ctx,
() -> insertOverwriteManager.taskGroupSuccess(groupId, (OlapTable) targetTable,

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] Release the table write lock before task-group journal writes. The new wrapper holds that lock for the entire taskGroupSuccess call. After replacePartition returns, taskGroupSuccess writes an ADD and a DROP edit-log entry for every group task, and each logEdit waits for journal completion. On a PARTITION(*) overwrite spanning many partitions, readers and writers of the target stay blocked through 2N serial journal waits; previously the utility released the lock after the swap, before this bookkeeping. Keep the last cancellation check and replacement under the lock, then finish task logging after unlocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed the way you describe. I checked the durability this path actually uses before answering: BDBEnvironment builds Durability(master_sync_policy, replica_sync_policy, replica_ack_policy) from the defaults SYNC / SYNC / SIMPLE_MAJORITY, so every logEdit in that bookkeeping waits for a replica acknowledgement -- and my wrapper was holding the target's write lock across 2N of them.

taskGroupSuccess is now split into replacePartitionsOfTaskGroup (the replacement) and finishTaskGroup (the per-task journaling and the task cleanup). The overwrite calls the replacement inside publishTheOverwrite -- under the lock, with the last-chance cancellation check -- and the bookkeeping after it returns. The owner-side entry (FrontendServiceImpl -> InsertOverwriteManager.taskGroupSuccess) keeps calling the composed method, and for a remote target the manager's replacement call is the existing RPC (the owner runs the whole sequence behind it) with finishTaskGroup a no-op there. So the lock again covers only the replacement and the last check, not the journal waits.

…lock

Two follow-ups from the previous commit. The task-group publication ran all of its
bookkeeping under the target table's write lock, which the wrapper holds: that bookkeeping
writes an edit-log entry for every task (an ADD and a DROP each), and with the default
journal durability (SYNC/SYNC/SIMPLE_MAJORITY) every one of them waits for a replica
acknowledgement, so a PARTITION(*) overwrite spanning many partitions kept readers and
writers of the target blocked through all of those waits. And the unit test added for the
dropped-target branch still called the publication method with its pre-refactor signature,
so its reflective call never reached the branch it was written for.

Key changes:
- taskGroupSuccess is split into the replacement and the bookkeeping. The overwrite calls
  the replacement under its last-chance lock and the bookkeeping after it; the owner-side
  entry point keeps calling the composed method, so that path is unchanged.
- The dropped-target test calls the current method with a publication callback and asserts
  the callback was not reached.

Unit Test:
- InsertOverwriteDroppedTargetTest#targetDroppedBeforeTheSwapFailsTheOverwrite
  (Tests run: 1, Failures: 0, Errors: 0)
- test_insert_overwrite_cancel, test_iot_auto_detect, test_iot_auto_detect_fail,
  test_iot_auto_detect_concurrent and test_iot_overwrite_and_create pass.
@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.

Complete static review of PR #68662 at head 1c28718. I reviewed all 13 changed files, traced the affected FE insert and overwrite paths, completed the risk scan and focused follow-up, and found no new inline issue to add.

  • Goal and tests: The local explicit-partition and PARTITION(*) paths now fail cancellation before an insert transaction commits and finish publication after a confirmed commit. The dropped-target unit test and ordered regression cases cover the intended branches by static inspection. I did not run builds or tests under this review contract, so runtime behavior is not independently verified here.
  • Scope and parallel paths: The change is focused on insert outcome tracking, overwrite publication, manager bookkeeping, and tests. Local OLAP, remote OLAP, and external/plugin executor paths were traced. The candidate concerning a successful empty transaction was dismissed: it crosses the PR's stated commit boundary and the subsequent swap completes the requested overwrite.
  • Concurrency and lifecycle: The cancellation flag is atomic. The local final check and replacement share the table write lock; task-group edit-log bookkeeping follows after unlocking, avoiding serial journal waits under that lock. Task cleanup after a completed replacement only targets temporary partitions. No new static/global lifecycle, lock-order, or memory-ownership change was found.
  • Conditions, errors, and data correctness: The context marker is set after acknowledged commits, including a local publication timeout, while the no-transaction path leaves it false. The local error response after a committed visibility timeout preserves publication. Three already-reported P1 paths still apply: a lost remote commit reply (4142311115), cancellation during the remote owner's swap lock wait (4142311125), and an ambiguous cloud commit (4142311141). Those existing inline threads are the blocking disposition; I am not duplicating them. The broader commit/swap crash window is outside this local fix and is already tracked in the PR discussion.
  • Persistence, compatibility, and configuration: The task-group split preserves the existing replacement and task journal operations. No new FE/BE field, wire format, persistent format, or configuration item is introduced. The change does not alter visible-version or delete-bitmap handling.
  • Coverage, observability, and performance: The added unit case targets a dropped table; regression cases use scoped debug points, error assertions, ordered expected results, and a cloud guard for the FE publication timeout. New cancellation and continuation logs include query identifiers. The narrower table-lock span addresses the apparent bookkeeping latency risk; no further hot-path cost was substantiated.

User focus: no additional focus was supplied. Review execution is complete on this head, with the three existing P1 findings still blocking.

Existing P0/P1 findings confirmed for this head: #68662 (comment), #68662 (comment), #68662 (comment)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	4776	3878	3830	3830
q2	927	384	315	315
q3	969	1350	778	778
q4	747	474	363	363
q5	1389	835	577	577
q6	188	172	143	143
q7	806	775	596	596
q8	1214	1195	1261	1195
q9	4245	4415	4178	4178
q10	1501	1311	1038	1038
q11	409	273	252	252
q12	369	417	304	304
q13	18008	2603	2015	2015
q14	262	263	237	237
q15	q16	746	713	653	653
q17	1785	1131	982	982
q18	6604	5583	5555	5555
q19	1297	1243	1046	1046
q20	458	380	262	262
q21	5720	2899	2669	2669
q22	434	349	298	298
Total cold run time: 52854 ms
Total hot run time: 27286 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4244	4143	4124	4124
q2	811	556	515	515
q3	4494	4837	4359	4359
q4	2188	2289	1463	1463
q5	4183	4077	4094	4077
q6	227	175	125	125
q7	1714	1587	1416	1416
q8	2167	1878	1872	1872
q9	6905	6848	6815	6815
q10	3630	3576	3074	3074
q11	522	389	347	347
q12	716	711	510	510
q13	2278	2615	2003	2003
q14	273	278	257	257
q15	q16	675	699	615	615
q17	7300	6733	6646	6646
q18	11992	11098	11734	11098
q19	1132	1066	1006	1006
q20	2209	2221	1920	1920
q21	4984	4078	4301	4078
q22	529	465	441	441
Total cold run time: 63173 ms
Total hot run time: 56761 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152392 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 1c287182321e75780125165061900374df220b7d, data reload: false

query5	4341	589	464	464
query6	421	209	200	200
query7	4955	531	279	279
query8	339	181	171	171
query9	8799	3956	3962	3956
query10	475	307	262	262
query11	5877	3526	3230	3230
query12	144	93	87	87
query13	1276	588	415	415
query14	6583	4525	4200	4200
query14_1	3952	3984	3963	3963
query15	204	207	189	189
query16	1004	457	440	440
query17	934	675	550	550
query18	2430	464	347	347
query19	202	183	145	145
query20	84	81	92	81
query21	221	137	118	118
query22	12990	13042	12774	12774
query23	14115	13003	12377	12377
query23_1	12562	12588	12547	12547
query24	7358	1146	672	672
query24_1	687	660	746	660
query25	573	436	365	365
query26	1266	295	171	171
query27	2720	572	333	333
query28	4574	1947	1947	1947
query29	1681	730	524	524
query30	298	222	183	183
query31	901	755	634	634
query32	154	94	93	93
query33	566	308	257	257
query34	1164	1127	634	634
query35	712	745	641	641
query36	813	799	743	743
query37	143	106	96	96
query38	1861	1780	1684	1684
query39	681	710	653	653
query39_1	660	626	647	626
query40	229	130	105	105
query41	72	71	69	69
query42	99	95	99	95
query43	334	350	307	307
query44	1360	709	728	709
query45	191	183	160	160
query46	1063	1186	724	724
query47	1489	1487	1376	1376
query48	403	409	282	282
query49	585	403	297	297
query50	935	338	249	249
query51	10526	10329	10579	10329
query52	85	92	75	75
query53	237	250	179	179
query54	243	201	183	183
query55	82	75	71	71
query56	236	217	201	201
query57	1493	1365	1386	1365
query58	289	260	249	249
query59	1999	2049	1873	1873
query60	284	246	227	227
query61	149	148	146	146
query62	402	321	284	284
query63	216	182	176	176
query64	2839	986	842	842
query65	3667	3406	3395	3395
query66	1813	426	316	316
query67	19986	19931	19858	19858
query68	3486	1536	904	904
query69	408	296	268	268
query70	905	832	835	832
query71	301	234	222	222
query72	2722	2517	2273	2273
query73	817	780	396	396
query74	4631	4517	4286	4286
query75	2302	2279	1933	1933
query76	2395	1115	786	786
query77	360	394	315	315
query78	9299	9254	8562	8562
query79	1383	1180	763	763
query80	1208	473	368	368
query81	593	321	280	280
query82	608	157	121	121
query83	299	217	195	195
query84	312	143	114	114
query85	1136	457	369	369
query86	381	236	221	221
query87	2004	1947	1814	1814
query88	3610	2700	2676	2676
query89	364	280	242	242
query90	1731	179	174	174
query91	170	156	131	131
query92	106	86	90	86
query93	1545	1350	866	866
query94	649	342	296	296
query95	655	433	345	345
query96	1003	782	329	329
query97	2433	2435	2327	2327
query98	167	153	166	153
query99	725	719	607	607
Total cold run time: 238477 ms
Total hot run time: 152392 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.10	0.05	0.05
query3	0.26	0.13	0.14
query4	1.60	0.14	0.14
query5	0.25	0.21	0.22
query6	1.16	0.94	0.93
query7	0.05	0.01	0.00
query8	0.05	0.04	0.03
query9	0.41	0.34	0.34
query10	0.55	0.62	0.54
query11	0.22	0.15	0.14
query12	0.18	0.16	0.14
query13	0.47	0.47	0.47
query14	0.96	0.95	0.93
query15	0.63	0.59	0.60
query16	0.32	0.31	0.34
query17	1.05	1.10	1.11
query18	0.23	0.20	0.20
query19	1.97	1.91	1.99
query20	0.02	0.01	0.01
query21	15.42	0.19	0.15
query22	4.92	0.05	0.05
query23	16.13	0.32	0.12
query24	2.99	0.42	0.31
query25	0.11	0.05	0.04
query26	0.75	0.21	0.15
query27	0.04	0.04	0.03
query28	3.52	0.75	0.34
query29	12.52	3.95	3.17
query30	0.29	0.16	0.15
query31	2.76	0.55	0.32
query32	3.22	0.59	0.49
query33	3.11	3.23	3.15
query34	15.53	3.90	3.28
query35	3.22	3.25	3.21
query36	0.56	0.46	0.42
query37	0.08	0.07	0.07
query38	0.05	0.04	0.04
query39	0.03	0.03	0.03
query40	0.17	0.14	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.02
query43	0.04	0.04	0.03
Total cold run time: 96.08 s
Total hot run time: 23.91 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 64.71% (55/85) 🎉
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