Skip to content

[feature](fe) Push down limit into CTE producer #63675

Open
CalvinKirs wants to merge 4 commits into
apache:masterfrom
CalvinKirs:cte_limit_push_down
Open

[feature](fe) Push down limit into CTE producer #63675
CalvinKirs wants to merge 4 commits into
apache:masterfrom
CalvinKirs:cte_limit_push_down

Conversation

@CalvinKirs
Copy link
Copy Markdown
Member

This PR adds CTE producer-side limit pushdown in Nereids.

When all CTE consumers only need a bounded number of rows, the optimizer collects the required row count from each consumer, takes
the maximum value, and pushes that limit into the CTE producer. The original consumer-side limit is still kept.

The rule only handles safe shapes:

LogicalLimit
  LogicalCTEConsumer
LogicalLimit
  LogicalProject
    LogicalCTEConsumer

The project must be row-preserving.

Scenarios

1. Direct Limit

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte
LIMIT 10;

The consumer only needs 10 rows, so the CTE producer can produce at most 10 rows.

2. Project + Limit

WITH cte AS (
    SELECT order_id, total_price, user_id FROM orders
)
SELECT order_id, total_price
FROM cte
LIMIT 10;

A normal project only prunes columns and does not change row count, so the producer can still be limited to 10 rows.

3. Multiple Consumers + Limit

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte LIMIT 10
UNION ALL
SELECT * FROM cte LIMIT 20;

For multiple CTE consumers, the producer limit is:

producerLimit = max(consumerLimit1, consumerLimit2, ...)

In this case, the pushed producer limit is 20.

If any consumer needs full CTE data, pushdown is skipped:

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte LIMIT 10
UNION ALL
SELECT * FROM cte;

4. Limit + Offset

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte
LIMIT 10 OFFSET 100;

The consumer needs to skip 100 rows and then return 10 rows, so the producer must provide at least 110 rows.

The producer side only truncates rows and does not apply offset:

producerLimit = limit + offset
producerOffset = 0

5. SplitLimit

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte
LIMIT 10 OFFSET 100;

Doris may split this into local/global limits. The local limit closest to the CTE consumer already represents limit + offset.

The collector uses the local limit value directly and does not add offset again.

6. Filter + Limit Is Not Matched

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte
WHERE order_id > 10
LIMIT 10;

Filter can reduce rows before limit, so the producer may need more than 10 input rows. This rule does not push limit through filter.

7. TopN Is Not Matched

WITH cte AS (
    SELECT * FROM orders
)
SELECT * FROM cte
ORDER BY order_id
LIMIT 10;

ORDER BY ... LIMIT is TopN. It needs the first N rows after ordering, so it cannot be treated as a normal limit.

8. Join / Aggregate / Window / Sort Are Not Matched

WITH cte AS (
    SELECT * FROM orders
)
SELECT *
FROM cte JOIN users ON cte.user_id = users.user_id
LIMIT 10;
WITH cte AS (
    SELECT * FROM orders
)
SELECT user_id, COUNT(*)
FROM cte
GROUP BY user_id
LIMIT 10;
WITH cte AS (
    SELECT * FROM orders
)
SELECT *
FROM (
    SELECT order_id, ROW_NUMBER() OVER (ORDER BY order_id) AS rn
    FROM cte
) t
LIMIT 10;
WITH cte AS (
    SELECT * FROM orders
)
SELECT *
FROM (
    SELECT * FROM cte ORDER BY order_id
) t
LIMIT 10;

These operators can change row cardinality or ordering semantics. Unless other rules have already rewritten the shape into Limit -> CTEConsumer or Limit -> Project -> CTEConsumer, this collector skips them.

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Push row limits from CTE consumers into the CTE producer when every consumer is bounded by a plain row-preserving limit, using the maximum rows needed across consumers.

### Release note

None

### Check List (For Author)

- Test: Unit Test

    - env MAVEN_OPTS=-Xmx4g -XX:MaxMetaspaceSize=1g ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CollectLimitAboveConsumerTest,org.apache.doris.nereids.rules.rewrite.RewriteCteChildrenLimitPushdownTest

- Behavior changed: Yes. Adds CTE producer limit pushdown for bounded CTE consumers.

- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Add planner-level coverage for CTE limit pushdown boundaries, including offset handling, producer output pruning, max rows across limited consumers, full-row consumers, and filter boundaries.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - env MAVEN_OPTS=-Xmx4g -XX:MaxMetaspaceSize=1g ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CteLimitPushdownPlanTest
    - env MAVEN_OPTS=-Xmx4g -XX:MaxMetaspaceSize=1g ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CollectLimitAboveConsumerTest,org.apache.doris.nereids.rules.rewrite.RewriteCteChildrenLimitPushdownTest,org.apache.doris.nereids.rules.rewrite.CteLimitPushdownPlanTest
    - Red check: temporarily disabled producer-side limit construction; CteLimitPushdownPlanTest failed 3 positive cases as expected.
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Add planner and regression coverage for CTE limit pushdown edge cases described in the design, including local split limits, non-row-preserving consumers, and nonmatching Filter/TopN/Join/Aggregate/Window shapes.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CollectLimitAboveConsumerTest
    - Unit Test: ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CteLimitPushdownPlanTest
    - Unit Test: ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.RewriteCteChildrenLimitPushdownTest
    - Regression test: ./run-regression-test.sh --run --conf /tmp/cte_limit_pushdown_regression-conf.groovy -d nereids_rules_p0/cte_limit_pushdown -s test_cte_limit_pushdown
