Skip to content

[fix](be) Use serialized hash keys for complex types - #66777

Open
Mryange wants to merge 1 commit into
apache:masterfrom
Mryange:fix-group-by-fixed-width-struct
Open

[fix](be) Use serialized hash keys for complex types#66777
Mryange wants to merge 1 commit into
apache:masterfrom
Mryange:fix-group-by-fixed-width-struct

Conversation

@Mryange

@Mryange Mryange commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary: Multi-column hash keys could incorrectly classify fixed-width complex types such as STRUCT as fixed keys. Because ColumnStruct stores its fields in separate child columns, it cannot be packed from a contiguous raw buffer. This change routes multi-column keys containing complex types through the serialized-key path, fixing GROUP BY and distinct aggregate failures involving fixed-width STRUCT columns.

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

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Multi-column hash keys classified fixed-width STRUCT columns as fixed keys based only on their maximum in-memory size. ColumnStruct stores fields in separate child columns, so fixed-key packing either rejected the computed key size or attempted to read a non-contiguous raw buffer. Use the existing serialized-key path whenever a multi-column key contains a complex type.

### Release note

Fix GROUP BY and DISTINCT aggregate failures when a grouped key contains a fixed-width STRUCT column.

### Check List (For Author)

- Test: Regression test
    - ./run-regression-test.sh --run -d datatype_p0/complex_types -s test_group_by_fixed_width_struct
    - Unit Test: Added HashKeyTypeTest coverage; not run because the existing BEUT binary predates the new test file
- Behavior changed: Yes, complex types in multi-column hash keys use serialized keys
- 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?

@Mryange

Mryange commented Aug 14, 2026

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 status: Changes requested

I found one blocking data-correctness issue: the new serialized path does not preserve Doris FLOAT/DOUBLE equality at every hash boundary for fixed-width STRUCT keys. See the inline comment for the concrete signed-zero INTERSECT failure.

Critical checkpoints:

  • Goal and proof: The change correctly prevents fixed-key raw-buffer packing for integer fixed-width STRUCT values, and the selector/unit cases plus deterministic GROUP BY/DISTINCT regression cover that repaired path. The goal is not complete for STRUCT values containing FLOAT/DOUBLE because semantically equal representations can still hash differently.
  • Scope and clarity: The implementation change is small and focused, and ordinary scalar fixed-key selection remains unchanged.
  • End-to-end and parallel paths: Aggregation, streaming/bucketed/distinct aggregation, joins, set operations, partition sort, recursive-CTE deduplication, and complex hash-map dictionaries all use the central selector. Exchange/local partition hashing and several local serialized-key consumers use raw nested float bits, which is the accepted issue.
  • Concurrency, lifecycle, memory, and errors: This is initialization-time method selection with no new shared mutable state, locks, static lifecycle, or error-suppression path. Existing serialized arenas retain key storage through their consumers; no separate ownership issue was found.
  • Compatibility, configuration, persistence, and writes: No protocol/storage-format, rolling-upgrade, configuration, transaction, persistence, or FE-BE variable change is introduced.
  • Tests: The BE unit is discovered by the test CMake glob and directly exercises selector behavior; the commit records the targeted regression as run. The new tests use only integral STRUCT fields and miss signed zero/NaN across set/hash-distribution paths. This review runner did not build or run tests, per its instructions.
  • Performance and observability: Scalar fixed-key fast paths are preserved; serialized keys are necessary for non-contiguous STRUCT storage. No separate performance regression or new metric/log requirement was substantiated.
  • User focus: No additional review focus was supplied.

Review completion: Converged in Round 2. One inline finding is accepted; its corroborating candidate was deduplicated, and no other suspicious point remains unresolved.


