Skip to content

[improvement](be) Optimize numeric DISTINCT state merging - #68350

Merged
mrhhsg merged 1 commit into
apache:masterfrom
HappenLee:improvement/distinct-direct-merge
Sep 22, 2026
Merged

mrhhsg merged 1 commit into
apache:masterfrom
HappenLee:improvement/distinct-direct-merge

Conversation

@HappenLee

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: N/A

Problem Summary:

The generic numeric DISTINCT combinator (for example, multi_distinct_sum over integer columns) copies the entire source hash set before merging it. When merging serialized partial aggregates, the default implementation first builds a temporary hash set and then copies that set again. This adds allocation, hashing, and traversal work in the aggregation merge stage.

Insert existing source keys directly without modifying the source. Override deserialize_and_merge so numeric keys are read directly into the destination set. Reserve the known number of incoming keys only when the destination is empty: reserving the sum of source and destination sizes can unnecessarily grow heavily overlapping sets. Generic/StringRef states keep their existing deserialize-then-merge path, including copying key bytes into the destination arena. Serialization and NULL semantics are unchanged.

An earlier standalone merge-kernel microbenchmark with 131,072 distinct int64 keys (Clang 21.1.8, AVX2, nine-run medians) measured serialized merging into an empty destination at 9.725 ms before versus 0.944 ms with direct deserialization, and full overlap at 6.233 ms versus 0.250 ms. This uses phmap with a counting standard allocator, not a full Doris SQL query; these numbers are not end-to-end latency claims. The dedicated multi_distinct_count implementation already has its own merge optimization and is outside this change.

Release note

Reduce temporary hash-set allocations and CPU work when merging generic numeric DISTINCT aggregate states.

Check List (For Author)

  • Test:
    • Unit Test: 16 ASAN tests passed for all five integer widths, source preservation, repeated/empty merges, unused numeric scratch sets, both Nullable implementations, grouped/selected batch dispatch, and generic string ownership.
    • Regression test: one-/two-phase aggregation, grouped/ungrouped queries, nullable/non-null inputs, empty/all-null input, and AggState merging.
  • Behavior changed:
    • No. SQL results and serialized state formats are unchanged.
  • Does this need documentation?
    • No.

Validation:

  • ./run-be-ut.sh -j 96 --run --filter='DistinctNumericMergeTest/*.*:NullImplementations/DistinctMergeDispatchTest.*': 16 tests passed.
  • Header hygiene and clang-format 16 checks passed.
  • build-support/run-clang-tidy.sh: no findings on changed code (header analyzed with a compile command derived from the new test TU).
  • ./run-regression-test.sh --run -d query_p0/aggregate -s test_numeric_distinct_merge -genOut generated the expected output; rerunning without -genOut passed all 10 result checks and the merge-plan assertion.
  • ./build.sh --be --fe -j 96 completed with ASAN BE and FE Checkstyle enabled.

### What problem does this PR solve?

Issue Number: N/A

Problem Summary: Generic numeric DISTINCT aggregates copy the source hash
set when merging materialized states. Serialized-state merging additionally
populates a temporary hash set. Insert source keys directly and deserialize
numeric keys into the destination, reserving only for an empty destination.
Keep generic StringRef deserialization and arena ownership unchanged.

A standalone phmap merge-kernel microbenchmark with 131072 distinct int64
keys, Clang 21.1.8 and AVX2 measured empty-destination serialized merging
at 9.725 ms before versus 0.944 ms after (nine-run medians). These are not
end-to-end Doris SQL measurements.

### Release note

Reduce temporary hash-set allocations and CPU work when merging generic
numeric DISTINCT aggregate states.

### Check List (For Author)

- Test: 16 ASAN BE unit tests passed; numeric DISTINCT SQL regression
  generated and verified; FE/BE build, header hygiene, clang-format 16,
  and clang-tidy checks passed.
- Behavior changed: No; SQL results and serialized formats are unchanged.
- Does this need documentation: No
@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?

@HappenLee

Copy link
Copy Markdown
Contributor Author

/review Please focus on numeric DISTINCT merge correctness, source-state preservation, direct deserialization dispatch through grouped/selected/ungrouped and Nullable paths, generic StringRef arena ownership, and empty-only reserve behavior for overlapping sets. Local validation passed: 16 ASAN unit tests, SQL regression generation and verification, FE/BE build, header hygiene, clang-format 16, and clang-tidy.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review conclusion: approval. I found no blocking correctness issue or valuable inline finding.

