第 16 篇 讲完内存账户与 spill;第 15 篇 讲完 planner 如何把谓词交给 Iceberg。上线后 BI 用户最常提的三句话是:「为什么全表扫?」「为什么就这一个 task 慢?」「为什么 Coordinator 又 OOM 了?」——三类问题分别对应 planning 裁剪失败、runtime 倾斜、内存治理,排查入口不同。
本文是系列 第 17 篇,收束 五类经典故障:每类给出 触发条件 → 观测信号 → 止血 → 长期修复,并指向系列前文的机制依据。
环境说明:本机 无 Trino/Spark 集群。工具字段与配置项按 Trino 476+、Spark 3.5+ 官方文档核对;不粘贴未执行的 Web UI 截图或伪造 task 耗时。DuckDB 单机排查可复用
EXPLAIN ANALYZE(第 14 篇)。
一、排查总览:先分 Planning 还是 Runtime
flowchart TD
SLOW["查询慢 / 失败"] --> Q1{"结果是否正确?"}
Q1 -->|错误| CORR["SQL 语义 / 连接器 bug"]
Q1 -->|正确| Q2{"Scan 输入是否远大于预期?"}
Q2 -->|是| PLAN["Planning:统计 / 下推 / 谓词"]
Q2 -->|否| Q3{"Stage 内 task 耗时是否极不均匀?"}
Q3 -->|是| SKEW["Runtime:倾斜 / straggler"]
Q3 -->|否| RES["资源:内存 / spill / 队列"]
| 阶段 | 典型工具 | 本篇章节 |
|---|---|---|
| Planning | EXPLAIN、EXPLAIN ANALYZE
layout/input rows |
二 |
| Shuffle | Stage 概览、task raw input rows | 三 |
| Memory | Query info、spilled、kill reason | 四 |
| Straggler | Task timeline、split 数 | 五 |
| Catalog | Coordinator log、REST/HMS 超时 | 六 |
二、全表扫:统计、分区谓词、Connector 未下推
2.1 触发条件
- Iceberg/Hive 表 无分区谓词 或谓词
不可折叠(函数包裹列、
now()非确定性)。 - 统计缺失:CBO 认为全表扫描更便宜(第 4 篇)。
- Connector 未把 filter 传进
applyFilter/ IcebergplanFiles()(第 15 篇)。 - File pruning 失效:数据布局导致 min/max
全覆盖(lakehouse/18
实验:
country='CN'仍 10/10 文件)。
2.2 观测信号
| 引擎 | 看什么 |
|---|---|
| Trino | Scan input rows /
input size vs 表规模;layout
partition constraint 是否为空 |
| Spark | PartitionFilters /
PushedFilters 为空 |
| DuckDB | Total Files Read 等于表文件总数 |
| 通用 | PyIceberg plan_files() 文件数(lakehouse/18
方法) |
2.3 止血与修复
- SQL:改写为裸列 op 常量;时间戳统一 UTC(lakehouse/18 附录 L)。
- 策略:Trino
iceberg.query-partition-filter-required对大表强制分区过滤(文档 Iceberg connector)。 - 数据:sort/z-order compaction 改善 file pruning(lakehouse/17)。
- 统计:
ANALYZE/ Iceberg Puffin NDV(改善 join 计划,不替代 manifest 裁剪)。
三、大 Shuffle 与数据倾斜
3.1 触发条件
- 高基数 join key → partitioned join 输出极大中间结果(第 5 篇)。
- Skewed key:少数 key 占大部分行(第 11 篇)。
- Broadcast 误判:统计过期以为小表,实际超大广播(第 4 篇)。
- Spark AQE 未生效(如 subquery 边界、非 shuffle join)。
3.2 观测信号
- Trino
EXPLAIN ANALYZE:各 task raw input rows 数量级差异(同 stage)。 - Spark UI:Shuffle Read Records 单 task 尖峰。
- Plan:PartitionedOutput vs BroadcastExchange 选择是否符合预期。
3.3 止血与修复
- SQL:过滤后再 join;避免
SELECT *大宽表 shuffle。 - Spark
AQE:
spark.sql.adaptive.skewJoin.enabled(文档 3.5+;版本边界以官方为准)。 - Trino:无 AQE 同等物时靠 统计刷新 + 手动 hint/重写(如 pre-agg、map join 语义改写);倾斜 key 可 salted join(工程手段,需验证语义)。
- 长期:键分布治理、分桶表设计(lakehouse 分区与 bucket spec)。
四、OOM 与 Spill 失败
4.1 触发条件
- 超过
query.max-memory/query.max-total-memory(第 16 篇)。 - Broadcast 多副本 + 大维表。
- Spill 磁盘满 或 spill 吞吐低于分配速度。
- Coordinator heap 过小(元数据 planning 大表 manifest 过多)。
4.2 观测信号
- Query 状态
Exceeded memory limit;Worker log Spill failed。 EXPLAIN ANALYZE:Spilled: true 且耗时激增(需真实集群观察)。- JVM OutOfMemoryError: Java heap space on Coordinator(与 query limit 区分)。
4.3 止血与修复
- 立即:kill 查询;临时调低并发 resource group。
- SQL/plan:改 partitioned join、减小 build 侧、加谓词减输入。
- 配置:合理
query.max-memory;保证 spill 路径 SSD 与空间。 - Coordinator:大 catalog 表增加 manifest 缓存/rewrite(lakehouse/18 附录 P)。
五、Straggler Task
5.1 触发条件
- Split 大小不均:大量小文件(lakehouse/17 compaction)。
- 单 Worker 热点:data locality 把大 split 堆到一节点。
- Spill 慢盘:一 task 外排 IO 拖尾。
- Remote exchange 等待最慢上游(第 11 篇)。
5.2 观测信号
- Stage 完成 99% task 后长时间剩 1 个 task。
- Task scheduled time 低、blocked time 高(Trino UI 字段名以文档为准)。
5.3 止血与修复
- Compaction 均衡文件大小。
- 提高并行度 或 调整 split 目标大小(connector 配置)。
- 节点异构:慢节点摘流或统一硬件;检查 spill 盘。
六、Metastore / Catalog 超时
6.1 触发条件
- Hive Metastore 雪崩、REST catalog 限流。
- Planning 读过多 manifest(snapshot 链长、未 rewrite)。
- 网络到 S3/对象存储 元数据路径不稳定。
6.2 观测信号
- 查询 长时间处于 PLANNING,scan 未开始。
- Coordinator log
Metastore timeout/ Iceberg REST 503。 - 同 SQL 第二次更快(元数据缓存命中)。
6.3 止血与修复
- 缓存:Trino metastore cache 配置;Iceberg REST 侧 caching(文档版本相关)。
- 维护:
expire_snapshots、rewrite_manifests(Spark/Trino procedure)。 - 架构:分离 heavy ETL catalog 与 interactive catalog;只读副本。
七、工具链速查
| 目标 | Trino | Spark | DuckDB |
|---|---|---|---|
| 计划形状 | EXPLAIN / VERBOSE |
explain() |
EXPLAIN |
| 运行时 | EXPLAIN ANALYZE |
SQL UI stages | EXPLAIN ANALYZE |
| 裁剪 | scan input rows、dynamicFilters | PushedFilters | Total Files Read |
| 倾斜 | task raw input | stage skew | 单进程无 shuffle |
| 队列 | resource group UI | pool | — |
Event listener / JMX(Trino docs, Monitoring)接 Prometheus 时,优先告警:planning 时长、p95 query memory、spill 率、failed query 原因分类——比单纯 QPS 更能预告故障。
八、小结
- 全表扫 先查 planner 约束与
plan_files文件数,再查数据布局(lakehouse/18)。 - 倾斜 看 task 级输入分布,根因在 key 分布与 join 策略(第 4、5、11 篇)。
- OOM 区分 query limit、broadcast 放大、spill 失败与 Coordinator heap(第 16 篇)。
- Straggler 常是小文件与 IO,不是 SQL 语义错。
- Catalog 超时 是 planning 阶段问题,与 scan 优化正交。
下一篇 第 18 篇 把 Trino/Spark/ClickHouse/DuckDB/PG 放进 选型决策树,并闭合 数据平台全栈阅读地图。
上一篇:内存、Spill 与资源隔离
下一篇:引擎选型与阅读地图
返回 系列目录
参考资料
- Trino Documentation 476+, Troubleshooting / SQL explain / Monitoring / Iceberg connector(A 级)。
- Apache Spark 3.5+ Documentation, SQL tuning / Adaptive Query Execution(A 级)。
- lakehouse/18 自检清单(A 级实测方法)。
- 公开事故复盘(Coordinator OOM、metastore 雪崩)——引用时标注来源与日期(B 级)。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【分布式 OLAP 查询引擎】Calcite 与规则/代价优化框架
RelNode、Convention、Trait 与 Rule-based 改写链;VolcanoPlanner/Cascades memo 搜索;Trino 476+ 自研 planner 与 Calcite 借鉴边界,对照 Spark Catalyst 优化阶段。
【分布式 OLAP 查询引擎】Trino 查询路径:从 SqlQueryExecution 到 Page 流
主线拆解 Trino Coordinator 上 SqlQueryExecution 的生命周期:analyze、plan、fragment、Stage 调度到 Worker Task;Iceberg connector 如何从 snapshot/manifest 过滤生成 Split;Page 在 Operator 链上流动;EXPLAIN 与 EXPLAIN ANALYZE 字段判读。
【分布式 OLAP 查询引擎】Spark SQL 与 Catalyst:逻辑/物理计划与 AQE
拆解 Spark 3.5+ Catalyst 的 Analyzed / Optimized / Physical 计划链、whole-stage codegen 与 shuffle 边界、AQE 的动态 coalesce/skew join/broadcast;并与 Trino 476 及 Iceberg V2 reader 下推能力对照。
【分布式 OLAP 查询引擎】Trino · Spark · DuckDB · 优化与 MPP 执行
闭合数据平台栈最后一块:从 SQL 解析与 Calcite 式优化,到 Volcano/向量化执行、Trino Coordinator/Worker 与 shuffle,再到 Iceberg connector 下推与生产排查。承接 lakehouse 第 18 章读湖视角,补全「谁在做 planning」的引擎内核层。