Skip to content

[fix](fe) Fix TopN lazy materialization for queries ordered by an alias - #68019

Open
englefly wants to merge 1 commit into
apache:masterfrom
englefly:topn-mat-orderkey
Open

englefly wants to merge 1 commit into
apache:masterfrom
englefly:topn-mat-orderkey

Conversation

@englefly

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

SELECT lazy_col AS x, lazy_col AS y FROM t ORDER BY x LIMIT 1 failed planning with
A expression contains slot not from children.

The TopN order key is the alias slot, so that alias has to be computed below the TopN.
MaterializeProbeVisitor only protects the order key slot itself (an order key slot is in
TopN.getInputSlots()) and never resolves an identity alias down to the column the alias reads.
The probe of the other output (lazy_col AS y) therefore resolved to the base column lazy_col
and classified it as lazily materialized, so LazySlotPruning removed lazy_col from the scan while
lazy_col AS x below the TopN still read it. The final Validator rejected the resulting plan and
the query returned an error. With fe_debug=true the failure was caught inside LazyMaterializeTopN
instead, which silently skipped lazy materialization (the query succeeded but lost the optimization).

Reproduction (master, fe_debug=false):

create table t(sort_col int, lazy_col int) duplicate key(sort_col)
  distributed by hash(sort_col) buckets 1 properties('replication_num'='1');
