Skip to content

[opt](merge into) Put MERGE INTO target on the probe side - #66565

Open
starocean999 wants to merge 1 commit into
apache:masterfrom
starocean999:master_mergeinto
Open

[opt](merge into) Put MERGE INTO target on the probe side#66565
starocean999 wants to merge 1 commit into
apache:masterfrom
starocean999:master_mergeinto

Conversation

@starocean999

Copy link
Copy Markdown
Contributor

Issue Number: close #66435

Related PR: #xxx

Problem Summary:

Doris builds the hash table on the right child, so the structurally wide side — the target must carry all columns plus the row identity (for Iceberg: struct<file_path, row_position, ...> with a full S3 URI per row) — always became the build side. Worse, LEFT_OUTER_JOIN is in DENIED_JOIN_TYPES, so the merge join could never produce runtime filters at all. The internal OLAP path (MergeIntoCommand) used LEFT_OUTER_JOIN unconditionally, even without WHEN NOT MATCHED clauses (the external path already had the INNER optimization). For reference, Trino plans MERGE as target RIGHT JOIN source and only allows dynamic filters on INNER || RIGHT — exactly complementary to Doris's denied list.

Release note

None

Check List (For Author)

  • Test

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

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

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

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

@hello-stephen

Copy link
Copy Markdown
Contributor

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

Please clearly describe your PR:

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

@starocean999

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17636	3950	3985	3950
q2	2037	334	203	203
q3	10267	1346	832	832
q4	4683	479	332	332
q5	7517	821	550	550
q6	179	163	135	135
q7	771	800	583	583
q8	9318	1655	1631	1631
q9	5324	4105	4116	4105
q10	6710	1610	1346	1346
q11	491	348	318	318
q12	719	573	436	436
q13	18082	3311	2703	2703
q14	266	260	242	242
q15	q16	730	735	665	665
q17	1000	919	950	919
q18	6646	5620	5570	5570
q19	1236	1214	1078	1078
q20	801	690	571	571
q21	5786	2566	2440	2440
q22	436	354	300	300
Total cold run time: 100635 ms
Total hot run time: 28909 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4302	4249	4194	4194
q2	282	311	211	211
q3	4553	4927	4351	4351
q4	2168	2263	1413	1413
q5	4233	4076	4114	4076
q6	229	172	124	124
q7	1676	1581	1365	1365
q8	2599	2309	2176	2176
q9	7377	7368	7565	7368
q10	4344	4243	3844	3844
q11	557	395	379	379
q12	741	723	514	514
q13	3252	3509	2933	2933
q14	298	289	260	260
q15	q16	701	732	656	656
q17	1315	1285	1281	1281
q18	12226	11054	11886	11054
q19	1188	1229	1154	1154
q20	2220	2240	1946	1946
q21	5657	4816	4834	4816
q22	533	480	434	434
Total cold run time: 60451 ms
Total hot run time: 54549 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 167601 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 4fc2a42cd7c2496f73a58d521b6310a99f0f2ba8, data reload: false

query5	4302	615	449	449
query6	449	230	203	203
query7	4877	587	359	359
query8	319	161	153	153
query9	8762	4087	4067	4067
query10	478	357	321	321
query11	5812	2174	2061	2061
query12	152	97	96	96
query13	1238	590	430	430
query14	6075	4438	4148	4148
query14_1	3943	3911	3911	3911
query15	202	204	181	181
query16	1010	480	490	480
query17	950	698	573	573
query18	2438	471	351	351
query19	211	194	155	155
query20	107	107	108	107
query21	233	166	150	150
query22	13022	13026	12783	12783
query23	15972	15178	14683	14683
query23_1	15535	15184	14996	14996
query24	7734	1708	1254	1254
query24_1	1259	1274	1281	1274
query25	561	453	383	383
query26	1310	361	219	219
query27	2583	564	394	394
query28	4560	2046	2060	2046
query29	1080	609	533	533
query30	342	257	220	220
query31	1184	1135	1064	1064
query32	115	64	53	53
query33	516	304	239	239
query34	1190	1116	640	640
query35	732	742	637	637
query36	762	783	697	697
query37	149	108	90	90
query38	1830	1780	1710	1710
query39	850	847	807	807
query39_1	775	787	768	768
query40	239	163	142	142
query41	61	60	61	60
query42	95	92	89	89
query43	327	336	283	283
query44	1492	758	785	758
query45	187	176	158	158
query46	1065	1179	719	719
query47	1532	1552	1446	1446
query48	411	406	293	293
query49	573	435	280	280
query50	1106	452	335	335
query51	10704	10686	10825	10686
query52	85	86	73	73
query53	259	274	207	207
query54	291	230	215	215
query55	74	69	67	67
query56	299	289	317	289
query57	1042	1000	940	940
query58	285	253	270	253
query59	1586	1658	1396	1396
query60	330	277	255	255
query61	156	151	144	144
query62	403	325	268	268
query63	230	202	198	198
query64	2899	1038	851	851
query65	3862	3807	3827	3807
query66	1873	464	347	347
query67	28194	28132	27883	27883
query68	3303	1575	1052	1052
query69	403	290	255	255
query70	905	819	803	803
query71	363	332	336	332
query72	3063	2658	2339	2339
query73	813	855	440	440
query74	4655	4499	4314	4314
query75	2401	2342	2022	2022
query76	2425	1207	769	769
query77	342	377	277	277
query78	11141	11104	10586	10586
query79	1352	1153	737	737
query80	662	562	478	478
query81	454	327	293	293
query82	635	180	141	141
query83	416	342	302	302
query84	330	165	135	135
query85	919	622	525	525
query86	323	231	229	229
query87	1989	1980	1838	1838
query88	3818	2806	2768	2768
query89	391	322	282	282
query90	1961	213	214	213
query91	212	194	161	161
query92	61	62	56	56
query93	1659	1601	964	964
query94	538	337	306	306
query95	788	605	498	498
query96	1078	818	360	360
query97	2494	2429	2343	2343
query98	198	189	180	180
query99	716	733	626	626
Total cold run time: 254180 ms
Total hot run time: 167601 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.09	0.05	0.05
query3	0.25	0.14	0.14
query4	1.60	0.14	0.14
query5	0.25	0.25	0.23
query6	1.16	0.82	0.80
query7	0.04	0.00	0.00
query8	0.06	0.04	0.03
query9	0.41	0.31	0.31
query10	0.53	0.56	0.56
query11	0.19	0.13	0.12
query12	0.18	0.14	0.14
query13	0.46	0.45	0.47
query14	1.01	1.00	0.99
query15	0.61	0.59	0.58
query16	0.34	0.35	0.33
query17	1.12	1.10	1.05
query18	0.23	0.19	0.19
query19	2.03	1.88	1.94
query20	0.02	0.01	0.02
query21	15.49	0.23	0.13
query22	4.76	0.05	0.05
query23	16.14	0.31	0.13
query24	2.99	0.43	0.32
query25	0.12	0.06	0.04
query26	0.72	0.20	0.14
query27	0.04	0.04	0.04
query28	3.52	0.72	0.36
query29	12.51	4.08	3.28
query30	0.28	0.15	0.15
query31	2.78	0.55	0.31
query32	3.24	0.59	0.49
query33	3.18	3.14	3.23
query34	15.52	3.89	3.26
query35	3.23	3.21	3.21
query36	0.58	0.42	0.42
query37	0.09	0.07	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.08	0.04	0.03
query42	0.04	0.03	0.04
query43	0.05	0.04	0.03
Total cold run time: 96.2 s
Total hot run time: 23.83 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 80.00% (4/5) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.37% (2/543) 🎉
Increment coverage report
Complete coverage report