for (const auto& data_type : data_types) {
if (!data_type->have_maximum_size_of_value()) {
if (is_complex_type(data_type->get_primitive_type()) ||

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 FLOAT equality at every STRUCT hash boundary

This newly sends a multi-column key such as (id, STRUCT<DOUBLE>) through serialized-key consumers, but nested floats are not canonicalized at every hash boundary. ColumnStruct preserves raw IEEE bits in both exchange CRC hashing and MethodSerialized, so (1, {-0.0}) and (1, {+0.0}) can route to different set instances or produce different local StringRefs even though CompareFloat() defines them as equal; INTERSECT can consequently drop the matching row (distinct NaN payloads have the same issue). Please canonicalize early enough for exchange partitioning and every local build/probe/partition/dedup path to consume the same semantic representation, and add a multi-key STRUCT float regression.

@Mryange

Mryange commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

/review 重新review 下, flaot这个问题过去就有 ,我们倾向于搞一个单独的pr。

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

Automated review result: no new valuable findings. This is a comment-only review with zero new inline comments.

Round 1 converged: both normal full-scope reviewers and the separate risk-focused reviewer returned NO_NEW_VALUABLE_FINDINGS, and the main review independently resolved every recorded risk.

Critical checkpoint conclusions:

  • Goal and correctness: the change correctly prevents multi-column fixed-width STRUCT keys from entering MethodKeysFixed, whose raw-buffer packing is incompatible with ColumnStruct's non-contiguous child-column layout. The existing typed serialized path preserves nullable markers, build/probe equality, owning-arena lifetimes, and output reconstruction.
  • Scope and clarity: the production change is a focused selector guard that reuses existing infrastructure. ARRAY and MAP were already serialized, nullable types delegate their primitive classification, single complex keys were already serialized, and scalar/VARIANT/fixed-size routing remains unchanged.
  • Parallel paths and error handling: aggregation (ordinary, streaming, bucketed, spill/phase 2), distinct/recursive CTE, hash join, INTERSECT/EXCEPT, and partition-sort variants all support HashKeyType::serialized. Their insertion, probe, output, and exception-to-Status paths were traced; no incompatible or silently failing path was found.
  • Concurrency and lifecycle: no new thread, lock, atomic, static initialization, or shared-state lifecycle is introduced. Serialized build keys are copied into or retained by the existing owning arenas; transient probe buffers are not persisted.
  • Configuration, compatibility, persistence, and writes: no configuration, FE-BE protocol, storage format, EditLog, transaction, or data-write behavior changes. Hash-key representation remains operator-local, so there is no rolling-upgrade wire-format dependency.
  • Performance and observability: serialization adds the necessary per-row arena work only for fixed-width STRUCT layouts that cannot safely use fixed packing. No new untracked allocation, pathological loop, long-running operation, log, or metric requirement was found.
  • Tests and results: the selector unit test is glob-registered by BE test CMake; current PR checks show BE UT (macOS) and Clang Formatter passing. The focused regression covers the formerly invalid multi-key GROUP BY layouts plus three DISTINCT aggregate paths, uses deterministic ordering, and its checked-in cardinalities match the inserted rows. The commit records the focused regression command as run; this review did not rerun builds/tests because the runner instructions prohibit builds.

User focus — FLOAT: I re-reviewed the raw IEEE equality concern. It already exists in the unchanged single-STRUCT serialized path (and related pre-existing hash boundaries), is already covered by the existing inline thread, and the user prefers a separate PR. I found no distinct FLOAT regression or other equality bug to comment on here, so I did not duplicate that thread.

Review completion status: converged in Round 1; no unresolved candidates and no new inline findings.

@Mryange

Mryange commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17570	3070	3048	3048
q2	2076	229	154	154
q3	10336	914	525	525
q4	4669	246	202	202
q5	7683	564	386	386
q6	139	117	92	92
q7	529	517	387	387
q8	9268	831	929	831
q9	3473	2387	2390	2387
q10	6513	861	711	711
q11	472	255	237	237
q12	708	403	327	327
q13	17881	2035	1560	1560
q14	166	144	135	135
q15	q16	449	402	374	374
q17	769	769	761	761
q18	3133	2314	2283	2283
q19	1253	939	844	844
q20	651	529	438	438
q21	5653	1756	1965	1756
q22	337	265	223	223
Total cold run time: 93728 ms
Total hot run time: 17661 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3455	3385	3352	3352
q2	210	214	159	159
q3	2247	2331	2161	2161
q4	1213	1195	893	893
q5	2228	2195	2144	2144
q6	173	126	90	90
q7	1047	969	874	874
q8	1606	1408	1395	1395
q9	3204	3165	3178	3165
q10	1894	1864	1652	1652
q11	361	276	260	260
q12	460	441	342	342
q13	1839	1869	1546	1546
q14	167	172	166	166
q15	q16	400	412	358	358
q17	1048	1030	1028	1028
q18	5130	4489	4905	4489
q19	1088	822	852	822
q20	1008	970	1016	970
q21	3717	3086	3269	3086
q22	399	341	318	318
Total cold run time: 32894 ms
Total hot run time: 29270 ms

@hello-stephen

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

query5	4229	403	330	330
query6	428	162	149	149
query7	4883	467	274	274
query8	311	117	110	110
query9	8691	2868	2869	2868
query10	388	253	219	219
query11	5409	1049	954	954
query12	117	76	68	68
query13	1209	477	326	326
query14	5958	2059	1908	1908
query14_1	1814	1810	1810	1810
query15	193	121	114	114
query16	969	359	355	355
query17	815	491	365	365
query18	2374	335	239	239
query19	175	147	134	134
query20	68	68	69	68
query21	209	121	97	97
query22	5519	5423	5451	5423
query23	7288	6789	6614	6614
query23_1	6790	6750	6777	6750
query24	7349	1057	771	771
query24_1	748	781	771	771
query25	418	281	242	242
query26	1253	273	166	166
query27	2713	448	282	282
query28	4629	1484	1500	1484
query29	992	426	336	336
query30	283	175	152	152
query31	980	663	609	609
query32	120	49	45	45
query33	479	204	164	164
query34	1041	878	486	486
query35	407	385	339	339
query36	559	537	513	513
query37	131	87	71	71
query38	1021	862	812	812
query39	533	555	500	500
query39_1	517	504	523	504
query40	228	126	108	108
query41	53	51	53	51
query42	75	75	75	75
query43	249	247	219	219
query44	1033	563	578	563
query45	109	109	100	100
query46	790	836	511	511
query47	995	974	926	926
query48	314	322	234	234
query49	531	238	187	187
query50	808	331	252	252
query51	8107	7977	8128	7977
query52	67	68	60	60
query53	213	235	156	156
query54	229	166	163	163
query55	78	56	55	55
query56	218	218	210	210
query57	715	632	588	588
query58	224	194	186	186
query59	1080	1091	958	958
query60	246	210	182	182
query61	114	114	110	110
query62	402	202	172	172
query63	189	162	154	154
query64	2706	708	569	569
query65	1628	1604	1586	1586
query66	1947	312	245	245
query67	10345	9832	9947	9832
query68	2777	1209	714	714
query69	362	226	188	188
query70	645	573	578	573
query71	286	251	265	251
query72	2377	1737	1547	1547
query73	703	574	357	357
query74	1612	1247	1162	1162
query75	1244	1165	1024	1024
query76	2324	737	542	542
query77	246	252	198	198
query78	5165	4972	4632	4632
query79	1286	832	560	560
query80	710	400	334	334
query81	437	192	170	170
query82	614	134	101	101
query83	320	247	227	227
query84	329	121	103	103
query85	874	433	390	390
query86	385	172	177	172
query87	1037	992	913	913
query88	2781	2139	2124	2124
query89	320	228	204	204
query90	1761	144	139	139
query91	152	141	119	119
query92	49	44	41	41
query93	1240	1156	803	803
query94	528	246	217	217
query95	645	367	427	367
query96	805	605	290	290
query97	1079	1076	1086	1076
query98	152	141	137	137
query99	461	360	307	307
Total cold run time: 178864 ms
Total hot run time: 85922 ms

@hello-stephen

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

query1	0.01	0.00	0.00
query2	0.08	0.04	0.04
query3	0.26	0.10	0.11
query4	1.60	0.11	0.09
query5	0.17	0.15	0.17
query6	1.16	0.70	0.67
query7	0.04	0.01	0.00
query8	0.05	0.03	0.04
query9	0.31	0.20	0.21
query10	0.34	0.38	0.34
query11	0.16	0.12	0.12
query12	0.14	0.11	0.12
query13	0.30	0.29	0.31
query14	0.47	0.49	0.46
query15	0.37	0.35	0.34
query16	0.23	0.22	0.25
query17	0.74	0.67	0.69
query18	0.18	0.16	0.16
query19	1.16	1.11	1.18
query20	0.01	0.01	0.01
query21	15.47	0.15	0.12
query22	5.02	0.04	0.04
query23	16.15	0.27	0.10
query24	3.35	0.32	0.26
query25	0.14	0.04	0.03
query26	0.74	0.16	0.12
query27	0.04	0.04	0.04
query28	3.59	0.53	0.27
query29	12.45	3.15	2.64
query30	0.26	0.11	0.12
query31	2.76	0.38	0.17
query32	3.50	0.33	0.23
query33	1.41	1.51	1.43
query34	15.40	2.16	1.79
query35	1.75	1.73	1.74
query36	0.47	0.29	0.28
query37	0.07	0.04	0.04
query38	0.04	0.04	0.03
query39	0.04	0.03	0.02
query40	0.11	0.08	0.08
query41	0.08	0.02	0.02
query42	0.04	0.03	0.03
query43	0.04	0.03	0.02
Total cold run time: 90.7 s
Total hot run time: 14.71 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 60.19% (26594/44181)
Line Coverage 44.70% (271327/607028)
Region Coverage 40.40% (216511/535941)
Branch Coverage 41.92% (99559/237474)

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.

2 participants