Skip to content

[fix](fe) refuse broadcast join when build-side row count was clamped from unknown#65900

Open
Baymine wants to merge 1 commit into
apache:masterfrom
Baymine:fix/stats-refuse-broadcast-on-clamped-rowcount
Open

[fix](fe) refuse broadcast join when build-side row count was clamped from unknown#65900
Baymine wants to merge 1 commit into
apache:masterfrom
Baymine:fix/stats-refuse-broadcast-on-clamped-rowcount

Conversation

@Baymine

@Baymine Baymine commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #65899

Problem Summary:
When an Olap or external (Hive/Iceberg/etc.) table has not been ANALYZE'd, its row count is propagated as UNKNOWN_ROW_COUNT (-1) from BE/catalog. StatsCalculator then clamps this -1 up to 1 row so the cost model (which cannot tolerate negatives) keeps working. The resulting "1 row" looks tiny, so JoinUtils.checkBroadcastJoinStats happily picks that table as the broadcast build side — even though its true size is unknown and potentially huge. At runtime the un-ANALYZE'd large table gets fully materialized on every BE node, causing OOM.

This adds a rowCountWasUnknown flag to Statistics (propagated through the copy ctor, the 6-arg ctor, StatisticsBuilder, and the withRowCount* family) that records whether the row count was clamped from UNKNOWN. StatsCalculator sets it in computeOlapScan and computeCatalogRelation (refreshing it in the external path so a row count recovered from per-column stats clears the flag). JoinUtils.checkBroadcastJoinStats now refuses broadcast when the flag is set, in addition to the existing guards.

Release note

When a table has not been analyzed, its row count is unknown and is internally clamped to 1. Previously such a table could be chosen as the broadcast join build side because 1 row looks small, leading to OOM at runtime on large un-analyzed tables. The optimizer now refuses broadcast for a build side whose row count was clamped from unknown, falling back to shuffle join instead.

Check List (For Author)

  • Test
    • Unit Test
  • Behavior changed:
    • Yes. An un-analyzed table whose row count was previously clamped to 1 will no longer be eligible as a broadcast join build side; the optimizer falls back to shuffle join. This avoids OOM but may change plans for queries that previously (possibly wrongly) used broadcast.
  • Does this need documentation?
    • No. Internal optimizer stats-tracking change; no user-facing knob.

… from unknown

### What problem does this PR solve?

Issue Number: close apache#65899

Problem Summary:
When an Olap or external (Hive/Iceberg/etc.) table has not been ANALYZE'd, its
row count is propagated as UNKNOWN_ROW_COUNT (-1) from BE/catalog. StatsCalculator
then clamps this -1 up to 1 row so the cost model (which cannot tolerate
negatives) keeps working. The resulting "1 row" looks tiny, so
JoinUtils.checkBroadcastJoinStats happily picks that table as the broadcast
build side — even though its true size is unknown and potentially huge. At
runtime the un-ANALYZE'd large table gets fully materialized on every BE node,
causing OOM.

This adds a `rowCountWasUnknown` flag to Statistics (propagated through the
copy ctor, the 6-arg ctor, StatisticsBuilder, and the withRowCount* family)
that records whether the row count was clamped from UNKNOWN. StatsCalculator
sets it in computeOlapScan and computeCatalogRelation (refreshing it in the
external path so a row count recovered from per-column stats clears the flag).
JoinUtils.checkBroadcastJoinStats now refuses broadcast when the flag is set,
in addition to the existing guards.

### Release note

When a table has not been analyzed, its row count is unknown and is internally
clamped to 1. Previously such a table could be chosen as the broadcast join
build side because 1 row looks small, leading to OOM at runtime on large
un-analyzed tables. The optimizer now refuses broadcast for a build side whose
row count was clamped from unknown, falling back to shuffle join instead.

### Check List (For Author)

- Test
    - [ ] Regression test
    - [x] 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 <!-- Add your reason? -->

- Behavior changed:
    - [x] Yes. An un-analyzed table whose row count was previously clamped to 1
          will no longer be eligible as a broadcast join build side; the
          optimizer falls back to shuffle join. This avoids OOM but may change
          plans for queries that previously (possibly wrongly) used broadcast.

- Does this need documentation?
    - [x] No. Internal optimizer stats-tracking change; no user-facing knob.
@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?

@Baymine

Baymine commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17788	4144	4011	4011
q2	2078	325	198	198
q3	10242	1452	848	848
q4	4677	475	338	338
q5	7631	845	562	562
q6	182	174	135	135
q7	746	811	615	615
q8	9324	1530	1585	1530
q9	5622	4383	4337	4337
q10	6756	1766	1503	1503
q11	508	362	316	316
q12	709	571	448	448
q13	18138	3398	2758	2758
q14	272	259	242	242
q15	q16	788	787	703	703
q17	1040	968	982	968
q18	6856	5740	5513	5513
q19	1205	1324	1041	1041
q20	818	666	596	596
q21	5693	2549	2521	2521
q22	419	357	305	305
Total cold run time: 101492 ms
Total hot run time: 29488 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4373	4303	4306	4303
q2	287	315	228	228
q3	4581	4964	4407	4407
q4	2061	2130	1349	1349
q5	4452	4272	4250	4250
q6	225	177	128	128
q7	1779	1624	2026	1624
q8	2616	2223	2234	2223
q9	8005	8091	7737	7737
q10	4697	4696	4188	4188
q11	565	436	381	381
q12	798	766	552	552
q13	3260	3588	2910	2910
q14	311	309	272	272
q15	q16	710	733	636	636
q17	1371	1328	1344	1328
q18	8129	7407	7195	7195
q19	1156	1158	1108	1108
q20	2204	2226	1942	1942
q21	5543	4565	4442	4442
q22	511	466	404	404
Total cold run time: 57634 ms
Total hot run time: 51607 ms