select lazy_col as x, lazy_col as y from t order by x limit 1;
-- ERROR 1105: A expression contains slot not from children
--   Slot: lazy_col#1  Children Output:{0, 4}
--   Plan: PhysicalProject[lazy_col#1 AS x#2, __DORIS_GLOBAL_ROWID_COL__t#4]
--         +--PhysicalLazyMaterializeOlapScan[PhysicalOlapScan[t]]

Fix: LazyMaterializeTopN resolves the TopN order keys through the identity alias chain of the
Projects under the TopN and adds the resolved slots (plus the intermediate alias slots) to
requiredMaterializedSlots, so the probe rejects every lazy candidate backed by a column an order
key reads. The resolution stops at set operations, which the probe never materializes through
(lazy materialization is not supported through set operations today; if that ever changes, order
keys have to be resolved per branch).

Effect: affected plans now either keep only the ordering column materialized (other columns are
still fetched lazily) or skip lazy materialization, and the plan stays valid. Plans that order by a
plain column are unchanged.

Release note

TopN lazy materialization no longer builds an invalid plan (no more
A expression contains slot not from children) when a query orders by an alias of a column.
The column that feeds the order key is materialized during the scan, while other columns keep using
lazy materialization.

Check List (For Author)

  • Test
    • Regression test (regression-test/suites/query_p0/topn_lazy/order_by_alias)
    • Unit Test (TopnLazyMaterializeTest, LazyMaterializeTopNTest)
  • Behavior changed:
    • Yes. Queries that order by an alias of a projected column no longer fail planning; the
      ordering column is kept materialized instead of being pruned from the scan.
  • Does this need documentation?
    • No.

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

Problem Summary:

`SELECT lazy_col AS x, lazy_col AS y FROM t ORDER BY x LIMIT 1` failed with
"A expression contains slot not from children".

The TopN order key is the alias slot, so that alias has to be computed below the
TopN. However `MaterializeProbeVisitor` only protects the order key slot itself
(an order key slot is in `TopN.getInputSlots()`) and never resolves an identity
alias down to the column the alias reads. The probe of the other output
(`lazy_col AS y`) therefore resolved to the base column `lazy_col` and classified
it as lazily materialized, and `LazySlotPruning` removed `lazy_col` from the scan
while `lazy_col AS x` below the TopN still read it. The final `Validator` rejected
the resulting plan, so the query returned an error. When `fe_debug=true`, the
failure was caught inside `LazyMaterializeTopN` instead, which silently skipped
lazy materialization (the query succeeded but lost the optimization).

Fix: `LazyMaterializeTopN` now resolves the TopN order keys through the identity
alias chain of the Projects under the TopN and adds the resolved slots (and the
intermediate alias slots) to `requiredMaterializedSlots`. The probe then rejects
every lazy candidate backed by a column that an order key reads, so the plan stays
valid. The resolution stops at set operations, which the probe never materializes
through (lazy materialization does not cross set operations today; if that is ever
extended, the order keys have to be resolved per set operation branch).

Only the ordering column is forced to be materialized: other columns are still
fetched lazily, and plans that order by a plain column are unchanged.

Reproduction on master:

```sql
create table t(sort_col int, lazy_col int) duplicate key(sort_col)
  distributed by hash(sort_col) buckets 1 properties('replication_num'='1');
set fe_debug = false;
select lazy_col as x, lazy_col as y from t order by x limit 1;
-- ERROR 1105: A expression contains slot not from children
--   Slot: lazy_col#1  Children Output:{0, 4}
```

### Release note

TopN lazy materialization no longer builds an invalid plan (no more
"A expression contains slot not from children") when a query orders by an alias of
a column. The ordering column is materialized during the scan, while other columns
keep using lazy materialization.

### Check List (For Author)

- Test
    - [x] Regression test (regression-test/suites/query_p0/topn_lazy/order_by_alias)
    - [x] Unit Test (TopnLazyMaterializeTest, LazyMaterializeTopNTest)
- Behavior changed: Yes. Queries that order by an alias no longer fail planning;
  the column feeding the order key stays materialized.
- Does this need documentation: No
@englefly

Copy link
Copy Markdown
Contributor Author

run buildall

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

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100.00% (26/26) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 59.52% (25/42) 🎉
Increment coverage report
Complete coverage report

@englefly

Copy link
Copy Markdown
Contributor Author

run performance

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17586	3043	3040	3040
q2	2099	251	229	229
q3	10222	892	513	513
q4	4673	253	205	205
q5	7666	584	387	387
q6	138	113	93	93
q7	530	503	409	409
q8	9231	927	913	913
q9	3486	2453	2414	2414
q10	6538	842	727	727
q11	391	200	181	181
q12	608	259	196	196
q13	18149	1554	1170	1170
q14	167	157	140	140
q15	q16	440	408	379	379
q17	1386	865	767	767
q18	3100	2330	2307	2307
q19	1125	946	767	767
q20	384	288	203	203
q21	5223	1612	1881	1612
q22	330	271	233	233
Total cold run time: 93472 ms
Total hot run time: 16885 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3372	3311	3314	3311
q2	507	400	373	373
q3	2300	2403	2250	2250
q4	1222	1195	900	900
q5	2272	2177	2164	2164
q6	173	127	88	88
q7	1067	953	900	900
q8	1604	1417	1421	1417
q9	3240	3230	3212	3212
q10	1942	1851	1696	1696
q11	360	273	262	262
q12	457	438	340	340
q13	1491	1517	1169	1169
q14	171	179	153	153
q15	q16	398	399	362	362
q17	3700	3355	3272	3272
q18	4983	4628	4995	4628
q19	981	853	868	853
q20	1039	1005	843	843
q21	3874	3193	3235	3193
q22	404	367	328	328
Total cold run time: 35557 ms
Total hot run time: 31714 ms

@hello-stephen

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

query5	4266	417	346	346
query6	389	139	130	130
query7	4934	422	232	232
query8	293	125	118	118
query9	8683	2924	2912	2912
query10	404	234	192	192
query11	5383	1054	941	941
query12	127	77	78	77
query13	1194	440	321	321
query14	6045	2289	2154	2154
query14_1	2039	2012	1986	1986
query15	178	122	114	114
query16	927	371	354	354
query17	792	461	353	353
query18	2336	341	237	237
query19	169	138	125	125
query20	76	73	72	72
query21	205	100	88	88
query22	5582	5593	5414	5414
query23	6818	6471	6227	6227
query23_1	6197	6169	6354	6169
query24	7254	1110	770	770
query24_1	773	799	799	799
query25	430	298	261	261
query26	1237	222	128	128
query27	2798	428	249	249
query28	4686	1489	1491	1489
query29	928	444	353	353
query30	280	158	130	130
query31	816	401	337	337
query32	128	74	74	74
query33	451	201	183	183
query34	996	827	485	485
query35	390	407	361	361
query36	611	561	536	536
query37	118	86	74	74
query38	1014	870	864	864
query39	518	498	489	489
query39_1	458	471	514	471
query40	202	90	80	80
query41	53	51	56	51
query42	80	73	76	73
query43	242	242	212	212
query44	979	531	543	531
query45	111	111	104	104
query46	762	802	535	535
query47	762	787	726	726
query48	309	309	218	218
query49	558	244	183	183
query50	726	267	190	190
query51	8067	7845	7931	7845
query52	66	71	61	61
query53	190	195	159	159
query54	216	210	156	156
query55	71	61	54	54
query56	177	178	177	177
query57	688	676	640	640
query58	183	164	164	164
query59	1273	1267	1122	1122
query60	239	204	175	175
query61	108	96	123	96
query62	351	199	191	191
query63	168	141	137	137
query64	2832	682	542	542
query65	1695	1616	1667	1616
query66	1810	253	211	211
query67	10007	9928	9645	9645
query68	3007	1234	673	673
query69	353	223	206	206
query70	677	646	601	601
query71	253	179	168	168
query72	2255	1685	1521	1521
query73	664	625	354	354
query74	2003	1244	1170	1170
query75	1187	1123	981	981
query76	2371	721	495	495
query77	255	243	216	216
query78	4052	3739	3300	3300
query79	2601	918	600	600
query80	1556	323	276	276
query81	482	162	132	132
query82	628	123	94	94
query83	285	210	194	194
query84	297	114	90	90
query85	787	341	280	280
query86	389	179	176	176
query87	1036	980	921	921
query88	2801	2089	2093	2089
query89	295	197	176	176
query90	1971	127	127	127
query91	127	114	97	97
query92	79	70	68	68
query93	1783	1085	681	681
query94	632	262	224	224
query95	520	244	303	244
query96	843	619	257	257
query97	1078	1116	1031	1031
query98	161	135	133	133
query99	422	343	310	310
Total cold run time: 179121 ms
Total hot run time: 82644 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.08	0.04	0.04
query3	0.25	0.11	0.11
query4	1.60	0.10	0.09
query5	0.17	0.16	0.15
query6	1.26	0.69	0.67
query7	0.04	0.01	0.01
query8	0.05	0.04	0.03
query9	0.28	0.22	0.23
query10	0.35	0.38	0.35
query11	0.17	0.12	0.11
query12	0.15	0.13	0.12
query13	0.32	0.31	0.32
query14	0.46	0.45	0.47
query15	0.37	0.36	0.35
query16	0.21	0.23	0.23
query17	0.72	0.73	0.70
query18	0.19	0.18	0.17
query19	1.28	1.14	1.18
query20	0.02	0.01	0.01
query21	15.46	0.15	0.12
query22	5.10	0.04	0.04
query23	16.14	0.26	0.11
query24	2.96	0.32	0.26
query25	0.12	0.04	0.02
query26	0.76	0.17	0.13
query27	0.03	0.03	0.04
query28	3.59	0.58	0.28
query29	12.42	3.19	2.56
query30	0.26	0.12	0.13
query31	2.75	0.38	0.17
query32	3.52	0.33	0.23
query33	1.40	1.43	1.45
query34	15.40	2.27	1.83
query35	1.85	1.78	1.76
query36	0.46	0.30	0.29
query37	0.06	0.04	0.04
query38	0.04	0.03	0.02
query39	0.04	0.02	0.02
query40	0.12	0.07	0.07
query41	0.07	0.02	0.03
query42	0.03	0.02	0.02
query43	0.03	0.03	0.03
Total cold run time: 90.58 s
Total hot run time: 14.86 s

@englefly

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 of exact head 4ea274aa90f586ad6fddf064d939672685d957be found one correctness blocker and one test-coverage gap.

The direct ORDER BY-alias case is fixed, but identity-alias bases consumed by descendant operators are still not protected: a reachable nested-TopN plan can prune a scan slot that a retained inner ordering alias reads, and the final validator rejects the query. The new index-mode regression cases also remain green on the old implementation and therefore do not prove that parallel path. Details and concrete repros are inline.

Critical checkpoint conclusions:

  • Goal and correctness: incomplete because the descendant-consumer case still creates an invalid plan; the direct current-TopN case is sound.
  • Scope and structure: the change is focused, and the postprocessor ordering through the final Validator is coherent.
  • Parallel paths and tests: traced default and index modes, local/merge and nested TopNs, Projects, joins, filters, pruning boundaries, and final validation. Direct non-index validity and unrelated-column laziness are covered; descendant consumers and a pre-fix-sensitive index shape are not.
  • Compatibility and lifecycle: no FE/BE protocol, rolling-upgrade, persistence, transaction, configuration-definition, concurrency, locking, security, or resource-lifecycle change is involved.
  • Performance and observability: the added traversal is linear over the local subtree and raises no material performance concern; no observability change is needed. The follow-up should close only identity-alias dependencies rather than force every descendant input eager.
  • User focus: no additional focus was supplied, so the complete authoritative patch and all changed files were reviewed.

This was a static-only review as required: no source edits, builds, or tests were run. The live PR base/head and all comment surfaces were rechecked immediately before submission; existing exact-head CI reports FE UT, P0 regression, compile, coverage, and performance success, with the automated code-review status pending.

private void collectOrderKeyColumns(PhysicalTopN<? extends Plan> topN, Set<Slot> requiredMaterializedSlots) {
Map<Slot, Slot> aliasToChild = new HashMap<>();
collectIdentityAliasMap(topN.child(), aliasToChild);
for (OrderKey orderKey : topN.getOrderKeys()) {

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] Protect alias bases consumed below this TopN Resolving only this TopN's order keys leaves the same invalid-plan bug for descendant TopNs. For example, select y from (select lazy_col AS x, lazy_col AS y, other_col AS z from topn_lazy_order_by_alias_tbl order by x limit 2) s order by z limit 1 preserves an outer TopN(z) -> ... -> TopN(x) -> Project(a AS x, a AS y, b AS z) -> Scan(a,b). This loop protects only z -> b; probing y can still mark a lazy, after which pruning removes scan slot a while retaining a AS x for the inner TopN, and the final Validator rejects the plan when fe_debug=false. Please close identity-alias dependencies for retained descendant consumers/already-required slots (stopping at the same boundaries) and add this nested regression.

// The same shapes through the inverted-index filter path.
sql """ set topn_lazy_materialization_using_index = true; """

order_qt_using_index_repeated_alias """

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] Exercise an index-mode shape that failed before this patch These two index-mode queries are pre-fix-insensitive: when topn_lazy_materialization_using_index is enabled, MaterializeProbeVisitor.visitPhysicalProject refuses an identity Alias as a lazy source and eagerly requires its child, so both alias-heavy shapes already keep lazy_col materialized on the old code. A sensitive case is select lazy_col AS x, lazy_col, other_col from topn_lazy_order_by_alias_tbl where sort_col > 0 order by x limit 1: before this change the bare lazy_col can be pruned while the retained alias still reads it, whereas the fix should protect it and leave other_col lazy. Please add this shape (with the validator enabled), plus a plan assertion if this suite is intended to prove selective laziness rather than only successful execution.

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