- Behavior changed: No
- 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?

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Remove a redundant row-preserving project check after the pattern guard and keep the maximum collected CTE consumer limit when the same consumer is collected multiple times.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.CollectLimitAboveConsumerTest
    - Regression test: ./run-regression-test.sh --run --conf /tmp/cte_limit_pushdown_regression-conf.groovy -d nereids_rules_p0/cte_limit_pushdown -s test_cte_limit_pushdown
- Behavior changed: No
- Does this need documentation: No
@CalvinKirs
Copy link
Copy Markdown
Member Author

run buildall

@CalvinKirs
Copy link
Copy Markdown
Member Author

/review

@hello-stephen
Copy link
Copy Markdown
Contributor

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

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17579	4123	4067	4067
q2	q3	10780	1343	832	832
q4	4687	474	345	345
q5	7652	2279	2108	2108
q6	244	176	137	137
q7	938	781	653	653
q8	9409	1741	1571	1571
q9	5161	4995	5001	4995
q10	6405	2208	1880	1880
q11	436	281	252	252
q12	627	423	291	291
q13	18095	3335	2778	2778
q14	258	258	247	247
q15	q16	816	772	708	708
q17	995	957	911	911
q18	7046	5874	5473	5473
q19	1639	1282	1220	1220
q20	576	467	304	304
q21	6149	2828	2711	2711
q22	619	372	322	322
Total cold run time: 100111 ms
Total hot run time: 31805 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4868	4829	4819	4819
q2	q3	4929	5277	4614	4614
q4	2132	2223	1438	1438
q5	5035	4868	4714	4714
q6	243	178	126	126
q7	1886	1825	1584	1584
q8	2441	2145	2136	2136
q9	7849	7472	7454	7454
q10	4808	4663	4217	4217
q11	553	388	358	358
q12	748	754	540	540
q13	2994	3400	2808	2808
q14	276	280	257	257
q15	q16	678	703	628	628
q17	1295	1258	1272	1258
q18	7333	6934	6846	6846
q19	1104	1092	1124	1092
q20	2228	2216	2016	2016
q21	5300	4639	4489	4489
q22	510	469	402	402
Total cold run time: 57210 ms
Total hot run time: 51796 ms

@hello-stephen
Copy link
Copy Markdown
Contributor

TPC-DS: Total hot run time: 172322 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 c948725cdd794972b87abfcae8fce484f0a39b21, data reload: false

query5	4304	652	519	519
query6	325	226	199	199
query7	4242	615	321	321
query8	326	233	214	214
query9	8849	4037	4032	4032
query10	462	366	307	307
query11	5809	2451	2255	2255
query12	204	123	127	123
query13	1277	596	420	420
query14	6076	5483	5197	5197
query14_1	4488	4497	4484	4484
query15	211	202	181	181
query16	975	447	413	413
query17	933	720	578	578
query18	2427	473	353	353
query19	211	202	157	157
query20	144	133	130	130
query21	211	137	118	118
query22	13704	13492	13465	13465
query23	17223	16582	16215	16215
query23_1	16395	16413	16396	16396
query24	7523	1778	1317	1317
query24_1	1346	1292	1350	1292
query25	598	504	451	451
query26	1317	334	175	175
query27	2675	578	346	346
query28	4441	1976	1982	1976
query29	1022	649	529	529
query30	306	238	203	203
query31	1143	1082	972	972
query32	87	81	76	76
query33	546	366	306	306
query34	1177	1143	651	651
query35	770	823	696	696
query36	1380	1427	1255	1255
query37	154	107	96	96
query38	3207	3187	3040	3040
query39	933	932	885	885
query39_1	870	878	871	871
query40	241	154	127	127
query41	71	68	69	68
query42	110	117	109	109
query43	329	330	297	297
query44	
query45	219	212	204	204
query46	1098	1251	732	732
query47	2345	2386	2232	2232
query48	412	421	326	326
query49	658	504	391	391
query50	998	387	251	251
query51	4379	4246	4269	4246
query52	103	105	91	91
query53	253	274	212	212
query54	312	284	275	275
query55	92	89	83	83
query56	297	304	295	295
query57	1422	1425	1356	1356
query58	307	277	266	266
query59	1611	1719	1502	1502
query60	315	326	312	312
query61	161	155	158	155
query62	701	667	587	587
query63	246	200	200	200
query64	2408	795	622	622
query65	
query66	1717	478	355	355
query67	29685	29732	29604	29604
query68	
query69	454	342	308	308
query70	1033	1028	981	981
query71	316	266	263	263
query72	3014	2745	2506	2506
query73	866	744	431	431
query74	5093	4961	4784	4784
query75	2720	2609	2261	2261
query76	2293	1131	761	761
query77	401	424	326	326
query78	12372	12570	11996	11996
query79	1279	1033	763	763
query80	572	547	475	475
query81	444	278	247	247
query82	237	160	119	119
query83	262	280	250	250
query84	285	137	111	111
query85	848	536	455	455
query86	370	347	336	336
query87	3444	3386	3281	3281
query88	3552	2711	2697	2697
query89	427	399	347	347
query90	2186	189	181	181
query91	177	171	148	148
query92	80	77	73	73
query93	1522	1541	809	809
query94	510	332	306	306
query95	682	464	346	346
query96	1037	798	350	350
query97	2730	2736	2611	2611
query98	235	230	229	229
query99	1230	1144	1038	1038
Total cold run time: 252482 ms
Total hot run time: 172322 ms

@hello-stephen
Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 31.25% (40/128) 🎉
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.

2 participants