DeepSeek总结的parquet Variant “碎形化“技术

📅 2026/6/16 1:30:47
DeepSeek总结的parquet Variant “碎形化“技术
来源https://github.com/apache/parquet-format/blob/master/VariantShredding.mdVariant 碎形化 (Shredding)Variant 类型旨在高效存储和处理半结构化数据即使面对异构值也是如此。查询引擎将每个 Variant 值以一种自描述格式编码并将其作为包含value和metadata二进制字段的 group 存储在 Parquet 中。由于数据通常是部分同构的将某些字段提取到单独的 Parquet 列中以进一步提高性能是有益的。这个过程称为碎形化 (shredding)。碎形化使得能够利用 Parquet 的列式表示来实现更紧凑的数据编码、用于数据跳过的列统计信息以及部分投影。例如查询SELECT variant_get(event, $.event_ts, timestamp) FROM tbl只需要加载event_ts字段如果该列已被碎形化则可以通过列式投影读取它而无需读取或反序列化eventVariant 的其余部分。类似地对于查询SELECT * FROM tbl WHERE variant_get(event, $.event_type, string) signupevent_type碎形化列的元数据可用于跳过数据并延迟加载 Variant 的其余部分。Variant 元数据无论 Variant 值是否被碎形化Variant 元数据都存储在顶层 Variant group 的一个二进制metadata列中。Variant 中的所有value列必须使用相同的metadata。Variant 的所有字段名无论是否被碎形化都必须存在于元数据中。值碎形化Variant 值存储在名为value的 Parquet 字段中。每个value字段可能有一个关联的碎形化字段名为typed_value当值与特定类型匹配时该字段存储该值。当存在typed_value时读取器必须根据此规范重构碎形化值。例如一个 Variant 字段measurement可以通过添加类型为int64的typed_value作为长整型值进行碎形化required group measurement (VARIANT(1)) { required binary metadata; optional binary value; optional int64 typed_value; }用于存储 variant 元数据和值的 Parquet 列必须按名称访问而不是按位置。一系列测量值34, null, n/a, 100将存储为值metadatavaluetyped_value3401 00v1/空null34null01 00v1/空00(null)null“n/a”01 00v1/空13 6E 2F 61(n/a)null10001 00v1/空null100value和typed_value都是用于编码单个值的可选字段。这两个字段中的值必须根据下表进行解释valuetyped_value含义nullnull值缺失仅对碎形化对象字段有效非 nullnull值存在可以是任何类型包括 nullnull非 null值存在并且是碎形化类型非 null非 null值存在并且是部分碎形化的对象当value是一个对象且typed_value是一个碎形化对象时该对象被称为部分碎形化。写入器不得生成value和typed_value都非 null 的数据除非 Variant 值是一个对象。如果在需要值的上下文中缺少 Variant读取器必须返回一个 Variant null (00)基本类型 0 (primitive) 和物理类型 0 (null)。例如如果需要一个 Variant如上面的measurement并且value和typed_value都为 null则返回的value必须是00(Variant null)。碎形化值类型碎形化值必须使用以下 Parquet 类型Variant 类型Parquet 物理类型Parquet 逻辑类型booleanBOOLEANint8INT32INT(8, signedtrue)int16INT32INT(16, signedtrue)int32INT32int64INT64floatFLOATdoubleDOUBLEdecimal4INT32DECIMAL(P, S)decimal8INT64DECIMAL(P, S)decimal16BYTE_ARRAY / FIXED_LEN_BYTE_ARRAYDECIMAL(P, S)dateINT32DATEtimeINT64TIME(false, MICROS)timestamptz(6)INT64TIMESTAMP(true, MICROS)timestamptz(9)INT64TIMESTAMP(true, NANOS)timestampntz(6)INT64TIMESTAMP(false, MICROS)timestampntz(9)INT64TIMESTAMP(false, NANOS)binaryBINARYstringBINARYSTRINGuuidFIXED_LEN_BYTE_ARRAY[len16]UUIDarrayGROUP; 见下面的数组LISTobjectGROUP; 见下面的对象基本类型基本类型值可以使用上表中等效的 Parquet 基本类型作为typed_value进行碎形化。除非该值作为对象被碎形化参见对象否则typed_value或value但不能同时必须非 null。数组数组可以通过为typed_value使用 3 级 Parquet 列表进行碎形化。如果该值不是数组则typed_value必须为 null。如果该值是数组则value必须为 null。列表element必须是一个 required group。elementgroup 可以包含value和typed_value字段。当typed_value不存在或无法表示元素时元素的value字段将元素存储为 Variant 编码的binary。当不将元素碎形化为特定类型时可以省略typed_value字段。当将元素碎形化为特定类型时可以省略value字段。但是这两个字段中至少必须存在一个。例如一个tagsVariant 可以使用以下定义碎形化为一个字符串列表optional group tags (VARIANT(1)) { required binary metadata; optional binary value; optional group typed_value (LIST) { # 必须为 optional 以允许 null 列表 repeated group list { required group element { # 碎形化元素 optional binary value; optional binary typed_value (STRING); } } } }数组的所有元素都必须存在不能缺失因为数组 Variant 编码不允许缺失元素。也就是说typed_value或value但不能同时必须非 null。null 元素必须在value中编码为 Variant null基本类型 0 (primitive) 和物理类型 0 (null)。一系列tags数组[comedy, drama], [horror, null], [comedy, drama, romance], null将存储为数组valuetyped_valuetyped_value...valuetyped_value...typed_value[comedy, drama]null非 null[null, null][comedy,drama][horror, null]null非 null[null,00][horror, null][comedy, drama, romance]null非 null[null, null, null][comedy,drama,romance]null00(null)null对象对象的字段可以使用一个包含碎形化字段的 Parquet group 作为typed_value进行碎形化。如果该值是对象则typed_value必须非 null。如果该值不是对象则typed_value必须为 null。如果typed_value为 null读取器可以假定该值不是对象并且typed_value字段值是正确的也就是说如果typed_value字段满足所需字段读取器不需要读取value列。typed_valuegroup 中的每个碎形化字段都表示为一个 required group其中包含可选的value和typed_value字段。当typed_value无法表示该字段时value字段将该值存储为 Variant 编码的binary。这种布局使读取器能够基于value和typed_value的字段统计信息跳过数据。当不将字段碎形化为特定类型时可以省略typed_value字段。部分碎形化对象的value列绝不能包含由typed_value中的 Parquet 列表示的字段碎形化字段。读取器可以始终假定数据被正确写入并且typed_value中的碎形化字段不会出现在value中。因此当一个字段同时定义在value和碎形化字段typed_value中时读取结果可能不一致。例如一个 Variantevent字段可以使用以下定义碎形化event_type(string) 和event_ts(timestamp) 列optional group event (VARIANT(1)) { required binary metadata; optional binary value; # 一个 variant预期是一个对象 optional group typed_value { # variant 对象的碎形化字段 required group event_type { # event_type 的碎形化字段 optional binary value; optional binary typed_value (STRING); } required group event_ts { # event_ts 的碎形化字段 optional binary value; optional int64 typed_value (TIMESTAMP(true, MICROS)); } } }每个命名字段的 group 必须使用重复级别required。字段的value和typed_value被设置为 null缺失以指示该字段在 variant 中不存在。要编码一个存在但值为 null 的字段value必须包含一个 Variant null基本类型 0 (primitive) 和物理类型 0 (null)。当一个字段的value和typed_value都非 null 时引擎应该失败。如果引擎选择在这种情况下读取则必须使用typed_value列。读取器可以始终假定数据被正确写入并且只定义了value或typed_value中的一个。因此当value和typed_value都定义时读取结果可能与只需要其中一列的优化读取不一致。下表显示了第一列中的一系列对象将如何存储Event 对象valuetyped_valuetyped_value.event_type.valuetyped_value.event_type.typed_valuetyped_value.event_ts.valuetyped_value.event_ts.typed_value注释{event_type: noop, event_ts: 1729794114937}null非 nullnullnoopnull1729794114937完全碎形化对象{event_type: login, event_ts: 1729794146402, email: userexample.com}{email: userexample.com}非 nullnullloginnull1729794146402部分碎形化对象{error_msg: malformed: ...}{error_msg: malformed: ...}非 nullnullnullnullnull所有碎形化字段都缺失的对象malformed: not an objectmalformed: not an objectnull不是对象存储为 Variant 字符串{event_ts: 1729794240241, click: _button}{click: _button}非 nullnullnullnull1729794240241字段event_type缺失{event_type: null, event_ts: 1729794954163}null非 null00(字段存在且为 null)nullnull1729794954163字段event_type存在且为 null{event_type: noop, event_ts: 2024-10-24}null非 nullnullnoop2024-10-24null字段event_ts存在但不是时间戳{ }null非 nullnullnullnullnull对象存在但为空null00(null)null对象/值为 null缺失nullnull对象/值缺失无效:{event_type: login, event_ts: 1729795057774}{event_type: login}非 nullnullloginnull1729795057774无效: 碎形化字段存在于value中无效:{event_type: login}{event_type: login}null无效: 碎形化字段存在于value中而typed_value为 null无效:aa非 nullnullnullnullnull无效:typed_value存在且value不是对象无效:{}02 00(包含 0 个字段的对象)null无效: 对象的typed_value为 null上表中的无效情况不得由写入器产生。当typed_value非 null 且包含碎形化字段时读取器必须返回一个对象。嵌套与任何 Variantvalue字段关联的typed_value可以是任何碎形化类型如上文各节所示。例如上面的event对象也可以将子字段碎形化为对象 (location) 或数组 (tags)。optional group event (VARIANT(1)) { required binary metadata; optional binary value; optional group typed_value { required group event_type { optional binary value; optional binary typed_value (STRING); } required group event_ts { optional binary value; optional int64 typed_value (TIMESTAMP(true, MICROS)); } required group location { optional binary value; optional group typed_value { required group latitude { optional binary value; optional double typed_value; } required group longitude { optional binary value; optional double typed_value; } } } required group tags { optional binary value; optional group typed_value (LIST) { repeated group list { required group element { optional binary value; optional binary typed_value (STRING); } } } } } }数据跳过当value始终为 null缺失时typed_value列的统计信息可用于文件、行组或页面跳过。当相应的value列全为 null 时所有值必须是碎形化typed_value字段的类型。由于类型已知与该类型值的比较是有效的。IS NULL/IS NOT NULL和IS NAN/IS NOT NAN的过滤结果也是有效的。与其他类型值的比较不一定有效不应跳过数据。Variant 的类型转换行为委托给处理引擎。例如将字符串解释为时间戳可能取决于引擎的 SQL 会话时区。重构碎形化 Variant可以使用递归算法恢复未碎形化的 Variant 值其中初始调用使用顶层 Variant group 字段调用construct_variant。defconstruct_variant(metadata:Metadata,value:Variant,typed_value:Any)-Variant:从 value 和 typed_value 构造 Variantiftyped_valueisnotNone:ifisinstance(typed_value,dict):# 这是一个碎形化对象object_fields{name:construct_variant(metadata,field.value,field.typed_value)for(name,field)intyped_value}ifvalueisnotNone:# 这是一个部分碎形化对象assertisinstance(value,VariantObject),部分碎形化的值必须是一个对象asserttyped_value.keys().isdisjoint(value.keys()),对象键必须不重叠# 合并碎形化字段和非碎形化字段# (字段 ID 和偏移量必须按对应字段名的顺序排列# 按字典顺序排序UTF-8 的无符号字节顺序)returnVariantObject(metadata,object_fields).union(VariantObject(metadata,value))else:returnVariantObject(metadata,object_fields)elifisinstance(typed_value,list):# 这是一个碎形化数组assertvalueisNone,碎形化数组不得与 variant 值冲突elements[construct_variant(metadata,elem.value,elem.typed_value)foreleminlist(typed_value)]returnVariantArray(metadata,elements)else:# 这是一个碎形化基本类型assertvalueisNone,碎形化基本类型不得与 variant 值冲突returnprimitive_to_variant(typed_value)elifvalueisnotNone:returnVariant(metadata,value)else:# value 缺失returnNonedefprimitive_to_variant(typed_value:Any):Variant:ifisinstance(typed_value,int):returnVariantInteger(typed_value)elifisinstance(typed_value,str):returnVariantString(typed_value)...向后和向前兼容性碎形化是 Variant 的一个可选特性读取器必须能够继续读取仅包含value和metadata字段的 group。不写入碎形化值的引擎必须能够根据此规范读取碎形化值或者必须失败。不同的文件可能包含冲突的碎形化 schema。也就是说文件可能为同一个 Variant 包含具有不兼容类型的不同typed_value列。可能无法推断或指定一个单一的碎形化 schema使得无需将值重构为 Variant 即可读取表的所有 Parquet 文件。