@hello-stephen

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

query5	4342	636	488	488
query6	477	242	229	229
query7	4927	587	341	341
query8	358	185	175	175
query9	8773	4082	4069	4069
query10	452	361	337	337
query11	5910	2334	2120	2120
query12	157	105	97	97
query13	1266	608	479	479
query14	6252	5230	4888	4888
query14_1	4203	4251	4164	4164
query15	227	206	179	179
query16	1024	466	430	430
query17	909	691	558	558
query18	2445	474	339	339
query19	201	185	144	144
query20	107	104	103	103
query21	233	156	132	132
query22	13580	13546	13380	13380
query23	17403	16460	16119	16119
query23_1	16242	16202	16221	16202
query24	7607	1763	1266	1266
query24_1	1273	1290	1266	1266
query25	536	438	400	400
query26	1384	365	220	220
query27	2567	617	384	384
query28	4544	1966	1955	1955
query29	1060	599	462	462
query30	343	264	223	223
query31	1114	1088	980	980
query32	112	61	59	59
query33	527	319	248	248
query34	1172	1124	643	643
query35	771	779	667	667
query36	1186	1172	1080	1080
query37	152	104	89	89
query38	1867	1718	1668	1668
query39	889	869	863	863
query39_1	832	823	835	823
query40	250	172	149	149
query41	65	64	64	64
query42	91	90	91	90
query43	328	328	286	286
query44	1414	775	762	762
query45	194	179	188	179
query46	1074	1181	722	722
query47	2098	2064	2016	2016
query48	419	427	302	302
query49	586	445	314	314
query50	1112	417	340	340
query51	10742	10838	10733	10733
query52	87	87	76	76
query53	269	282	197	197
query54	308	256	234	234
query55	78	74	67	67
query56	303	315	295	295
query57	1324	1279	1193	1193
query58	307	266	265	265
query59	1669	1671	1413	1413
query60	335	281	267	267
query61	175	174	177	174
query62	547	501	419	419
query63	244	210	206	206
query64	2986	1177	919	919
query65	4714	4643	4774	4643
query66	1833	507	383	383
query67	29291	29223	29067	29067
query68	3282	1586	982	982
query69	418	299	275	275
query70	1100	973	967	967
query71	362	352	320	320
query72	3059	2692	2436	2436
query73	808	765	424	424
query74	5081	4920	4754	4754
query75	2513	2508	2119	2119
query76	2292	1162	775	775
query77	367	372	291	291
query78	11960	11802	11382	11382
query79	1243	1094	741	741
query80	638	577	486	486
query81	451	333	296	296
query82	239	153	120	120
query83	321	335	293	293
query84	306	158	128	128
query85	911	615	532	532
query86	330	300	275	275
query87	1821	1822	1737	1737
query88	3698	2781	2770	2770
query89	420	378	328	328
query90	2143	209	198	198
query91	208	193	166	166
query92	66	61	55	55
query93	1560	1488	922	922
query94	541	350	326	326
query95	792	505	551	505
query96	1123	808	356	356
query97	2643	2602	2527	2527
query98	208	209	205	205
query99	1084	1117	967	967
Total cold run time: 262562 ms
Total hot run time: 177962 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.10	0.06	0.04
query3	0.26	0.14	0.14
query4	1.61	0.14	0.14
query5	0.24	0.21	0.23
query6	1.23	1.03	1.07
query7	0.04	0.00	0.00
query8	0.05	0.04	0.04
query9	0.39	0.31	0.33
query10	0.56	0.56	0.56
query11	0.19	0.14	0.13
query12	0.17	0.13	0.14
query13	0.46	0.47	0.49
query14	1.03	1.03	1.02
query15	0.62	0.59	0.60
query16	0.32	0.33	0.32
query17	1.14	1.14	1.12
query18	0.23	0.20	0.22
query19	2.00	1.98	1.94
query20	0.01	0.01	0.01
query21	15.51	0.21	0.15
query22	4.93	0.06	0.05
query23	16.12	0.31	0.12
query24	3.00	0.43	0.31
query25	0.12	0.05	0.05
query26	0.73	0.20	0.16
query27	0.04	0.04	0.03
query28	3.53	0.88	0.52
query29	12.51	4.09	3.29
query30	0.28	0.16	0.15
query31	2.77	0.59	0.31
query32	3.22	0.60	0.49
query33	3.21	3.21	3.15
query34	15.49	4.23	3.50
query35	3.54	3.55	3.58
query36	0.56	0.44	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.17	0.16
query41	0.08	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.73 s
Total hot run time: 25.04 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 67.86% (19/28) 🎉
Increment coverage report
Complete coverage report

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broadcast join wrongly chosen for un-analyzed tables, causing OOM

2 participants