pingcap / pingcap/tidb

planner,distsql: avoid forcing cop concurrency=1 for CLUSTER_SLOW_QUERY TopN->TableReader->Limit path

Open
#66,352 1 comment 0 reactions 0 assignees View on GitHub
type/enhancement
Dominant language
Go
Stars
40.5k
Forks
6.2k
PR merge metrics
PR metrics pending

Description

## Enhancement

For `information_schema.cluster_slow_query` queries like:

```sql
SELECT ...
FROM information_schema.cluster_slow_query
WHERE ...
ORDER BY time DESC
LIMIT 100;
```

TiDB v8.5 may produce a plan shape like:
```
mysql> EXPLAIN ANALYZE
-> SELECT
-> Digest,
-> Query,
-> Conn_ID,
-> Query_time,
-> Mem_max
-> FROM information_schema.cluster_slow_query
-> ORDER BY Time DESC
-> LIMIT 100;
+-----------------------------+---------+---------+-----------+--------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+---------+
| id | estRows | actRows | task | access object | execution info | operator info | memory | disk |
+-----------------------------+---------+---------+-----------+--------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+---------+
| Projection_7 | 100.00 | 52 | root | | time:8.33ms, open:1.61ms, close:8.54µs, loops:2, RU:0.69, Concurrency:OFF | information_schema.cluster_slow_query.digest, information_schema.cluster_slow_query.query, information_schema.cluster_slow_query.conn_id, information_schema.cluster_slow_query.query_time, information_schema.cluster_slow_query.mem_max | 16.5 KB | N/A |
| └─TopN_9 | 100.00 | 52 | root | | time:8.3ms, open:1.6ms, close:6.92µs, loops:2 | information_schema.cluster_slow_query.time:desc, offset:0, count:100 | 18.3 KB | 0 Bytes |
| └─TableReader_18 | 100.00 | 52 | root | | time:6.68ms, open:86.7µs, close:5.79µs, loops:3, cop_task: {num: 1, max: 6.52ms, proc_keys: 0, copr_cache_hit_ratio: 0.00, max_distsql_concurrency: 1}, fetch_resp_duration: 6.55ms, rpc_info:{Cop:{num_rpc:1, total_time:6.48ms}} | data:Limit_17 | 13.9 KB | N/A |
| └─Limit_17 | 100.00 | 0 | cop[tidb] | | | offset:0, count:100 | N/A | N/A |
| └─MemTableScan_16 | 100.00 | 0 | cop[tidb] | table:CLUSTER_SLOW_QUERY | | | N/A | N/A |
+-----------------------------+---------+---------+-----------+--------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+---------+
5 rows in set (0.01 sec)

```
This is semantically correct, but the current execution often runs with max_distsql_concurrency = 1, which can under-utilize multi-TiDB fanout and hurt latency.

### Why this happens (code-level explanation)
1. Planner rewrite for CLUSTER_SLOW_QUERY
* In task.go, attach2Task4PhysicalTopN calls pushLimitDownToTiDBCop for TiDB cop tasks.
* pushLimitDownToTiDBCop applies a special rewrite for CLUSTER_SLOW_QUERY: it sets TableScan.Desc, TableScan.KeepOrder, and pushes a PhysicalLimit below TableReader.
2. Root TopN is intentionally kept
* In find_best_task.go, matchProperty does not treat cluster table scan as directly satisfying global sort property.
* So the planner keeps root TopN for global ordering.
3. keepOrder here is not global ORDER BY merge across TiDB nodes
* Cluster fanout tasks are built per TiDB server in coprocessor.go(buildTiDBMemCopTasks).
* keepOrder consumption in cop iterator is task-order consumption, not a multi-source merge by time.
* Therefore root TopN is still required to produce globally correct order.
4. Small-limit heuristic forces low concurrency
* In request_builder.go, RequestBuilder.SetDAGRequest applies the “simple scan + small limit => minimal concurrency” rule.
* For this rewritten DAG shape, request concurrency can be set to 1 (or partition count).
* SetFromSessionVars only sets default or caps upper bound; it does not raise this value back.
* Result: even with multiple TiDB fanout tasks, runtime may show max_distsql_concurrency: 1.

### Why this is suboptimal in this scenario
Even with cop-side local limit, root TopN still needs candidate rows from all TiDB sources (no global early-stop guarantee from input order).
So forcing concurrency to 1 can serialize fanout processing and lose useful parallelism.

This is a performance strategy issue, not a correctness bug.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.