跳转至

查询加速 · 数据布局 + 二级索引 + 加速副本机制

Explanation · 原理资深

一句话定位

把物理数据按查询 pattern 重新排 + 建索引 + 必要时同步到本地列存副本——让谓词下推跳过绝大部分数据。不改 SQL 不加机器 · 也能把查询从 30s 降到 3s。湖仓 BI 性能调优的头等招式。

TL;DR · 三层加速栈

  • 第一层 · 数据布局:分区 · Sort · Z-order · Liquid Clustering(Delta 独有 GA · Iceberg 侧 2026 Public Preview)
  • 第二层 · 二级索引:Parquet footer stats · Bloom Filter · Iceberg Puffin · Lance 向量/标量索引
  • 第三层 · 加速副本:当湖上引擎仍打不到仪表盘 SLO(p95<1s / 并发>100)· 上 StarRocks/ClickHouse/Doris 镜像
  • 决策顺序:布局优化 → 二级索引 → MV → 加速副本 · 层层递进 · 别一上来就副本
  • 物化视图 互补不互斥 · 加速让扫快 · MV 让不扫

1. 核心原理 · 扫得少比扫得快重要

湖表查询的瓶颈通常不是"扫得慢"而是"扫了太多没必要扫"。每层文件 metadata 都是潜在的剪枝点:

层级 含什么 剪什么
Iceberg manifest list 每 manifest 的 partition range 整批 manifest
Iceberg manifest 每 data file 的 partition value + 列 min/max 整个文件
Parquet footer 每 row group 的列 min/max + bloom filter row group
Parquet page index (2022+) 每 page 的列 min/max data page
Puffin / Lance 外挂索引 值-文件/位置倒排 · 向量 ANN file 或 row

关键:min/max 有没有用 · 取决于数据物理是否聚集。同一 user_id 分散在 1000 个文件 · 每个文件 min/max 都是 [0, max] · 剪枝失败。

加速的本质就是让物理排列匹配查询 pattern

2. 数据布局 · 第一层

2.1 分区(Partitioning)· 粗粒度

粗粒度按列值切目录/文件组。典型 PARTITIONED BY (days(ts), bucket(16, shop_id))

  • Iceberg Hidden Partitioning · SQL 直接 WHERE ts >= '...' 自动走分区 · 不要用 extract(year) 这类表达式(破坏 partition pruning)
  • Partition Evolution · Iceberg 独有 · 不用重写历史数据改分区
  • 过度分区坑:每分区 < 100MB 就是小文件灾难 · 健康是 256MB-1GB
  • bucket transform · 打散防倾斜 · 对 join key 或 high-cardinality 列有用

2.2 Sort Order · 单列范围查询

把数据按某列全局排序再重写。

  • Iceberg rewrite_data_files(sort_order=...) · 按时间列排序 · 时间范围查询极快
  • Paimon Order / Zorder · 物理排序 compaction 时生效
  • 局限:单列 sort 对其他列完全无帮助 · ORDER BY tsWHERE user_id = ... 0 收益

2.3 Z-order · 多维 Morton 曲线

把 2-5 列通过 Morton 交错编码映射到一维 · 保留多维局部性。

  • Delta OPTIMIZE ... ZORDER BY (col1, col2)
  • Iceberg rewrite_data_files(sort_order=...) + Z-order strategy
  • 效果递减:每列都受益但都不如单列 sort 好;3-5 列 Z-order 是效果极限 · 再多就每维都差
  • 不是万能:基数极高列(如 UUID)Z-order 意义小

2.4 Liquid Clustering · Databricks Delta 2024 GA · Iceberg 侧 Public Preview

痛点:传统 Sort/Z-order 需要预定义列 + 大量重写。数据/查询 pattern 变了 · 改分区键是 O(N) 代价。

Liquid Clustering 的想法: - 不预定义分区键 · 系统按 clustering keys 自适应组织数据 - 根据实际查询历史自动调整 clustering - 增量 reorganize · 不需要全量重写 - 多 key 聚集 · 比 Z-order 理论最优

2026-04 生产状态: - Delta Lake · GA(Databricks 2024 推出 · OSS Delta 3.1+ 可用 · 2025 生态广泛 · Delta 4.0 2025-09 进一步改进 clustered tables kernel) - Apache Iceberg spec 不包含 Liquid Clustering 定义(仅 sort_order)· Databricks managed Iceberg V3 (2026-04 Public Preview) 独家支持 liquid clustering · 不是 OSS Iceberg 规范的一部分 - 社区 proposal 进行中 · 但跨引擎统一支持仍未到位 · 多引擎环境用 Z-order 替代

