alitrack

Parquet Variant × DuckDB:列存半结构化数据的正确打开方式(上)

Parquet Variant × DuckDB:列存半结构化数据的正确打开方式

半结构化数据的存储困局

如果你的数据长这样:

1{"event": "page_view", "user_id": 1001, "page": "/home", "duration_ms": 3420}
2{"event": "purchase", "user_id": 1002, "items": ["sku_a", "sku_b"], "total": 129.99, "coupon": "NEW10"}
3{"event": "error", "user_id": 1003, "error_code": 500, "stack": "RuntimeError: ...", "retry_count": 3}

三条日志,三个 schema。传统做法无非几种选择:

  • 全部存 JSON 字符串:查询时每次都要 parse,列存优势全丢
  • 全部展平为 STRUCT:需要所有行 schema 一致,做不到
  • 拆成三张表:查询时 UNION ALL,维护成本高
  • MAP<string,string>:值类型全变成字符串,数值和时间的类型信息丢失

过去十年,数据湖上的半结构化数据始终没有一个"正确的答案"。
2025 年 8 月,Apache Parquet 社区给出的答案来了:Variant。

什么是 Parquet Variant?

Variant 是 Parquet 2.12.0 正式定稿的逻辑类型。它不是"把 JSON 塞进 Parquet",而是一个精心设计的二进制编码格式。

物理结构

Variant 在 Parquet 中是一个 group 类型,包含两个 binary 字段:

1Variant Group
2├── metadata   ← 共享字符串字典(去重字段名)
3└── value      ← 紧凑二进制编码的实际数据

它支持四种基本类型:

类型编码
Primitive21 种:null, bool, int8~64, float, double, decimal, date, timestamp, string, uuid 等
Short String长度 <64 字节的字符串,类型字节直接携带长度
Object无序 key/value,字段名用字典索引,排序后支持二分查找
Array有序 Variant 值列表

两个关键设计

1. 偏移量导航
Object 和 Array 使用偏移量数组存储。读取某个字段时,直接跳到偏移对应位置——不需要完整 parse 整条数据。这是 Variant 比 JSON 字符串快的第一个原因:

1读 JSON 字符串:     parse 整条 → 构建 AST → 定位字段
2读 Variant 编码:    metadata 查字典 → value 跳偏移 → 直接读取

2. 共享字段名字典
传统 JSON 存储中,字段名 ("event", "user_id", "page") 在每一行都重复。Variant 的 metadata 字段只存一份字典,行内存字典索引。行数越多,节省越显著。

Shredding:真正改变游戏规则的设计

Shredding(切分)是 Variant 规格中最重要创新。它的思路很朴素:

既然我们知道用户最常查哪些字段——那就把它们提取成独立的 typed column。

1写入前判断                写入时存储
2┌─────────────────┐     ┌──────────────────────────┐
3│ {"event":"view",│     │ event: VARCHAR  (typed)  │
4│  "user":1001,   │ ──→ │ user: INT32     (typed)  │
5│  "page":"/home"}│     │ value: BINARY   (原始)    │
6└─────────────────┘     └──────────────────────────┘

Shredding 带来的三个关键收益:

  • 列剪枝:只查 event 和 user 时,不需要读原始 value 列
  • 谓词下推:WHERE user_id = 1001 直接在 Parquet 统计信息层过滤
  • 向量化执行:提取后的 typed column 走 DuckDB 的向量化执行路径

Databricks 实测数据:

  • Shredded Variant 比 JSON 字符串 快 30 倍
  • 比未 shred 的 Variant 快 8 倍

DuckDB 的 Variant 支持

DuckDB 是第一个原生支持 Variant 全链路(读写 + shredding)的开源引擎。

版本演进

时间里程碑
2025-07读取支持(非 shredded + shredded)
2025-10写入支持 + 自动 shredding
2026-03DuckDB v1.5.0 "Variegata" 正式发布
2026-04Snowflake Variant Parquet 兼容

SQL 语法

最直观的变化:不再需要 JSON 函数。

1-- 建表
2CREATE TABLE events (
3    id INTEGER,
4    payload VARIANT
5);
6
7-- 插入
8INSERT INTO events VALUES
9    (1, '{"event":"view", "user":1001, "page":"/home"}'::VARIANT),
10    (2, '{"event":"purchase","user":1002,"total":129.99}'::VARIANT);
11
12-- 查询:点号表达式!
13SELECT id, payload.event, payload.user FROM events;
14-- 不需要 json_extract_string(payload, '$.event')
15
16-- 类型检查:每行单独返回底层类型
17SELECT id, variant_typeof(payload) FROM events;
18-- → OBJECT, OBJECT
19
20-- 写入 Parquet(自动 shred 第一 row group 中推断的字段)
21COPY events TO 'events.parquet' (FORMAT PARQUET);
22
23-- 显式指定 shredding schema
24COPY events TO 'events_shred.parquet'
25  (FORMAT PARQUET,
26   SHREDDING {'payload': 'STRUCT(event VARCHAR, user INTEGER)'});

自动 Shredding 机制

DuckDB 的自动 shredding 分析第一个 row group 的结构,推断哪些字段可以提取。用户写 COPY events TO 'file.parquet' 时不需要额外配置——它会自动做最优选择。
例外:DECIMAL 类型不自动 shred,因为精度和标度匹配逻辑复杂,需要显式指定。

性能表现

实验室基准(10GB, 10M 行)

查询JSON 文本VARIANT加速比
全扫描 + 嵌套提取8.4s0.9s9.3x
过滤 + 投影5.1s0.6s8.5x
GROUP BY 嵌套字段12.3s1.4s8.8x
存储大小2.0 GB1.2 GB40% 节省

MotherDuck 的数据更激进:对于只查少数字段的 JSON 查询,shredding + 列剪枝可达 100x 提升。

真实世界表现

社区开发者 marending 在 2026 年 5 月用真实可观测性数据做了一组对比:

格式大小
JSON 文本2.2 MB
MAP1.3 MB
Variant1.1 MB
全物化列1.0 MB

文件大小非常理想,但查询方面 Variant 却不如预期——原因是一个未优化代码路径(GitHub #22024,已在追踪修复中)。社区评价:这个实现在 v1.5 阶段"对生产使用来说还太年轻"。

当前限制

虽然方向正确,但 Variant × DuckDB 在 v1.5 阶段有几个明确的短板:

  1. 无原生 DuckDB Variant 类型:目前内部转为 JSON 处理,真正的原生类型列为未来工作
  2. JSON Path 的 projection pushdown 尚未实现
  3. Variant 列没有统计信息发射:无法利用 min/max/null count 做数据跳过
  4. DECIMAL 不在自动 shredding 范围
  5. #22024 引起的性能退化:特定数据模式下的查询比预期慢

与其他方式的对比

存储方式性能Schema 灵活性类型保真适用场景
JSON 字符串❌ 最慢⭐⭐⭐⭐⭐❌临时分析
STRUCT⭐⭐⭐⭐⭐⭐✅Schema 固定的数据
MAP⭐⭐⭐⭐⭐⭐⭐❌值类型统一
Variant⭐⭐⭐⭐⭐⭐⭐⭐✅动态 schema、异构数据
Variant + Shredding⭐⭐⭐⭐⭐⭐⭐⭐⭐✅推荐的生产模式

(本文为上下篇之一,下篇将继续讨论实战建议、生态对比与总结。敬请期待。)