土法炼钢兴趣小组的算法知识备份

【分布式 OLAP 查询引擎】统计信息与代价模型

文章导航

分类入口
databasedistributed
标签入口
#statistics#cost-model#ndv#histogram#analyze#trino#cbo#cardinality-estimate#iceberg#puffin

目录

CBO(Cost-Based Optimizer)在 第 3 篇 的规则链之后做 数值决策:选哪种 join 算法、哪种 join 顺序、是否 broadcast 小表。决策输入是 基数估计(cardinality)代价常量;输出是 estimated rows / cost 列在 EXPLAIN 里。

本文拆解 OLAP 引擎(Trino 476+ 主线)的统计来源与代价组成,并对照 postgresql-kernel 第 10 篇pg_statistic。文末用 DuckDB 1.5.4 本机 plan 中的 Cardinality 说明估算如何进入计划;Trino 有/无 ANALYZE 的对比实验需 Docker 栈,本机未跑时不伪造 Trino 数字。

环境:WSL2、i9-12900K;DuckDB 1.5.4 实测;无 Trino JVM。Trino 机制引用官方 Cost-based optimizations(476+)。


一、CBO 在优化链中的位置

flowchart TB
  RULES["Logical / physical rules"]
  STATS["Statistics snapshot"]
  COST["CostCalculator"]
  WIN["Pick lowest cost plan"]
  RULES --> COST
  STATS --> COST
  COST --> WIN
无统计时 典型后果
默认 row count 常数 join 顺序随机或固定启发式
NDV 未知 hash table 内存预估偏差
分区 min/max 缺失 分区裁剪失败 → 全表扫

第 5 篇 的 broadcast vs shuffle 阈值直接依赖 build 侧 estimated size


二、统计对象:表与列

统计量 符号/字段 用途
Row count \(N_R\) 扫描基数起点
NDV \(D_c\) 等值谓词选择率 \(\approx 1/D_c\)
Null fraction \(f_{null}\) IS NULL / join 语义
Min / Max \(min_c, max_c\) 范围谓词、分区裁剪
Histogram 等深/等宽桶 非均匀分布
Average width 字节宽 内存与 network 估算

2.1 选择率合成

多谓词 AND 常假设 ** independence**(与 PG clauselist_selectivity 类似):

\[ sel(p_1 \land p_2) \approx sel(p_1) \times sel(p_2) \]

相关列(city + zipcode)会 严重低估或高估;PG 用 CREATE STATISTICS;Trino/Iceberg 侧部分依赖 Puffin 扩展统计(Iceberg spec,A 级)。

2.2 Join 基数

等值 join 常用:

\[ |R \bowtie S| \approx \frac{|R| \cdot |S|}{\max(D_{R.k}, D_{S.k})} \]

NDV 估错时 中间结果数量级错误,导致 错误 join 顺序(先放大再过滤 vs 先过滤再 join)。


三、统计来源

来源 机制 典型引擎
ANALYZE 采样写 catalog PG、Trino Hive connector
Connector layout split 时文件 row count Iceberg manifest
Table property 外部维护 手工 CALL system.register_table
Runtime 动态反馈 Spark AQE(B 级对照)

3.1 Trino ANALYZE

Trino 对支持 analyze properties 的 connector 提供 ANALYZE(文档 ANALYZE,476+)。写入 column statistics 到 metastore 或 connector 元数据,供下次 planning 读取。

本机限制reproduce/docker-compose.yml 未在本写作环境执行;不编造 「ANALYZE 前后 estimated rows 从 X 变 Y」的 Trino 片段。复现步骤见 reproduce/run_trino_experiments.shlakehouse reproduce

3.2 Iceberg 与湖上统计

层级 内容 lakehouse/18 关系
Manifest per-file record count、column bounds file pruning
Puffin 外部 stats blob NDV、histogram 可选
Partition spec transform 列 partition pruning

引擎 不扫描全表 即可从 manifest 汇总 分区级 row count;这是湖仓相对裸 Parquet 的关键优势(lakehouse/08)。


四、代价模型组成

Trino CostCalculator(概念,476+ 文档 Cost-based optimizations)将计划节点代价分解为:

分量 含义 分布式特有
CPU 表达式、hash、比较 各节点加总
IO 扫描字节/页 connector 延迟
Network Exchange shuffle 数据量 MPP 核心

\[ Cost(plan) = w_{cpu} \cdot CPU + w_{io} \cdot IO + w_{net} \cdot Network \]

权重 \(w_*\) 可通过 session property 或 config 调整(具体 property 名以 476+ 文档为准)。