选型提示:Databricks 生态用 · 其他引擎等规范 · 暂用 Z-order 替代。

2.5 三者决策树

查询 pattern 稳定 · 1 个主排序列?
  └─ 单列 Sort · 最简最有效
查询 pattern 稳定 · 2-4 列组合 filter?
  └─ Z-order 这 2-4 列
查询 pattern 经常变 · 在 Databricks 生态?
  └─ Liquid Clustering
查询 pattern 经常变 · 不在 Databricks 生态?
  └─ 目前仍是 Z-order + 定期 review · 或改走加速副本

3. 二级索引 · 第二层

Parquet footer 的列 min/max 是天然索引但有局限:

  • 只对物理聚集的数据有效(见 §1)
  • 无法点查(WHERE user_id = X 只有 min/max 是范围命中 · 不是精确命中)
  • 高基数离散列效果差

因此需要外挂二级索引

3.1 Parquet Bloom Filter · 点查命中

每 row group 一个 Bloom Filter · 判断 "某值是否可能存在"。

  • Iceberg / Delta / Spark / Trino 都支持写入 Parquet bloom
  • 点查命中率高(低 false positive)· 范围查询帮不上
  • 写入开销 · 每 row group 多几 KB · 大 table 可观
-- Iceberg 写入时指定 bloom
ALTER TABLE orders SET TBLPROPERTIES (
  'write.parquet.bloom-filter-enabled.column.user_id' = 'true'
);

3.2 Iceberg Puffin · 元数据 sidecar 格式

Iceberg 在 spec 层定义了 Puffin · 让任意 blob 元数据挂到 snapshot。

  • 目前主要用途 · NDV sketch(COUNT DISTINCT 加速)· Bloom filter · 定制统计
  • 未来可期 · 社区在推 bitmap index / BSI / 向量索引 走 Puffin
  • 对比 Lance · Puffin 是 metadata 外挂 · 不改变 Parquet 格式 · 和 Iceberg 生态更自然
  • 详见 Puffin vs Lance

3.3 Lance · 索引即一等公民

Lance 文件格式原生支持: - 标量索引(B-tree / Bitmap)· 点查/范围 - 向量索引(IVF/HNSW)· ANN - FTS 索引(BM25)· 全文检索

BI 场景下 · Lance 适合单表高频查询 + 索引驱动的加速副本场景 · 传统湖表 + Puffin 的组合仍是主流。

3.4 Bitmap / Bit-sliced Index · 高基数聚合

  • Bitmap Index · 每可能值一个位图 · 低基数列最优(性别/状态/类别)· 高基数膨胀
  • Bit-sliced Index (BSI) · 数值列的位切片 · 支持范围 + 聚合 · 高基数数值友好
  • Druid / Pinot 原生使用 · 湖仓侧通过 Puffin 或加速副本引入

3.5 索引使用的代价

类型 写入代价 读取收益 适合
Zone Maps (min/max) 几乎 0 需物理聚集才生效 全场
Parquet Bloom 几 KB/row group 点查 · 非范围 user_id/order_id 等
Puffin NDV 小 · per-column sketch COUNT DISTINCT 10-100× 去重聚合
Bitmap 中 · per-value bitmap 低基数 filter status/category
BSI 较高 数值范围 + 聚合 数值度量
Lance 标量索引 点/范围查 10-100× 单表高频

4. 加速副本 · 第三层(机制视角)

当湖上引擎(Trino/Spark)调优到极限仍打不到目标 SLO · 上本地列存副本

4.1 什么信号触发

  • 仪表盘 p95 硬要求 < 1s · Trino 即使走 MV 也 2-3s
  • 并发 > 100 QPS · 长查询挤占短查询
  • 高基数精确去重(COUNT DISTINCT)· 湖上无 BSI 算不动
  • 用户面 Dashboard(SaaS 客户自己看)· 延迟和成本都敏感

4.2 副本的机制本质

