优化器把逻辑计划变成物理计划后,执行往往从 TableScan 开始。lakehouse 第 18 章 从 读湖漏斗 讲了 partition/file/row-group 裁剪;本篇从 Trino 476+ 执行与 SPI 回答:Coordinator 如何把 Scan 切成 Split、connector 如何只读必要 列、哪些 Filter/Project 必须留在引擎里。
一、TableScan 在 pipeline 中的位置
Trino worker 上,TableScanOperator(A
级:TableScanOperator.java)是
SourceOperator:只实现
getOutput(),通过
ConnectorPageSourceProvider.createPageSource(...)
拉 Page(第 6
篇)。
flowchart TB
COORD["Coordinator<br/>schedule splits"]
TS["TableScanOperator"]
CPS["ConnectorPageSource"]
STO["Object store / DB"]
COORD -->|"Split assignment"| TS
TS --> CPS
CPS --> STO
TS --> FIL["FilterOperator"]
FIL --> PRJ["ProjectOperator"]
| 组件 | 职责 |
|---|---|
| ConnectorSplitManager | 把表切成可并行处理的 Split |
| ConnectorPageSourceProvider | 给定 split + 列列表,产生 Page 流 |
| ConnectorMetadata | planning 阶段接受 下推(filter / project / limit / topN) |
Scan 本身不做 SQL 表达式求值;表达式在 FilterOperator / ProjectOperator 或已下推到 connector 的 handle 里完成。
二、ConnectorSplit:并行与 locality
ConnectorSplit(SPI)是 worker
上并行度的基本单位。ConnectorSplitManager.getSplits(...)
返回一批 split,Coordinator 调度到 Task(Trino Documentation
Connectors — ConnectorSplitManager)。
2.1 Split 粒度因 connector 而异
| 数据源 | Split 典型含义 | Locality |
|---|---|---|
| Hive / Iceberg | 一个或一组数据文件 | getAddresses() 提示对象存储前缀 /
缓存节点 |
| JDBC | 按分区或 LIMIT 切分 |
常无 affinity |
| TPCH connector | 合成 split | 测试用 |
| 单机内存 | 整表或行范围 | N/A |
Split 越小,并行度越高,但 scheduling 与元数据开销越大;Iceberg 常在 file 级切 split,并依赖 manifest 已裁掉的文件列表(第 15 篇)。
2.2 Dynamic Filter 与 Scan
TableScanOperator 构造
PageSource 时可传入
DynamicFilter(runtime filter):build 侧
join 运行中产生的 bloom/min-max 谓词,缩小 probe 端 scan(与
第 5
篇 DPP 衔接)。这是 执行期 对 Split
内数据的二次过滤,不改变 Split 个数。
sequenceDiagram
participant HB as Hash Build side
participant DF as DynamicFilter
participant TS as TableScan
HB->>DF: publish domain
TS->>DF: getCurrentPredicate()
TS->>TS: skip rows / pages in split
三、Column Pruning(Projection Pushdown)
Projection pushdown:优化器从
SELECT、join key、谓词列收集
必要列,经
ConnectorPageSourceProvider.createPageSource(..., List<ColumnHandle> columns, ...)
传给 connector(Trino Documentation Pushdown —
Projection pushdown)。
| 阶段 | 谁决定列集 | 效果 |
|---|---|---|
| Analyzer | 解析 SELECT * 展开 |
逻辑列依赖 |
| Optimizer | 裁剪未引用列 | 更新 TableScanNode 列列表 |
| Connector | 只读文件内对应列 | 减少 IO / 解压 |
Parquet / ORC 上 pruning 在 文件列 chunk 级生效;列存与行存 JDBC 源的能力差异极大,需查各 connector 文档。
验证:EXPLAIN 中
TableScan 的 columns
列表应只剩必要列;Iceberg 场景还可对照 lakehouse/18
的 scanned bytes。
四、谓词与 Limit 下推
Trino 在 planning 时反复调用
ConnectorMetadata(A 级:Trino Documentation
Connectors):
| 方法 | 下推内容 | 返回 |
|---|---|---|
applyFilter |
TupleDomain 谓词 |
新 ConnectorTableHandle 或 empty |
applyProjection |
列映射 | 新 handle + 投影 |
applyLimit |
LIMIT n |
无排序要求 |
applyTopN |
ORDER BY ... LIMIT |
排序下推 |
成功下推后,handle 携带
约束;getSplits 与
createPageSource 只接触
缩小后的布局。Trino 文档说明:若 connector
不支持某下推,应返回
Optional.empty(),引擎保留对应
Filter / Sort /
Limit 算子。
4.1 读湖时的两层裁剪
| 层 | 发生位置 | 与 Scan 关系 |
|---|---|---|
| Layout 裁剪 | Coordinator / Metadata | fewer splits(manifest、partition) |
| Row group / page | Worker / PageSource | 单 split 内跳过 row group |
| 引擎 Filter | FilterOperator | 无法下推的谓词、非确定性函数 |
lakehouse/18 的①②层在 打开文件前;③④在 PageSource 内;仍无法表达的谓词才出现在 FilterOperator。
五、下推边界:何时必须留在 Scan 之上
下列情况通常 不能 完全下推,引擎保留 Filter / Project:
| 类别 | 示例 | 原因 |
|---|---|---|
| 非确定性 | WHERE ts > now() |
语义随执行时刻变 |
| 非内置函数 | UDF、部分 connector 未映射函数 | SPI 无等价 |
| 跨行语义 | 窗口、EXISTS 相关子查询未改写 |
需引擎算子 |
| 类型/ collation | 与远端类型规则不一致 | 正确性 |
| Security | Row filter 与 column mask | 引擎强制 |
| 表达式不在列上 | WHERE a + b > 10 且源不支持 expr
pushdown |
Project + Filter |
Trino 文档 Pushdown 建议用
EXPLAIN 对比:若 aggregate
已下推,计划中 不出现 引擎侧
Aggregate;同理 Filter 下推成功后,remote 扫描
handle 带约束而 无多余 Filter 节点(取决于
EXPLAIN 格式版本)。
5.1 Lazy Materialization
优化器可能先只读 filter 列 + key
列,命中后再读 payload 列(lazy /
late materialization)。这在 Trino 与 ClickHouse
PREWHERE 思想相近(columnar-engine
读路径):减少无关列进入 CPU cache。是否启用取决于
planner 规则与 connector 是否支持
只读子集列 的两阶段读。
5.2 Dictionary Block 上的 Filter
Connector 若返回 DictionaryBlock(第
7 篇),Filter 可先对 字典
求值,再通过索引过滤,避免展开全量字符串。引擎内
FilterOperator 需识别 block
编码;下推失败时仍可在引擎侧做 dictionary-aware 过滤。
六、FilterOperator 与 ProjectOperator
| 算子 | 输入 → 输出 | 典型工作 |
|---|---|---|
| FilterOperator | Page → Page(行数减少) | 对保留谓词 batch 求值 |
| ProjectOperator | Page → Page(列变换) | 表达式、cast、列重排 |
二者在 pipeline 中常紧挨 Scan;若谓词已下推,Filter 节点 被消除 或只做 残余谓词。Project 在 join 前可能多次出现(列裁剪、算 join key)。
Limit pushdown 与 TopN
pushdown 的边界:仅 LIMIT 无
ORDER BY 时可用
applyLimit,结果顺序
任意;带排序必须 applyTopN
或引擎 TopN 算子(Trino
Pushdown)。
七、与 PostgreSQL 的对照
| 机制 | PostgreSQL | Trino |
|---|---|---|
| 扫描单位 | SeqScan 逐行 |
Split + Page |
| 列裁剪 | Bitmap / index only scan(有限) |
connector projection |
| 谓词下推 | storage / FDW pushdown |
ConnectorMetadata.applyFilter |
| 并行 | parallel seq scan | 多 Driver + 多 Worker |
PG 执行器篇
中 SeqScan 在 ExecQual 做
filter;Trino 把 能下推的 qual 尽量放进
TableHandle,减少 Page 过网络与算子链。
八、排查清单
EXPLAINScan 节点:列数是否仍含未引用列?→ projection 未生效或SELECT *。- Splits 数量:相对表文件数是否异常多/少?→ 分区谓词、manifest 裁剪(lakehouse/08 manifest)。
- Filter 节点残留:谓词是否含不支持函数或类型?→ 改 SQL 或换 connector 能力。
EXPLAIN ANALYZEPhysical Input:相对表总行数;与 lakehouse/18 漏斗对照。
九、边界
参考资料
| 类型 | 来源 |
|---|---|
| 文档 | Trino 476+ — Connectors(SplitManager, PageSourceProvider, Metadata pushdown) |
| 文档 | Trino 476+ — Pushdown(projection, filter, TopN) |
| 源码 | trinodb/trino —
TableScanOperator.java,
ConnectorPageSourceProvider SPI |
| 系列 | lakehouse/18 查询引擎读湖 |
系列导航:系列索引 · 上一篇:向量化与 morsel-driven · 下一篇:Hash Join 与 Hash Aggregation
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【分布式 OLAP 查询引擎】Iceberg 下推全链路:Planner 视角
与 lakehouse/18 分工:那边讲四层读湖漏斗是什么;本篇讲 Trino/Spark/DuckDB 在 SQL 优化链的哪一步把谓词变成 layout constraint、谁调用 Iceberg planning、split 如何携带残余谓词。引用官方文档与 lakehouse/18 本机 PyIceberg 实测,不伪造 Trino 计划输出。
【分布式 OLAP 查询引擎】Trino · Spark · DuckDB · 优化与 MPP 执行
闭合数据平台栈最后一块:从 SQL 解析与 Calcite 式优化,到 Volcano/向量化执行、Trino Coordinator/Worker 与 shuffle,再到 Iceberg connector 下推与生产排查。承接 lakehouse 第 18 章读湖视角,补全「谁在做 planning」的引擎内核层。
【分布式 OLAP 查询引擎】Calcite 与规则/代价优化框架
RelNode、Convention、Trait 与 Rule-based 改写链;VolcanoPlanner/Cascades memo 搜索;Trino 476+ 自研 planner 与 Calcite 借鉴边界,对照 Spark Catalyst 优化阶段。
【分布式 OLAP 查询引擎】统计信息与代价模型
Table/column 统计、NDV 与 histogram、Iceberg Puffin/manifest 统计来源;Trino CostCalculator 与 CPU/IO/network 权重;统计过期导致全表扫与 join 中间结果膨胀;对照 PostgreSQL pg_statistic。