englefly commented on code in PR #66898:
URL: https://github.com/apache/doris/pull/66898#discussion_r4089175753
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java:
##########
@@ -620,6 +637,22 @@ public Statistics computeOlapScan(OlapScan olapScan) {
return computeVirtualColumnStats(olapScan, builder.build());
}
+ /**
+ * Returns the slots whose column stats should be fetched for the query.
+ *
+ * <p>Only operative slots' column stats are needed by the query, column
stats of other slots are
+ * useless and fetching them would pollute the column stats cache and
waste time on wide tables.
+ * If operative slots are not derived yet (e.g. stats derivation during
RBO) or full stats
+ * fidelity is required (forbidUnknownColStats), fall back to all output
slots.
+ */
+ private List<Slot> getStatsNeededSlots(OlapScan olapScan) {
+ if (forbidUnknownColStats) {
Review Comment:
Fixed. The state of the derivation is now kept apart from the slot list:
`CatalogRelation#isOperativeSlotsDerived()` records it, set by the
`withOperativeSlots` of the relations that derive them (`LogicalOlapScan`,
`LogicalOlapTableStreamScan`, `LogicalFileScan`), and `LogicalPlanDeepCopier`
no longer marks a copy as derived when the plan it copied from was not derived.
`getStatsNeededSlots` now fetches every output slot only when full stats
fidelity is required (`forbid_unknown_col_stats`, which has to load the
statistics to tell "no statistics" from "not loaded") and when the operative
slots have not been derived yet. A derived empty list is neither of those: it
means the query needs no column of the relation, so nothing is fetched.
Coverage:
*
`StatsCalculatorTest#testComputeOlapScanWithDerivedEmptyOperativeSlotsFetchesNoColumnStats`:
a scan with two output columns and a derived empty operative list triggers no
column stats load at all, while the same scan without the derivation still
fetches both columns (the pre-existing fallback).
*
`OperativeColumnDeriveTest#testPhysicalResultSinkDoesNotMarkOutputAsOperativeSlots`
now also asserts that the derived empty list is marked as derived.
* `shape_check/tpcds_sf100` (395 suites) passes with `fe_debug = true`.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java:
##########
@@ -609,7 +619,14 @@ public Statistics computeOlapScan(OlapScan olapScan) {
} else {
// get table level stats
for (SlotReference slot : visibleOutputSlots) {
- ColumnStatistic cache =
olapTableStats.getColumnStatistics(slot.getName(), connectContext);
+ ColumnStatistic cache;
+ if (!statsNeededSlots.contains(slot)) {
Review Comment:
Addressed in the follow-up PR that this branch stacks on, which is about the
width of columns whose statistics are not fetched. There, a slot the query does
not need is unknown and as wide as its data type
(`ColumnStatistic#createUnknownByDataType`) instead of being read from the
cache, so its width no longer depends on which columns another query happened
to load.
`StatisticsCache#getColumnStatisticsIfPresent` is removed by that change,
which also covers the cache probe issue below.
It is kept out of this PR on purpose: it is an estimation/width change, not
a change of which statistics are fetched. It moves the join order of
`shape/query4`, `shape/query14`, `rf_prune/query4`, `rf_prune/query14` and
`noStatsRfPrune/query24`, whereas this PR alone changes no plan shape: 395/395
suites of `shape_check/tpcds_sf100` pass, deterministically with and without
the fuzzy session variables.
##########
fe/fe-core/src/main/java/org/apache/doris/statistics/cache/StatisticsCache.java:
##########
@@ -136,6 +155,28 @@ private ColumnStatistic doGetColumnStatistics(
return ColumnStatistic.UNKNOWN;
}
+ /**
+ * Returns the column statistic only when it is already present in the
cache, without
+ * triggering a cache load. Used for columns whose stats are not needed by
the query,
+ * so that useless columns do not pollute the cache nor issue queries to
the stats table.
+ */
+ private ColumnStatistic doGetColumnStatisticsIfPresent(
+ long catalogId, long dbId, long tblId, long idxId, String colName,
ConnectContext ctx) {
+ StatisticsCacheKey k = new StatisticsCacheKey(catalogId, dbId, tblId,
idxId, colName);
+ CompletableFuture<Optional<ColumnStatistic>> f =
columnStatisticsCache.getIfPresent(k);
Review Comment:
Covered by the follow-up width change:
`StatisticsCache#getColumnStatisticsIfPresent` and
`doGetColumnStatisticsIfPresent` are removed there, so the `refreshAfterWrite`
-> `afterRead` -> loader path and the access policy side effect of the probe
cannot happen any more. No quiet replacement probe is needed, because the
caller no longer reads the cache for such a slot at all: it uses unknown
statistics that carry the width of the data type.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java:
##########
@@ -678,6 +685,13 @@ public class Rewriter extends AbstractBatchJobExecutor {
topDown(new PushDownAggThroughJoinOnPkFk()),
topDown(new PullUpJoinFromUnionAll())
),
+ // RBO rules that depend on statistics (e.g. InitJoinOrder,
SkewJoin, Eager
+ // aggregation, DecomposeRepeatWithPreAggregation,
DistinctAggStrategySelector)
+ // must be placed AFTER OperativeColumnDerive:
StatsCalculator.computeOlapScan
+ // only fetches column stats of operative slots, so rules
running before the
+ // derivation would fetch stats of all table columns,
polluting the column stats
+ // cache and wasting time on wide tables.
+ custom(RuleType.OPERATIVE_COLUMN_DERIVE,
OperativeColumnDerive::new),
Review Comment:
Added. The rewriter now derives the operative slots before the "Set
operation optimization" topic, i.e. before `InferSetOperatorDistinct`, which is
the first rewrite rule that derives statistics (`StatsDerive` on a set
operation whose statistics are missing).
The derivation is repeated before "Reorder join before eager aggregation"
and at the end of rewrite, so the passes are now: materialized view pre
rewrite, this one, before the eager aggregation topic, and the end of rewrite.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java:
##########
@@ -415,6 +415,13 @@ public class Rewriter extends AbstractBatchJobExecutor {
topDown(new LimitSortToTopN()),
topDown(new SplitLimit()),
custom(RuleType.SET_PREAGG_STATUS,
SetPreAggStatus::new),
+ // Derive operative columns on the plan
recorded for materialized view
+ // pre rewrite: the pre rewrite runs a
cost-based optimization on this
+ // recorded plan to choose the best
materialized view, and without
+ // operative slots computeOlapScan would
fall back to fetching column
+ // stats of all table columns. The
derivation is repeated before "init
+ // join" and at the end of rewrite, where
the operative slots of the
+ // plans finally stored into the memo are
recomputed.
custom(RuleType.OPERATIVE_COLUMN_DERIVE,
OperativeColumnDerive::new),
Review Comment:
Fixed in `RewriteCteChildren#visitLogicalCTEAnchor`: the operative slots of
the producer are derived before the prerequisite `StatsDerive`, and on the plan
whose statistics are computed and which is rewritten afterwards, so those
statistics are not attached to a discarded copy of the producer.
This covers the ordinary CTE path and the materialized view pre rewrite,
which share this method.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java:
##########
@@ -826,6 +841,11 @@ public class Rewriter extends AbstractBatchJobExecutor {
)
),
topDown(new CollectCteConsumerOutput()),
+ // Re-derive operative columns at the end of rewrite: rules
after the early
+ // OperativeColumnDerive (before "init join") may rebuild
scans or add virtual
+ // columns (e.g. stream scan normalization, variant virtual
column push down),
Review Comment:
Added: the rewriter derives the operative slots once more right after the
"Table/Physical optimization" topic, which contains
`NormalizeOlapTableStreamScan`, and therefore before the "set initial join
order" topic that runs `SkewJoin`. The pass at the end of rewrite is kept for
the rules that add columns afterwards (variant virtual column push down).
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Rewriter.java:
##########
@@ -690,6 +690,13 @@ public class Rewriter extends AbstractBatchJobExecutor {
topDown(new PushDownAggThroughJoinOnPkFk()),
topDown(new PullUpJoinFromUnionAll())
),
+ // RBO rules that depend on statistics (e.g. InitJoinOrder,
SkewJoin, Eager
+ // aggregation, DecomposeRepeatWithPreAggregation,
DistinctAggStrategySelector)
+ // must be placed AFTER OperativeColumnDerive:
StatsCalculator.computeOlapScan
+ // only fetches column stats of operative slots, so rules
running before the
+ // derivation would fetch stats of all table columns,
polluting the column stats
+ // cache and wasting time on wide tables.
+ custom(RuleType.OPERATIVE_COLUMN_DERIVE,
OperativeColumnDerive::new),
Review Comment:
补一下这轮改动后的最终状态。现在共 5 遍
`OperativeColumnDerive`,确实都不能省;而且为了保证中间的统计消费者不会退回"取全列统计",还新增/前移了两遍:
1. MV pre-rewrite(MV job list 内):MV 选型会对记录下来的 plan 跑一次 CBO,没有 operative
slots 就会退回全列取统计;
2. **新增**在 "Set operation optimization" 之前:`InferSetOperatorDistinct` 是
rewrite 期第一个统计消费者(缺统计时会调 `StatsDerive`);
3. 原来那遍:rebase 到最新 master 后,上游已经把旧的 "init join" topic 换成了 "Reorder join
before eager aggregation"(`ReorderJoinBeforeEagerAgg`),这遍就挂在新 topic 之前,给依赖统计的
RBO 规则用;
4. **新增**在 "Table/Physical optimization" 之后:该 topic 里的
`NormalizeOlapTableStreamScan` 会重建扫描节点(新节点 operative 为空),而 `SkewJoin` 在后面的 "set
initial join order" topic 里会对没有统计的孩子推导统计;
5. rewrite 末尾:variant 虚拟列下推等规则会新增列,需要最终重算,供 CBO 统计推导和 BE lazy
materialization 使用。
另外,为了让 `select count(*) from wide_table`
这种"推导结果本来就是空"的情况一列统计都不取,`getStatsNeededSlots` 现在用 `isOperativeSlotsDerived()`
区分"已推导且为空"和"还没推导",不再靠列表是否为空来判断。
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]