加速副本 = 湖表的物化视图 + 就地列存 + 专门执行引擎

  • 湖是真相源 · 副本挂了能重建
  • 增量同步 · 通常基于湖表 snapshot 差量 · StarRocks/Doris 原生支持 Iceberg 外表 + 增量 MV
  • 专门执行引擎 · 副本引擎针对单表 OLAP 优化(向量化 · BSI · runtime filter · pipeline 执行)· 打得赢 Trino 的通用 DAG

4.3 同步路径

Iceberg/Paimon (真相源 · S3)
       │ 路径 A · 副本引擎直读外表 + 本地 MV
       │ 路径 B · 外部同步器(Flink CDC · SeaTunnel)
StarRocks / ClickHouse / Doris (本地列存)
BI Dashboard
  • 路径 A(推荐):StarRocks 的 Iceberg Catalog + 异步增量 MV 在副本引擎里自动同步 · 运维简单
  • 路径 B:ClickHouse 无湖原生支持 · 依赖外部同步 · 运维重

4.4 陷阱

  • 副本当真相源 · 挂了数据找不回 · 每次副本架构设计先问"能重建吗?"
  • 全量拷贝 · 成本爆 · 只同步热分区(近 30-90 天)
  • 同步延迟无监控 · 副本 lag 2 小时 · 业务看的数据过时 · 必须监控同步 lag
  • 副本间一致性 · 多副本 + 写入路径多 · 不一致是常见 bug

产品横比:详见 OLAP 加速副本横比 · StarRocks / ClickHouse / Doris / Druid / Pinot。

5. CBO + 统计收集 · 让优化器做正确的事

布局和索引都到位 · 优化器不用仍是 0 收益。

5.1 ANALYZE · 收集统计

-- Trino / Spark / StarRocks 都有 ANALYZE
ANALYZE TABLE orders COMPUTE STATISTICS FOR COLUMNS user_id, amount, region;

-- Iceberg 1.4+ 写 Puffin NDV sketch
CALL system.rewrite_manifests('db.orders');
CALL system.add_files(...);  -- 伴随统计

关键统计: - NDV(Number of Distinct Values) · Join 顺序决策 - 列 min/max · 分区裁剪 - Histogram · 偏斜列的 CBO 关键(Iceberg Puffin 在加) - Table size · Join side 决策

5.2 CBO 失败信号

  • 明明数据布好了 · 查询不走布局键 · 查 EXPLAIN 看 planner 决策
  • 小表被当大表 Join · NDV 没更新
  • 过度 broadcast · 或该 broadcast 不 broadcast · 都是统计问题
  • Runtime Filter / Dynamic Filter 没触发 · 检查引擎开关(Trino enable_dynamic_filtering

5.3 统计更新的 pipeline

ETL 完成 → ANALYZE 增量 → Puffin 写 sketch → CBO 读到

生产建议:ANALYZE 放进 ETL 末尾 · 不要单独调度(容易忘)。

6. 实战配方 · Top 10 查询法

  1. 找 Top 10 热查询(BI 系统访问日志 · 每查询出现频次 × 平均延迟 = 总成本)
  2. 看它们的 WHERE / JOIN / GROUP BY
  3. 第一档优化 · 按这些列调分区 + Sort/Z-order
  4. 第二档优化 · 高频点查列加 Bloom Filter
  5. 第三档优化 · 仍慢的 Top 3 打 物化视图
  6. 第四档优化 · 仍打不到 SLO · 上加速副本
  7. Benchmark 前后对比 · 先期通常 3-10× 加速 · 别过度工程

7. 陷阱

  • 不 compact 就不生效 · 新写入文件是乱的 · 要靠定期 rewrite_data_files / OPTIMIZE
  • 排序列选太多 · 全选 = 谁都不好 · Z-order 3-5 列是实际上限
  • 分区和布局互斥思维 · 分区是粗粒度 · Sort/Z-order 是细粒度 · 互补不互斥
  • 忽略写入代价 · Z-order / Liquid Clustering 写入更贵 · 按收益-成本权衡
  • Bloom Filter 全开 · 每列都加 · row group metadata 膨胀 · 只加点查 Top 列
  • 一上来就加速副本 · 湖上 80% 查询 Trino 调优后够用 · 副本是最后一招
  • 副本同步没 lag 监控 · 看板数据过时业务先发现
  • 不收集 ANALYZE · CBO 全靠默认估计 · 查询计划烂
  • Hidden Partitioning 不用 · 在 SQL 里写 extract(year from ts) · 破坏 partition pruning

8. 相关

9. 延伸阅读