
Delta Lake Collated String Type 协议详解字符串排序规则、collations 表特性与按排序规则统计的文件跳过【免费下载链接】deltaAn open-source storage framework that enables building a Lakehouse architecture with compute engines including Spark, PrestoDB, Flink, Trino, and Hive and APIs项目地址: https://gitcode.com/GitHub_Trending/del/deltaDelta Lake 默认对所有字符串按 UTF-8 二进制编码进行等值比较与排序而本仓库 protocol_rfcs/collated-string-type.md 提出了一项协议级增强为字符串列引入可选的 collation排序规则如大小写不敏感比较、按语言区域排序并让 per-file 列统计与排序规则版本关联从而在保证正确性的前提下继续支持文件级数据跳过data skipping。读完本文你将掌握 collations 表特性的启用前提Writer Version 7、collations与domainMetadata两个 writer feature、collation 标识符的三段式格式、__COLLATIONS在表 schema 中的存储编码方式以及statsWithCollation统计结构的读写要求。一、为什么需要 Collations字符串比较规则被固化在协议层在 Delta Lake 中所有字符串都以 UTF-8 编码存储默认的二进制 collation 意味着两个字符串相等当且仅当其 UTF-8 字节序列相同排序也直接按字节序进行。这种设计简单、确定但对真实业务并不总是友好例如大小写不敏感查找Delta与delta按二进制 collation 不等需要引擎额外转换或依赖自定义逻辑语言区域排序德语、土耳其语等语言对字母排序如变音符号、i/ı有不同于字节序的规则文件跳过失效当查询条件使用了与写入统计时不同的比较规则时用旧规则收集的 min/max 统计可能给出错误的下界/上界导致数据跳过产生错误结果。Collated String Type 协议改变正是为了解决这些问题的它允许在表 schema 中为字符串列声明 collation让读者按 schema 中的规则进行比较与排序同时让每个版本的 collation 拥有独立的列统计从而保证基于统计的文件跳过依然精确。该协议变更由三部分组成见 RFC 开头在表 schema 中声明 collationsPer-column statistics 用产生它们的 collation 进行标注使用 domain metadata 记录活跃的 collation 版本。需要强调的是collation 只影响比较与排序不改变字符串的存储方式。列仍以 UTF-8 字节序列存储只是相等判断和排序顺序可以按规则变化。二、启用前提Collations Table Feature 与 Writer Version 7RFC 明确给出了启用collations表特性必须同时满足的三个条件表的Writer Version 必须为 7表的writerFeatures中必须包含collations表的writerFeatures中必须包含domainMetadata用于存放 collation 版本提示。在仓库的 kernel 实现中可以找到与 RFC 完全对应的特性定义。见 TableFeatures.javaCOLLATIONS_PREVIEW_W_FEATURE名为collations-preview被注释为被其稳定版本collations取代而COLLATIONS_W_FEATURE对应Collations类其构造为super(collations, /* minWriterVersion */ 7)是一个writer-only 特性——这与 RFC 中collations表特性是 writer only 特性的描述完全一致。值得注意的是writer-only的深层含义不支持 collations 的客户端仍然可以读取表只是它们必须退回 UTF-8 二进制 collation 来解读字符串只有写入端才必须感知该特性。另外收集 collated 统计是可选的——即使某字段声明了非二进制 collation写入端也可以只提供 UTF-8 二进制 collation 的统计。三、Reader 要求谁允许用哪份统计做文件跳过RFC 为读者定义了三条约束当表的writerFeatures包含collations时读者可以依据 schema 中声明的 collation 对字符串做等值比较与排序若某个字符串类型未声明 collation读者必须使用 UTF-8 二进制表示的默认比较运算符文件跳过必须严格匹配 collation 及其版本只有当过滤操作符显式指定列使用某个 collation 时才允许用该 collation 收集的列统计做跳过。RFC 举例说明当使用配置为ICU.en_US.72的等值比较操作符过滤字符串列时读者不得使用spark.UTF8_LCASE.75.1的统计做跳过也不得使用ICU.en_US.69的统计——因为 collation 版本号不一致。这一条是正确性的关键min/max 值会随 collation 及版本不同而变化混用版本可能导致错误跳过数据。RFC 在结尾还补充了一条通用约束引擎可以依据自身的 collation 优先级规则对操作实际应用与 schema 不同的 collation但只有当用于跳过的统计 collation 与过滤操作所用 collation 在所有方面包括版本号完全一致时才可使用该统计。在 Spark 侧的统计读取实现中collation 版本确实参与了统计路径的定位。DataSkippingReader.scala 的getStatsColumnOpt注释明确写道对于 collated 字符串的统计该路径包含带版本的 collation 标识符the path contains the versioned collation identifier且路径按逆序存储方法会先校验路径在 stats schema 中是否存在若统计类型不存在如未收集统计或禁用了列统计则返回None。四、Writer 要求写 schema 元数据、写统计、维护版本提示RFC 对写入端提出五条要求对使用非默认 collation即不是按 UTF-8 二进制比较的列必须在 schema metadata 中写入 collation 标识符对使用默认 collationUTF-8 二进制比较的列不得写入 collation 标识符对使用非默认 collation 的字符串列可以在statsWithCollation中写 per-file 统计详见 PROTOCOL.md 的 Per-file Statistics 一节若写入端为某个新版本的 collation 新增了 per-file 统计应同步更新collations表特性的domainMetadata把用于收集统计的新 collation 版本加入其中若某个 collation 版本不再需要收集统计例如引擎升级了 ICU 库、改用更新的版本可以从domainMetadata中移除该版本。第 4、5 条体现了一种协作式的版本管理domainMetadata里的writeVersions只是一个提示hint帮助客户端在写入时选择合适的 collation 版本而不必扫描所有 AddFile 的统计RFC 同时声明客户端允许忽略这些提示。五、Collation 标识符Provider.Name[.Version] 三段式Collation 通过标识符引用。Delta 协议本身除了二进制 collation 外不规定任何具体的排序规则但它支持 provider 的概念引擎可以使用 ICU 之类的 provider 并在统计中做相应标注。标识符由三个部分用点号连接而成| 部分 | 说明 | |-|-| | Provider提供者 | provider 的名称不允许包含点号| | Name名称 | provider 提供的 collation 名称不允许包含点号| | Version版本 | 版本字符串允许包含点号此部分可选。不带版本的 collation 用于 schema 中因为读者不被强制使用某个特定版本统计则必须使用带版本的 collation 标注以保证正确性 |仓库 kernel API 中 CollationIdentifier.java 是这一格式的直接实现默认常量SPARK_UTF8_BINARY new CollationIdentifier(SPARK, UTF8_BINARY)即 Spark 默认的 UTF-8 二进制 collation构造时 provider、name、version 都会转为大写且 version 可为空OptionalStringfromString(String identifier)按点号个数解析1 个点表示PROVIDER.NAME2 个及以上表示PROVIDER.NAME.VERSION使用split(\\., 3)保证版本里的点不被拆散toString()输出PROVIDER.NAME[.VERSION]toStringWithoutVersion()输出PROVIDER.NAMEequals要求 provider、name、version 三者全部相同这与 RFC 中统计只能被相同 collation含版本复用的语义一致。例如ICU.de_DE无版本用于 schema、ICU.en_US.72带版本用于统计标注、spark.UTF8_LCASE.75.1Spark 提供的大小写不敏感 collation版本 75.1。在 kernel 的类型系统中StringType直接携带 collation。StringType.java 中StringType.STRING常量以SPARK_UTF8_BINARY为默认 collation也提供了StringType(CollationIdentifier)与StringType(String collationName)两个构造器。值得注意的实现细节是写路径的兼容性检查忽略 collation 差异——isWriteCompatible只要求目标也是StringType而equivalent也只看类型是否为 string这意味着写入时不会因 collation 不同而拒绝数据。六、在表 schema 中声明 Collations__COLLATIONS元数据键6.1 存储位置与编码规则Collations 可以为 schema 中的任何字符串类型指定范围包括字符串字段本身map 的 key 与 value 类型array 的 element 类型。它们存储在最近的祖先 StructField的 metadata 的__COLLATIONS键中。对于嵌套的 map/array其路径编码方式与 IcebergCompatV2 中的 id 编码方式相同以点号连接嵌套路径如col2.element.key。Collation 标识符在 schema 中不带版本存储因为读者读取时不被强制使用特定版本。6.2 完整示例从数据 schema 到带 collation 的 JSON schemaRFC 给出了如下数据 schema 示例省略无关字段|-- col1: string |-- col2: array | |-- elementType: map | |-- keyType: string | |-- valueType: struct | |-- f1: string对应的、带 collation 信息的 JSON schema 如下{ type:struct, fields:[ { name:col1, type:string, metadata:{ __COLLATIONS:{ col1:ICU.de_DE } } }, { name:col2, type:{ type:array, elementType:{ type:map, keyType:string, valueType:{ type:struct, fields:[ { name:f1, type:string, metadata:{ __COLLATIONS:{ f1:ICU.de_DE } } } ] } } }, metadata:{ __COLLATIONS:{ col2.element.key:ICU.en_US } } } ] }观察这个示例可以得出三条实用结论普通字符串字段col1__COLLATIONS直接写在该字段自身的 metadata 中值为无版本的ICU.de_DE嵌套结构中的字符串字段col2.element.value.f1__COLLATIONS写在f1字段其最近的祖先 StructField的 metadata 中值同样为ICU.de_DE嵌套 map/array 中的 key/value/element 字符串col2.element.key__COLLATIONS写在最外层承载该结构的字段col2的 metadata 中键使用点号路径表示嵌套位置col2.element.key与 IcebergCompatV2 的 id 编码风格一致。6.3 对协议表的更新RFC 还同步要求更新协议文档中的两张表Primitive Types 表中的 string 行更新为UTF-8 编码的字符序列。可以在 Column Metadata 中指定 collation见 Specifying collations in the table schema 一节否则默认使用二进制 collation。Column Metadata 表新增一行| Field Name | Description | |-|-| |__COLLATIONS| 存储在该字段中、或存储在该字段内且不含嵌套 struct 的 map/array 组合中的字符串的 collations。详见 Specifying collations in the table schema 一节 |七、Collation 版本提示collations的 Domain MetadataRFC 规定collations表特性的 Domain Metadata 中存放客户端在写入时应为哪些版本的 collation 产生统计的提示。其结构如下{ writeVersions: { ICU.en_US: [72, 73] } }含义解读writeVersions是一个 map键是无版本的 collation 标识符如ICU.en_US值是建议产生统计的版本字符串列表如[72, 73]这些版本按写入端维护的版本集合给出该结构帮助客户端在写入时无需逐个查看所有 AddFile 的统计即可选择合适的 collation 版本客户端允许忽略这些提示——它们是hints而非强约束。结合 Writer 要求第 4、5 条写入端在引入新版本统计时应把新版本加入writeVersions在废弃某版本统计时如 ICU 库升级可将其从列表中移除。这也是为什么该特性必须依赖domainMetadata表特性——它需要一块可独立于表 schema 演化的元数据区域来维护版本清单。八、Per-file StatisticsstatsWithCollation结构8.1 基本语义Per-column statistics 记录文件中每一列的统计信息其编码镜像实际数据的 schema。统计是可选的并且允许在字段声明了非二进制 collation 时仍提供 UTF-8 二进制统计。RFC 给出了一个带 collation 字段的示例数据 schema|-- a: struct | |-- b: struct | | |-- c: long |-- d: struct |-- e: string collate ICU.en_US.72对应地统计可以存储为如下 schema|-- stats: struct | |-- numRecords: long | |-- tightBounds: boolean | |-- minValues: struct | | |-- a: struct | | | |-- b: struct | | | | |-- c: long | |-- maxValues: struct | | |-- a: struct | | | |-- b: struct | | | | |-- c: long | |-- statsWithCollation: struct | | |-- ICU.en_US.72: struct | | | |-- minValues: struct | | | | |-- d: struct | | | | | | e: string | | | |-- maxValues: struct | | | | |-- d: struct | | | | | | e: string这个示例展示了三层设计顶层minValues/maxValues继续服务于默认二进制比较场景——本示例中a.b.c是 long 类型直接放在顶层statsWithCollation是一个按带版本的 collation 标识符如ICU.en_US.72作 key 的结构其内部再嵌套minValues/maxValuescollated 字段d.e的统计只出现在statsWithCollation.version之下与顶层统计隔离从而避免不同排序规则产生的 min/max 互相污染。在 Spark 侧统计 schema 的解析逻辑位于 DataSkippingReader.scalagetStatsColumnOpt接收统计类型的路径对 collated 字符串而言其中包含版本化 collation 标识符与嵌套列名路径两者都以逆序传入方法沿 stats schema 逐层折叠查找字段路径中任一环节不存在即返回None。这从实现上印证了 RFC 的结构设计collated 统计是 stats schema 中真实存在、可被路径寻址的独立子树。8.2 Per-column statistics 支持的类型RFC 更新了 per-column statistics 的完整定义下表按stats.tightBounds的取值区分语义| Name | Descriptionstats.tightBoundstrue | Descriptionstats.tightBoundsfalse | |-|-|-| |nullCount| 该列的null值数量 | 若某列的nullCount等于物理记录数stats.numRecords则该列所有有效行都必须是null反之不一定成立若nullCount等于 0则该列所有有效行都非null反之不一定成立若nullCount是除这两种特殊情况外的任意值则不携带任何信息应视同缺失 | |minValues| 一个等于该文件中此列最小有效值1的值若所有有效行均为 null则不携带信息 | 一个小于等于该文件中此列所有有效值1的值若所有有效行均为 null则不携带信息 | |maxValues| 一个等于该文件中此列最大有效值1的值若所有有效行均为 null则不携带信息 | 一个大于等于该文件中此列所有有效值1的值若所有有效行均为 null则不携带信息 | |statsWithCollation| 针对不使用二进制 collation 的字符串列的 minValues 与 maxValues | 与顶层 minValues/maxValues 语义相同但把 minValues 与 maxValues 都包装进一个以生成它们的 collation 为 key 的对象中 |tightBounds的语义区分值得单独强调当tightBoundstrue时统计是精确边界min 恰好等于最小有效值当tightBoundsfalse时统计退化为不等式边界min ≤ 所有有效值max ≥ 所有有效值此时文件跳过的条件判断会相应放宽但安全性不变。而statsWithCollation在两种模式下都遵循同层语义、按 collation 分桶的原则。九、协议落地的整体视图与后续阅读将本 RFC 的条款与仓库实现对照可以形成一张完整的落地视图| RFC 条款 | 仓库实现佐证 | |-|-| | Writer Version 7 collationswriter feature | TableFeatures.java 中Collations定义super(collations, 7)属 writer-only 特性并保留collations-preview兼容旧名 | | 三段式标识符PROVIDER.NAME[.VERSION]| CollationIdentifier.java 的fromString/toString/equals实现 | | 字符串类型携带 collation默认 SPARK UTF8_BINARY | StringType.java 中StringType.STRING默认 collation 为SPARK.UTF8_BINARY| | collated 统计以版本化标识符寻址 | DataSkippingReader.scala 中统计路径包含版本化 collation 标识符 |如果希望继续深入推荐按以下顺序阅读仓库相关材料RFC 原文protocol_rfcs/collated-string-type.md通用表特性机制与 writerFeatures 的完整列表TableFeatures.javacollation 标识符单元测试覆盖fromString解析、大小写归一化、版本可选性等边界CollationIdentifierSuite.scala字符串类型与 collation 的关联测试StringTypeTest.java数据跳过含 collated 统计路径的测试DataSkippingUtilsSuite.scala 与 StatsSchemaHelperSuite.scala。十、总结Collated String Type 协议以三个精心设计的机制解决了带排序规则的字符串比较与基于统计的文件跳过之间的正确性矛盾schema 中的__COLLATIONS元数据声明列的默认排序规则无版本读者据此决定比较与排序行为而不支持该特性的客户端仍可用 UTF-8 二进制规则安全读取statsWithCollation按版本化 collation 分桶存储 min/max使不同排序规则、不同 ICU 版本的统计互不干扰且允许写入端在声明了非二进制 collation 时仍然只写二进制统计domainMetadata中的writeVersions提示让写入端不必扫描全部文件即可选择合适的统计版本并在引擎升级如 ICU 升级时平滑迁移版本集合。整个设计始终坚持一个原则统计只能被与其完全一致含版本的 collation 复用——这是文件跳过正确性的最后一道防线也是本 RFC 最值得所有引擎实现者牢记的一条约束。字符串列在固定前缀长度处截断时间戳列截断到毫秒。↩ ↩ ↩ ↩【免费下载链接】deltaAn open-source storage framework that enables building a Lakehouse architecture with compute engines including Spark, PrestoDB, Flink, Trino, and Hive and APIs项目地址: https://gitcode.com/GitHub_Trending/del/delta创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考