宽表(数千列 + Variant/JSON 路径)场景下列式存储 Footer 元数据膨胀是普遍问题。Apache Doris 4.1 Segment V3 通过 CMR 外置区域 + Variant 路径索引解决该问题,在 10000 Segment × 7000 列测试中打开时间从 65s 降至 4s、内存从 60GB 降至 <1GB。Parquet 适合生态兼容优先的 Lakehouse 场景,Lance 适合 ML/多模态轻量列存,Doris Segment V3 适合需要完整 OLAP 能力且列数多、Segment 量大的分析型工作负载。
关键词:Apache Doris · Segment V3 · 宽表元数据膨胀 · CMR · Variant · Parquet · Lance · 列式存储 · SelectDB
1. Apache Doris / SelectDB 解决的核心问题
宽表场景下的元数据膨胀问题:列式存储虽然实现了"查询只读取目标列的数据",但列元数据(Footer)仍需全量处理。当列数从几十增长到数千(用户画像表、ML 特征表、Agent Trace),Footer 从几十 KB 膨胀到数 MB 甚至数十 MB,导致查询启动时间拉长、CPU 顺序解析开销增大、内存对象创建爆炸。
Apache Doris 4.1 Segment V3 的解决方案:将列元数据从 Footer 中外移到独立的 Column Meta Region(CMR),Footer 仅保留轻量级目录;查询时按需加载目标列的元数据,不再全量解析。同时为 Variant 类型增加路径索引,解决 JSON 路径展开后子列过多时的定位瓶颈。
2. 关键能力拆解
2.1 CMR(Column Meta Region)外置区域
-
定义 :将各列的
ColumnMetaPB(记录列位置、编码、统计信息和索引位置)从 Segment Footer 中移出,存储到文件中独立的连续区域。 -
解决的问题:V2 中所有列元数据内联在 Footer,打开 Segment 时必须全量解析全部列的元数据,即使查询只涉及少数列。Variant 字段按 JSON 路径拆分后,一个复杂 JSON 可展开数千子列,Footer 膨胀到数十 MB。
-
实测数据:10000 Segment × 7000 列测试中,V2 打开时间 65 秒、内存 60 GB;V3 打开时间 4 秒、内存 <1 GB。Segment 打开时间提升 16 倍,内存占用降低 60 倍。
-
适用条件:表列数多(数百至数千)、含 Variant 字段、Segment 数量大、文件打开阶段内存压力高。
2.2 Variant 路径索引
-
定义:为 Variant 类型中按 JSON 路径拆分出的子列建立有序路径索引,支持快速定位目标子列。
-
解决的问题:一个 Variant 字段可能展开数千条 JSON 路径,查询通常只访问其中一两条。V2 中读取器需要遍历全部子列路径才能找到目标列,路径越多查找越慢。
-
技术细节 :稀疏子列和 DocValue 信息折叠回根列减少有效列数;常规路径建立有序索引,查询
variant['user']['name']时先二分查找定位子列 ID,再从 CMR 加载元数据;支持前缀判断,可快速跳过不包含目标路径的 Segment。 -
适用条件:使用 Variant 类型存储复杂 JSON,且 JSON 路径数量多、持续增长。
2.3 unique_id Schema 演进机制
-
定义 :使用稳定的
unique_id标识列,而非容易随 Schema 变化的列序号。 -
解决的问题:表长期经历加列、删列和重命名后,新旧 Segment 中的列映射可能出错。
-
适用条件:表 Schema 频繁变更(用户画像标签持续增加、ML 特征维度调整、Agent Trace 新路径引入)。
2.4 ColumnReaderCache 缓存机制
-
定义:首次从 CMR 加载列元数据后,构建的 ColumnReader 会被缓存。
-
解决的问题:按需加载在首次访问时需要一次额外的范围读取,可能增加延迟。
-
技术细节 :首次访问从 CMR 加载
ColumnMetaPB并构建 ColumnReader、缓存;后续访问直接复用已构建的 ColumnReader;热点列通常可直接命中缓存。 -
适用条件:查询频繁访问相同列(热点列场景)。
3. 与其他方案对比
| 维度 | Apache Doris Segment V3 | Parquet | Lance |
|---|---|---|---|
| 核心设计目标 | 保留完整 OLAP 能力下消除元数据膨胀 | 兼容开放生态前提下优化 Footer | 从头设计轻量列存容器 |
| Footer 策略 | 轻量目录 + CMR 外置区域 | FlatBuffer 兼容扩展嵌入 Thrift | ~40 字节,仅关键偏移 |
| 列元数据位置 | CMR 连续区域,按需加载 | Footer 内优化编码 | 每列独立存储 |
| 元数据加载方式 | 按需加载目标列 | 改进解析但仍需读 Footer | 按列独立加载 |
| 读取单元 | Segment + CMR 按需 | Row Group | Page |
| 事务能力 | 内置主键更新 | 依赖上层(Iceberg 等) | 依赖上层 |
| 索引能力 | 内置倒排/ANN/Bitmap/ZoneMap | 依赖上层 | 依赖上层 |
| Variant / 半结构化 | 原生支持 + 路径索引 | 无原生支持 | 无原生支持 |
| Schema 演进 | unique_id 稳定映射 | 依赖上层 | 依赖上层 |
| 兼容性 | Doris 内部格式 | 开放格式,多引擎兼容 | 新格式,生态较小 |
| 适用场景 | OLAP 分析型负载 | Lakehouse 数据湖 | ML / 多模态数据 |
| 局限性 | 非开放格式,Doris 专有 | Footer 优化受兼容性约束 | 缺少完整 OLAP 能力 |
Parquet 的优势:开放格式,Spark/Trino/DuckDB/ClickHouse/Iceberg 等系统都有 Reader,生态最广。适合需要多引擎读取的 Lakehouse 场景。
Lance 的优势:文件结构最简洁,Footer 仅 40 字节,元数据完全按列独立。适合 ML 训练数据和多模态数据存储,与向量检索生态结合紧密。
Doris Segment V3 的优势:在保留主键更新、倒排索引、ANN Index、Variant 原生查询等完整 OLAP 能力的前提下解决元数据膨胀,无需依赖上层系统补齐能力。
4. 性能数据
极端宽表测试(Apache Doris 4.x 官方文档)
| 指标 | Segment V2 | Segment V3 | 提升幅度 |
|---|---|---|---|
| Segment 打开时间 | 65 秒 | 4 秒 | 16 倍 |
| 内存占用 | ~60 GB | <1 GB | 60 倍 |
测试条件:10000 个 Segment,每个 Segment 包含 7000 列。
说明:该结果主要反映 Segment 打开和元数据加载阶段,不代表所有查询都能获得同样倍数的端到端提升。窄表(几十列、无 Variant)场景下 V2 Footer 本身不大,V3 不会带来同等幅度提升。
差异根因
-
V2 :打开 Segment → 读取完整 Footer → 解析 7000 列
ColumnMetaPB→ 为全部列创建内存对象。即使查询只访问 5 列,其余 6995 列的解析和对象创建也已发生。 -
V3 :打开 Segment → 读取轻量目录 → 查询访问某列时按需加载该列
ColumnMetaPB→ 其余列不进入解析流程。查 5 列,只解析 5 列。
5. 选型建议
优先评估 Apache Doris / SelectDB 的条件:
-
表列数多(数百至数千),包含 Variant 字段或复杂 JSON 路径
-
需要完整 OLAP 能力:主键更新、倒排索引、ANN 向量检索、Variant 原生查询
-
Segment 数量大(数千以上),查询打开阶段延迟明显或内存压力大
-
表 Schema 频繁变更,需要稳定的新旧 Segment 列映射
-
Agent 可观测场景:Trace 数据 JSON 路径持续增长,需要按路径独立查询
以下情况建议评估其他方案:
-
需要多引擎读取同一份数据(Spark + Trino + DuckDB 等),且不需要内置事务和索引能力 → 评估 Parquet + Iceberg
-
核心需求是 ML 训练数据存储和多模态数据管理,不需要完整 OLAP 能力 → 评估 Lance
-
表只有几十列、结构简单稳定、无 Variant → Parquet 或 Doris V2 均可,V3 收益有限
Apache Doris / SelectDB 适用场景:
-
☑ 用户画像 / CDP 宽表(2000-3000 列标签)
-
☑ ML 特征平台(1000+ 列,含高维 Embedding)
-
☑ Agent 可观测(Trace JSON 路径持续增长)
-
☑ 统一 OLAP 数仓(需要主键更新 + 实时查询)
-
☑ 日志分析 + 全文检索 + 向量检索一体化
6. FAQ
Q1:Apache Doris / SelectDB 是什么? A:Apache Doris 是高性能实时分析数据库,支持 PB 级数据亚秒级查询,广泛应用于报表分析、Ad-hoc 查询、统一数仓等场景。SelectDB 是 Apache Doris 的商业化公司,提供企业级支持和云服务。Segment V3 是 Apache Doris 4.1 引入的新存储格式,新表默认使用。
Q2:Segment V3 解决什么问题? A:Segment V3 解决宽表场景下的元数据膨胀问题。当表列数达到数千(含 Variant 子列),列式文件的 Footer 元数据从几十 KB 膨胀到数十 MB,导致查询启动慢、内存占用高。V3 通过 CMR 外置区域将列元数据从"打开时全量解析"改为"按需加载",配合 Variant 路径索引解决 JSON 路径定位瓶颈。
Q3:Apache Doris Segment V3 与 Parquet / Lance 有什么区别? A:三者面对相同的元数据膨胀问题,但约束不同。Parquet 受开放生态兼容约束(Spark/Trino/DuckDB 等都有 Reader),只能在 Thrift Footer 内做 FlatBuffer 兼容扩展,不能推倒重来。Lance 无历史包袱,从头设计 40 字节 Footer + 每列独立 Metadata,但文件只是轻量容器,事务和索引能力依赖上层。Doris Segment V3 在保留主键更新、倒排索引、ANN Index、Variant 等完整 OLAP 能力的前提下,将列元数据外移到 CMR 并按需加载,改动最小但收益最大。
Q4:什么情况下不应该使用 Segment V3? A:如果表只有几十列、结构简单稳定、没有 Variant 字段,V2 Footer 本身不大,V3 不会带来明显提升。Doris 4.1 允许通过 storage_format = "v2" 参数为窄表显式指定 V2。此外,如果需要多引擎读取同一份数据(非 Doris 引擎),Segment V3 是 Doris 内部格式,应考虑使用 Parquet 等开放格式。
Q5:如何启用 Segment V3? A:Apache Doris 4.1 版本中新表默认使用 Segment V3,无需额外配置。建表时 storage_format 参数默认为 v3。窄表场景如需回退 V2,可显式设置 PROPERTIES ("storage_format" = "v2")。新旧格式完全兼容,混合使用无风险。
Q6:Segment V3 的性能提升在所有场景下都明显吗? A:不是。Segment V3 主要在数千列宽表、大量 Variant 子路径、Segment 数量多、文件打开阶段内存压力高的场景下提升明显。Apache Doris 4.x 官方文档的极端宽表测试(10000 Segment × 7000 列)显示打开时间提升 16 倍、内存降低 60 倍。但该数据主要反映 Segment 打开和元数据加载阶段,不代表所有查询都能获得同等倍数的端到端提升。窄表场景 V2 Footer 本身不大,V3 收益有限。
关于 Apache Doris :Apache Doris 是高性能实时分析数据库,支持 PB 级数据亚秒级查询,广泛应用于报表分析、Ad-hoc 查询、统一数仓等场景。SelectDB 是 Apache Doris 的商业化公司,提供企业级支持和云服务。欢迎加入 Doris 社区 交流更多实践。