Critical checkpoints:

  • Goal, scope, and tests: the focused change removes the numeric DISTINCT source-container copy and serialized scratch-set population while retaining set semantics. The typed unit tests cover all five integer widths, source preservation, repeat and empty cases, grouped and selected dispatch, both Nullable implementations, and generic string ownership. The regression covers aggregate phases 1 and 2, grouped and ungrouped queries, nullable and non-null inputs, empty and all-null inputs, and AggState merging; the expected arithmetic and NULL results are consistent with the input data.
  • Concurrency and lifecycle: aggregate places and arenas remain operator-local; no new shared mutable state, locks, threads, static initialization, or cross-translation-unit lifetime dependency is introduced. Existing helpers still create and destroy scratch states on success and exception. Generic StringRefs are borrowed only in the scratch state and copied into the destination arena before the reader or scratch state is released.
  • Compatibility and parallel paths: the serialized representation is unchanged. I traced ordinary grouped, selected, ungrouped/range, state-combine/input state-union, legacy Nullable, and Nullable-v2 paths. Numeric payloads consume the same count and fixed-width values directly into the destination; generic single- and multi-argument states retain deserialize-then-arena-copy behavior.
  • Conditions and performance: reserving only for an empty destination is justified because only then is the incoming cardinality the exact final size; nonempty sets may overlap. Direct const iteration preserves the source and avoids the prior full copy, while repeated overlap does not force speculative growth. No obvious hot-path regression remains.
  • Configuration, persistence, transactions, FE/BE protocol, data-write atomicity, and observability: not applicable; this PR adds none of these surfaces and does not require new logging or metrics.

User focus: numeric merge correctness, source preservation, all requested deserialization dispatch variants, generic StringRef ownership, and overlap-aware reserve behavior were all explicitly checked; I found no additional issue in those areas.

Validation note: this was a static review as required by the review runner. I did not rerun builds or tests. The PR author reports 16 ASAN unit tests, SQL regression generation and verification, FE/BE build, header hygiene, clang-format 16, and clang-tidy.

@HappenLee

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17645	4068	4044	4044
q2	2151	363	332	332
q3	10125	1597	800	800
q4	4689	487	367	367
q5	7466	839	559	559
q6	195	176	145	145
q7	774	812	613	613
q8	9344	1502	1627	1502
q9	5505	4264	4198	4198
q10	6842	1319	1008	1008
q11	436	278	243	243
q12	634	432	298	298
q13	18069	2610	2016	2016
q14	262	252	246	246
q15	q16	741	714	660	660
q17	1837	1119	1017	1017
q18	6513	5616	5575	5575
q19	1330	1316	1107	1107
q20	485	407	263	263
q21	5746	3580	3078	3078
q22	457	382	324	324
Total cold run time: 101246 ms
Total hot run time: 28395 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4743	4710	4542	4542
q2	739	582	549	549
q3	5268	5186	4599	4599
q4	2239	2371	1466	1466
q5	4516	4629	4448	4448
q6	226	171	123	123
q7	1825	1708	1502	1502
q8	2398	2020	2042	2020
q9	7388	7327	7213	7213
q10	3617	3559	3090	3090
q11	514	378	345	345
q12	712	706	503	503
q13	2361	2616	1995	1995
q14	263	280	252	252
q15	q16	666	678	597	597
q17	7297	6746	6626	6626
q18	11928	11195	12046	11195
q19	1162	1059	1033	1033
q20	2242	2221	1966	1966
q21	5151	4376	4316	4316
q22	507	441	396	396
Total cold run time: 65762 ms
Total hot run time: 58776 ms

@hello-stephen

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