4.1 与 PostgreSQL 对照

PG GUC / 概念 Trino 类比
seq_page_cost / random_page_cost scan CPU + IO 权重
cpu_tuple_cost 表达式 CPU
effective_cache_size connector 侧较少显式建模
network cost(PG 无 shuffle)

PG 第 10 篇rows=1 灾难 在 Trino 体现为 estimated 1 row → nested loop + 实际百万行 shuffle


五、DuckDB 1.5.4:计划中的 Cardinality

本机 run_duckdb_experiments.py(100k orders,5000 customer):

查询 计划要点 估算基数
q_filtero_custkey = 3 SEQ_SCAN + filter ~14286\(\approx 100000/7\)
q_join:日期 filter + join + agg HASH_JOIN,scan 带 o_orderdate>=1996-06-01 filter 后 ~20000

DuckDB 在 优化阶段 用表统计(generate_series 合成数据亦有内置估计)写入 plan Cardinality 字段。这与 Trino EXPLAINEstimates: rows=... 角色相同;数值 不可跨引擎对比(算法与假设不同)。

实测 wall time(q_join,EXPLAIN ANALYZE 3 次中位数):0.0028 s——说明 估计与执行在小数据上一致,不代表 Trino 在 TB 级 Iceberg 上的行为。


六、统计过期与估算误差

flowchart LR
  STALE["Stats stale"] --> BAD["Bad cardinality"]
  BAD --> WRONG["Wrong join order"]
  WRONG --> SCAN["Full table scan"]
  WRONG --> BOOM["Huge intermediate"]
  WRONG --> BC["Bad broadcast"]
症状 可能统计原因 运维动作
Iceberg 全文件 scan 分区 transform 谓词不可推导 检查 predicate on partition column
Broadcast 巨大表 OOM build 侧 row 低估 更新 ANALYZE / 调 threshold
Shuffle 远大于预期 join NDV 低估 对比 EXPLAIN ANALYZE actual rows
计划频繁变 无 stats 默认常量 启用 analyze 或 manifest stats

Trino EXPLAIN ANALYZE 对比 estimated vs actual 是生产第一排查手段(第 17 篇)。


七、Histogram 与 NDV 精度

等深直方图(equi-depth):每桶行数相近,桶边界记录值域;范围查询用 桶重叠比例 估选择率。

NDV:HyperLogLog 或采样估计;小样本下 distinct 低估 会导致 join 结果 高估(保守)或 低估(激进),取决于引擎公式。

Iceberg Puffin 可存储 sketches;connector 是否读取取决于 Trino Iceberg 版本实现(第 15 篇)。


八、代价 vs 真实延迟

指标 反映什么 不反映什么
EXPLAIN cost 优化器内部比较 用户 wall time
EXPLAIN ANALYZE 实际 CPU/row 排队、metastore 冷启动
DuckDB 0.0028s 本地内存 scan 跨 AZ network

优化器选「最低 cost plan」是在模型内最优,不是全局 SLA 最优;resource group、并发 spill 会改变实际表现(第 16 篇)。


九、诊断清单

  1. SHOW STATS FOR / information_schema(connector 支持时)。
  2. EXPLAINEstimates 是否全为默认值(如 -1 或固定常数)。
  3. EXPLAIN ANALYZEInput rows vs Planned rows 比率。
  4. 湖表:对照 lakehouse/18layout constraint 是否出现。
  5. 必要时 ANALYZE 或重写 SQL(谓词写在 partition 列上)。

十、小结

统计信息把 「这张表有多大、这列有多散」 变成数字,代价模型把 算法与 shuffle 体积 变成可比较的 cost。湖格式把 file-level stats 推到 planning 入口,减少全表采样依赖;但 ** independence 假设与过期 stats** 仍是 join 计划崩溃的主因。

下一篇进入 Join 重排与物理算子选择:left-deep vs bushy、broadcast vs partitioned join、DPP。


参考资料

  1. Trino Documentation, Cost-based optimizations / ANALYZE(476+)。
  2. Apache Iceberg Specification, Metrics / Puffin
  3. postgresql-kernel 第 10 篇(pg_statistic)。
  4. DuckDB 1.5.4,reproduce/run_duckdb_experiments.py(Cardinality 与 timing)。
  5. 第 3 篇(CBO 位置)。
  6. lakehouse 第 18 章(manifest stats 与 pruning)。

返回 系列目录 | 上一篇:Calcite 与优化框架 | 下一篇:Join 规划

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .