ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

StarRocks 如何查询 Paimon 半结构化数据?Variant、Shredding 与 SQL 实践

StarRocks 如何查询 Paimon 半结构化数据?Variant、Shredding 与 SQL 实践 作者王日宇霁谦StarRocks Committer阿里云技术专家在电商订单、用户行为和设备日志等场景中一条数据通常同时包含两类信息order_id、event_time、amount等结构相对稳定的业务字段商品属性、营销信息、客户端参数等持续变化的扩展字段。前一类字段适合使用明确的 Schema 进行管理后一类字段则常以 JSON Payload 的形式保存以适应不断变化的数据结构。但当这些 JSON 数据进入湖仓并被频繁查询时新的问题也随之出现如果每次过滤、聚合都需要重新解析完整 JSON并在运行时查找路径、判断类型和完成转换动态字段越多、访问越频繁查询成本就越明显。Variant 为这类数据提供了另一种选择。它将半结构化数据保存为带有类型信息的二进制结构在保留 Schema 灵活性的同时减少查询过程中重复解析 JSON 文本的成本。如果少数内部路径逐渐成为长期高频访问的热点还可以进一步通过 Parquet Shredding将这些路径保存为类型化子列使其更好地参与列式读取和计算。StarRocks 可以通过 Paimon Catalog 读取 Parquet Variant并使用get_variant_*、variant_query、variant_typeof和CAST访问其中的动态字段。本文将从普通 Parquet JSON 与 Variant 的区别出发介绍 StarRocks 如何读取 Paimon Variant、Shredding 如何优化热点路径以及如何在实际业务中选择合适的数据组织方式。需要强调的是Variant 并不是为了替代所有正式类型列。参与分区、Join、排序以及强 SLA 查询的核心字段仍然更适合建成正式列如果数据主要用于原文归档几乎不查询内部结构普通 Parquet JSON 也可能是更简单的选择。是否使用 Variant关键不在于“数据是不是 JSON”而在于内部路径是否需要被反复查询、类型是否需要保真以及热点字段是否值得进一步列式化。01 Variant 是什么它和普通 Parquet JSON 有什么区别本文所说的“普通 Parquet JSON”是指将 JSON Payload 以STRING、BINARY或 Parquet JSON Logical Type 的方式保存在 Parquet 中并不是指 StarRocks 的原生 JSON 类型。普通 Parquet JSON灵活但内部结构对 Parquet 不透明例如一条订单事件包含如下 Payload{ sku: SKU-001, channel: app, campaign_id: 9001, paid: true, items: [ { sku: SKU-001, qty: 2 } ] }当它以普通 JSON 保存到 Parquet 时底层通常仍由一个BYTE_ARRAY承载。Parquet 知道这一列保存了 JSON但sku、channel、campaign_id等内部路径并不是独立的 Parquet 类型列。因此查询campaign_id时通常需要经历读取完整 JSON Payload解析 JSON 文本查找目标路径将结果转换成需要的数据类型。如果多个查询反复访问相同路径这些解析和类型转换成本也会被反复支付。Variant结构灵活但不再只是文本Variant 是一种面向半结构化数据的类型化二进制表示。对象、数组和标量在写入时会被编码为metadata保存字段名称、编码信息等元数据value保存值的类型、位置和实际内容。逻辑上不同行仍然可以拥有不同字段物理上数据不再是一段只能从头解析的 JSON 文本。例如{campaign_id: 9001} {campaign_id: unknown} {coupon: NEW20}这些结构不同、甚至同一路径类型不同的数据都可以保存在同一个 Variant 列中。Apache Parquet 已定义 Variant 的二进制编码和 Shredding布局。普通 Parquet JSON 与 Parquet Variant 对比对比维度普通 Parquet JSONParquet Variant物理表示JSON 文本或二进制 Payload带类型信息的二进制编码Schema 灵活性高不同行可以有不同结构高不同行可以有不同结构类型保真主要保留 JSON 基础类型查询时经常需要 CAST编码中保存具体类型信息路径访问需要解析 JSON 文本并查找路径根据二进制结构定位路径重复查询成本每次查询都可能重新解析避免反复解析 JSON 文本语法内部字段列式化内部路径不是独立 Parquet 列可通过 Shredding 将热点路径保存为类型化子列写入成本写入相对简单写入时需要进行 Variant 编码完整对象读取直接读取原始 Payload过程简单需要解码或重建 Variant 对象适用场景原文归档、内部字段很少查询Schema 持续变化同时需要反复分析内部路径Variant 的核心优势可以概括为三点保留半结构化数据的 Schema 灵活性减少查询时重复解析 JSON 文本的成本通过 Shredding让热点路径具备类型化、列式化的物理表示。Variant 并不意味着文件一定更小也不代表所有查询都会更快。如果数据只写入一次、读取一次或者查询总是返回完整 Payload普通 JSON 仍然可能是更简单的选择。02 StarRocks 如何读取 Paimon Variant在 StarRocks 查询 Paimon Variant 的过程中Paimon 负责管理表的 Snapshot、Schema 和数据文件StarRocks 负责读取 Parquet 文件并执行 Variant 查询。整体架构如下整个读取过程可以分为三个阶段。2.1 获取 Paimon 表与 Split 信息StarRocks 通过 Paimon Catalog 获取表结构和当前 Snapshot再由 Paimon 完成数据文件的 Split 规划。StarRocks 抽取 Split 文件信息使用 StarRocks Parquet Reader 读取 Variant。2.2 将 Parquet Variant 转换为列式数据Parquet Reader 根据文件中的物理 Schema 读取Plain Variant 中的metadata和valueShredded Variant 中的metadata、value和typed_value。读取结果被组织为 StarRocks 的 Variant 列向量继续参与过滤、投影、聚合等向量化计算。2.3 通过 Variant 函数访问内部字段用户可以使用不同函数访问 Variantget_variant_string提取字符串get_variant_int提取整数get_variant_double提取浮点数get_variant_bool提取布尔值variant_query返回指定路径下的 Variantvariant_typeof查看 Variant 值的类型CAST将 Variant 转换为目标 SQL 类型。03 Shredding 如何优化 Variant 读取Plain Variant 的物理结构可以简化为payload ├── metadata └── value它已经避免了 JSON 文本语法的重复解析但所有内部字段仍集中在 Variant 的二进制结构中。如果业务长期反复访问少数热点路径例如$.sku $.channel $.campaign_id $.paid可以通过 Shredding 将这些路径保存为类型化子列payload ├── metadata ├── value └── typed_value ├── sku STRING ├── channel STRING ├── campaign_id BIGINT └── paid BOOLEANShredding 并没有把 Variant 变成固定 Schema 的STRUCT。没有被 Shredding 的长尾字段仍然保存在value中类型不符合预期的数据也可以通过value保留。例如campaign_id大部分时候是整数但某一行写入了字符串{campaign_id: unknown}整数值可以进入typed_value.campaign_id类型冲突的值则继续保存在 Variant 的通用value中保证原始数据语义不会丢失。Shredding 的读取优势主要来自热点路径拥有明确的数据类型减少运行时类型判断和转换热点路径形成独立的 Parquet 类型化子列可以使用 Parquet 的编码和压缩能力查询热点字段时可以直接利用typed_value减少对通用 Variant 内容的解码类型化子列为路径级列裁剪和 Parquet 统计信息过滤提供物理基础。一张 Paimon 表的 Snapshot 会引用多个 Parquet 数据文件。在表结构演进过程中早期 Parquet 文件中的payload可以采用 Plain Variant 物理布局后续 Parquet 文件中的payload可以采用 Shredded Variant 物理布局。两者都是 Parquet 文件区别仅在于 Variant 列的物理 Schema。StarRocks Parquet Reader 会根据每个文件的实际 Schema 读取数据并向查询层提供一致的 Variant 语义。04 如何使用 StarRocks 查询 Paimon Variant下面以电商订单事件表为例介绍如何使用 StarRocks 查询 Paimon Variant。本文采用阿里云 DLF 托管的 Paimon Catalog 作为示例环境后续配置与 SQL 均基于该环境展开。假设 Catalog 中已经存在一张 Paimon 表demo.order_events表结构如下字段类型说明orderamp;amp;#95;idBIGINT订单 IDshopamp;amp;#95;idBIGINT店铺 IDeventamp;amp;#95;timeTIMESTAMP事件时间amountDECIMAL(18,2)订单金额statusSTRING订单状态payloadVARIANT商品、营销和客户端扩展信息其中payload中的数据可能是{ sku: SKU-001, channel: app, campaign_id: 9001, paid: true, items: [ { sku: SKU-001, qty: 2 } ] }另一行可以包含不同字段{ sku: SKU-002, channel: web, paid: false, coupon: NEW20 }4.1 在 StarRocks 中连接 Paimon CatalogCREATE EXTERNAL CATALOG paimon_dlf PROPERTIES ( type paimon, paimon.catalog.type rest, uri https://cn-hangzhou-vpc.dlf.aliyuncs.com, paimon.catalog.warehouse dlf_catalog_name, token.provider dlf );这里的paimon.catalog.warehouse表示 DLF Catalog 名称。uri和dlf.region需要替换为 DLF 实例所在地域的实际配置。创建 Catalog 后即可查看表结构DESC paimon_dlf.demo.order_events;结果中可以看到order_id BIGINT shop_id BIGINT event_time DATETIME amount DECIMAL(18,2) status VARCHAR payload VARIANT4.2 查看完整 VariantSELECT order_id, payload, variant_typeof(payload) AS payload_type FROM paimon_dlf.demo.order_events LIMIT 10;对于示例中的订单数据payload_type通常为Object。4.3 按类型提取字段SELECT order_id, get_variant_string(payload, $.sku) AS sku, get_variant_string(payload, $.channel) AS channel, get_variant_int(payload, $.campaign_id) AS campaign_id, get_variant_bool(payload, $.paid) AS paid FROM paimon_dlf.demo.order_events;查询结果示意order_idskuchannelcampaign_idpaid10001SKU-001app9001TRUE10002SKU-002webNULLFALSE10003SKU-003app9001TRUE如果路径不存在或者实际值无法转换为目标类型对应的get_variant_*函数返回NULL。4.4 读取嵌套对象与数组SELECT order_id, variant_query(payload, $.items[0]) AS first_item, get_variant_string(payload, $.items[0].sku) AS first_item_sku, get_variant_int(payload, $.items[0].qty) AS first_item_qty FROM paimon_dlf.demo.order_events;variant_query返回的仍然是 Variant适合继续访问嵌套结构如果需要明确的 SQL 类型可以使用get_variant_*或CAST。例如SELECT order_id, CAST( variant_query(payload, $.campaign_id) AS BIGINT ) AS campaign_id FROM paimon_dlf.demo.order_events;4.5 使用 Variant 字段进行过滤和聚合SELECT order_id, get_variant_string(payload, $.sku) AS sku FROM paimon_dlf.demo.order_events WHERE get_variant_string(payload, $.channel) app AND get_variant_bool(payload, $.paid) true;也可以先抽取动态字段再参与聚合WITH extracted AS ( SELECT get_variant_string(payload, $.channel) AS channel, get_variant_bool(payload, $.paid) AS paid FROM paimon_dlf.demo.order_events ) SELECT channel, COUNT(*) AS event_count, SUM(CASE WHEN paid THEN 1 ELSE 0 END) AS paid_count FROM extracted GROUP BY channel;这样Paimon 表中的动态 Payload 就可以像普通类型列一样参与 StarRocks 的过滤、聚合和分析。05 如何选择普通 JSON、Plain Variant、Shredded Variant 和正式类型列数据特征推荐方式需要保留 JSON 原文几乎不查询内部字段普通 Parquet JSONSchema 经常变化需要反复访问内部路径Plain Variant文档较宽长期反复访问少数热点路径Shredded Variant字段参与分区、Join、排序或强 SLA 查询正式类型列在电商订单场景中一种更合理的建模方式是order_id、shop_id、event_time、amount、status使用正式类型列商品属性、促销规则、支付回调和渠道扩展信息放入 Variantsku、channel、campaign_id等稳定热点路径使用 Shredding如果某个动态字段逐渐成为核心过滤、Join 或分区字段再将它提升为正式类型列。Variant 的价值不是把所有字段都塞进一个 Payload而是在“固定 Schema”和“完全动态 JSON”之间提供更合适的平衡。06 常见问题6.1 Variant 是否一定比普通 JSON 更快不一定。Variant 的优势主要来自避免重复解析 JSON 文本以及为热点路径提供类型化访问。如果查询总是读取完整 Payload、数据只读取一次或者数据规模很小普通 JSON 可能更简单。6.2 Shredding 会把 Variant 变成固定 Schema 的 STRUCT 吗不会。Shredding 只把选定热点路径保存为类型化子列长尾字段和类型冲突值仍可保留在通用value中因此 Variant 的动态结构不会消失。6.3 同一路径出现不同类型时会怎样例如大多数campaign_id是整数但少数行写入字符串unknown。符合 Shredding 类型的值可以进入typed_value类型冲突值继续保存在通用 Variant 内容中。查询时仍应根据业务语义处理 NULL、类型检查和转换失败。6.4 哪些字段不应该长期留在 Variant 中长期参与分区、Join、排序、主键或强 SLA 过滤的字段应优先提升为正式类型列。Variant 适合承载持续演进的扩展属性而不是把全部业务 Schema 都隐藏在一个 Payload 中。07 结论什么时候值得使用 Paimon Variant普通 Parquet JSON 解决了动态数据的存储问题但频繁分析内部字段时需要持续承担文本解析和类型转换成本。Variant 将半结构化数据编码为带类型信息的二进制结构在保留 Schema 灵活性的同时让动态字段重新进入列式计算体系。在此基础上Shredding 可以进一步将热点路径保存为类型化 Parquet 子列减少通用 Variant 解码和类型转换并为列裁剪、编码压缩和统计过滤提供物理基础。通过 Paimon DLF REST CatalogStarRocks 用户可以直接使用get_variant_*、variant_query、variant_typeof和CAST查询 Paimon Variant 数据将动态 Payload 与普通结构化字段放在同一套 SQL 分析链路中。相关代码已合入 StarRocks Main 分支将随下一版本正式发布。后续文章也将结合版本进展进一步介绍不同能力的使用方式与适用场景。08 参考资料Apache Parquet Variant Encodinghttps://parquet.apache.org/docs/file-format/types/variantencoding/Apache Parquet Variant Shreddinghttps://parquet.apache.org/docs/file-format/types/variantshredding/
返回列表