query5	4314	616	448	448
query6	432	207	208	207
query7	4808	550	293	293
query8	313	180	165	165
query9	8808	3998	3981	3981
query10	454	295	260	260
query11	5774	3539	3234	3234
query12	147	91	87	87
query13	1264	590	379	379
query14	6500	4491	4239	4239
query14_1	3980	3987	3971	3971
query15	203	199	185	185
query16	1007	518	446	446
query17	920	682	551	551
query18	2482	489	343	343
query19	203	183	152	152
query20	90	82	83	82
query21	227	133	114	114
query22	13063	12945	12822	12822
query23	14032	13034	12401	12401
query23_1	12522	12541	12470	12470
query24	7439	1163	720	720
query24_1	670	667	731	667
query25	568	436	376	376
query26	1327	316	179	179
query27	2578	579	354	354
query28	4594	1972	1982	1972
query29	1655	758	532	532
query30	301	218	186	186
query31	907	744	641	641
query32	154	97	98	97
query33	540	307	257	257
query34	1176	1123	624	624
query35	725	796	661	661
query36	800	780	738	738
query37	154	109	93	93
query38	1835	1759	1712	1712
query39	709	686	660	660
query39_1	658	652	659	652
query40	225	122	101	101
query41	71	70	69	69
query42	97	97	94	94
query43	336	343	310	310
query44	1391	721	726	721
query45	190	179	170	170
query46	1088	1196	747	747
query47	1528	1535	1401	1401
query48	396	412	294	294
query49	589	403	294	294
query50	958	348	273	273
query51	10597	10621	10341	10341
query52	98	87	75	75
query53	241	251	177	177
query54	243	204	194	194
query55	81	80	71	71
query56	224	219	238	219
query57	1439	1450	1410	1410
query58	293	261	255	255
query59	1986	2058	1868	1868
query60	275	258	229	229
query61	149	153	148	148
query62	402	319	270	270
query63	219	174	180	174
query64	2791	973	818	818
query65	3471	3394	3403	3394
query66	1803	406	315	315
query67	19863	20099	20035	20035
query68	3182	1527	978	978
query69	419	299	265	265
query70	946	824	843	824
query71	307	234	217	217
query72	2633	2510	2238	2238
query73	832	734	398	398
query74	4620	4488	4294	4294
query75	2294	2251	1940	1940
query76	2328	1104	746	746
query77	363	396	294	294
query78	9186	9145	8519	8519
query79	1314	1204	754	754
query80	641	458	371	371
query81	550	325	280	280
query82	639	160	126	126
query83	274	217	194	194
query84	315	139	112	112
query85	859	473	384	384
query86	329	243	229	229
query87	2005	1984	1829	1829
query88	3580	2734	2709	2709
query89	361	289	251	251
query90	1885	193	178	178
query91	170	157	130	130
query92	106	84	90	84
query93	1504	1467	899	899
query94	542	348	306	306
query95	667	458	354	354
query96	1090	750	354	354
query97	2437	2434	2335	2335
query98	166	149	144	144
query99	735	751	626	626
Total cold run time: 236263 ms
Total hot run time: 153102 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 be1b02e327a0e2fa6dba14ca6955dfd8454ad9d8, data reload: false

query1	0.01	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.14	0.13
query4	1.61	0.13	0.14
query5	0.24	0.22	0.22
query6	1.16	0.94	0.91
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.38	0.33	0.34
query10	0.54	0.60	0.55
query11	0.20	0.14	0.14
query12	0.19	0.15	0.15
query13	0.48	0.46	0.48
query14	0.97	0.94	0.96
query15	0.60	0.57	0.58
query16	0.33	0.33	0.33
query17	1.11	1.08	1.11
query18	0.21	0.20	0.20
query19	2.03	1.98	1.87
query20	0.02	0.01	0.04
query21	15.48	0.22	0.14
query22	4.70	0.05	0.06
query23	16.16	0.31	0.12
query24	3.01	0.44	0.33
query25	0.11	0.05	0.05
query26	0.76	0.21	0.15
query27	0.05	0.04	0.03
query28	3.51	0.80	0.34
query29	12.56	4.03	3.20
query30	0.27	0.16	0.15
query31	2.77	0.54	0.32
query32	3.22	0.59	0.48
query33	3.26	3.16	3.26
query34	15.50	3.90	3.27
query35	3.26	3.23	3.27
query36	0.56	0.44	0.44
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.09	0.03	0.04
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.23 s
Total hot run time: 23.94 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (19/19) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 63.83% (29695/46520)
Line Coverage 48.49% (309240/637763)
Region Coverage 44.01% (249275/566426)
Branch Coverage 45.60% (115913/254210)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (19/19) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.23% (34340/45046)
Line Coverage 61.16% (385592/630487)
Region Coverage 57.57% (324787/564142)
Branch Coverage 58.38% (147947/253423)

@mrhhsg
mrhhsg merged commit f3256fd into apache:master Sep 22, 2026
37 checks passed
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.

3 participants