@starocean999
starocean999 marked this pull request as ready for review August 7, 2026 06:44
@morrySnow morrySnow changed the title [opt](nereids) Put MERGE INTO target on the probe side [opt](merge into) Put MERGE INTO target on the probe side Aug 7, 2026

@morrySnow morrySnow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! The semantic equivalence between source LEFT_OUTER target and target RIGHT_OUTER source is correct, and moving the target to the probe side so runtime filters can benefit is a solid optimization. The extraction into MergeUtils is clean.

I submitted a few minor inline comments for consideration — none are blocking. LGTM overall.

* Build the base join between merge target and source, with the target on the LEFT (probe)
* side. Doris builds the hash table on the right child, and the target side is structurally
* the wide one: it must carry every table column plus the row identity for the sink, while
* the source usually only carries join keys and new values. Keeping the target on the probe

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.

The Javadoc mentions that "Doris builds the hash table on the right child" — it would be helpful to also explicitly note that (which excludes but allows and ) is a co-motivator for this choice. This makes the rationale self-contained for future readers who might wonder why RIGHT_OUTER was chosen over LEFT_OUTER.

LogicalPlan result = (LogicalPlan) generateBasePlan.invoke(command);
Assertions.assertInstanceOf(LogicalJoin.class, result);
LogicalJoin<?, ?> logicalJoin = (LogicalJoin<?, ?>) result;
// WHEN NOT MATCHED needs the unmatched source rows: source is the preserved right side

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.

The new test asserts the left child is a LogicalSubQueryAlias but does not verify the alias value. For consistency with testGenerateBasePlanWithAlias (which extracts the alias and asserts .getAlias() equals "alias"), consider adding the same assertion here.

* Build the base join between merge target and source, with the target on the LEFT (probe)
* side. Doris builds the hash table on the right child, and the target side is structurally
* the wide one: it must carry every table column plus the row identity for the sink, while
* the source usually only carries join keys and new values. Keeping the target on the probe

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.

The Javadoc mentions that "Doris builds the hash table on the right child" — it would be helpful to also explicitly note that RuntimeFilterGenerator.DENIED_JOIN_TYPES (which excludes LEFT_OUTER_JOIN but allows RIGHT_OUTER_JOIN and INNER_JOIN) is a co-motivator for this choice. This makes the rationale self-contained for future readers who might wonder why RIGHT_OUTER was chosen over LEFT_OUTER.

@morrySnow

Copy link
Copy Markdown
Contributor

Minor observation: The MergeIntoCommandTest now covers both INNER_JOIN (no NOT MATCHED) and RIGHT_OUTER_JOIN (with NOT MATCHED) paths through MergeUtils.buildMergeJoin. There is no corresponding test for the external path (ExternalRowLevelMergePlanBuilder) exercising the RIGHT_OUTER_JOIN branch. Since both paths share MergeUtils.buildMergeJoin, the risk is low, but a test passing a MergeNotMatchedClause to the external builder would close the coverage gap.

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

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

Labels

approved Indicates a PR has been approved by one committer. dev/4.1.x reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants