写点什么

IceBerg 表规格(20210330 翻译者:聚变)

用户头像
聚变
关注
发布于: 2021 年 03 月 31 日
IceBerg表规格(20210330翻译者:聚变)

(20210330 翻译者:聚变)


原文:

https://iceberg.apache.org/spec/

这是 Iceberg 表格式的规范,旨在用于管理作为表的分布式文件系统或键值存储中的大型,变化缓慢的文件集合。

版本 1:分析数据表

当前版本为 Iceberg 规范版本 1。它定义了如何使用不变的文件格式(例如 Parquet,Avro 和 ORC)管理大型分析表。

版本 2:行级删除

Iceberg 社区当前正在研究 Iceberg 格式的版本 2,该版本支持对行级删除进行编码。v2 规范不完整,可能会更改,直到完成并采用为止。本文档包含暂定的 v2 格式要求,但当前与未完成的 v2 规范不存在兼容性保证。

版本 2 的目标是提供一种对行级删除进行编码的方法。此更新可用于删除或替换不可变数据文件中的各个行,而无需重写该文件。

目标

  • 可序列化的隔离–读取将与并发写入隔离,并且始终使用表数据的已提交快照。写入将支持在单个操作中删除和添加文件,并且永远不会部分可见。读者将不会获得锁。

  • 速度–操作将使用 O(1)远程调用来计划要扫描的文件,而不是 O(n),其中 n 随着表的大小而增加,例如分区或文件的数量。

  • 规模–工作计划将主要由客户处理,而不是中央元数据存储的瓶颈。元数据将包含基于成本的优化所需的信息。

  • 演化–表将支持完整的架构和分区规范演化。模式演化支持安全的列添加,删除,重新排序和重命名,包括在嵌套结构中。

  • 可靠的类型–表将为一组核心类型提供良好定义和可靠的支持。

  • 存储分离–分区将是表配置。使用数据值而不是分区值的谓词计划读取。表将支持不断发展的分区方案。

  • 格式–底层数据文件格式将支持相同的架构演变规则和类型。读和写优化格式都将可用。

概览



此表格式跟踪表中的单个数据文件,而不是目录。这允许写入者就地创建数据文件,并且仅在显式提交中将文件添加到表中。

表状态保存在元数据文件中。对表状态的所有更改都会创建一个新的元数据文件,并用原子交换替换旧的元数据。表元数据文件跟踪表架构,分区配置,自定义属性和表内容的快照。快照表示某个时间表的状态,用于访问表中的完整数据文件集。

快照中的数据文件由一个或多个 manifest 文件跟踪,manifest 文件包含表中每个数据文件的一行,文件的分区数据及其指标。快照中的数据是其清单中所有文件的并集。清单文件可在快照之间重用,以避免重写变化缓慢的元数据。manifest 可以跟踪具有表的任何子集的数据文件,并且不与分区关联。

构成快照的 manifest 存储在 manifest list 文件中。每个 manifest list 存储有关 manifest 的元数据,包括分区统计信息和数据文件计数。这些统计信息用于避免读取操作不需要的清单。

乐观并发

一个表元数据文件的原子交换为另一个提供了序列化隔离的基础。读者使用加载表元数据时的当前快照,并且在刷新并选择新的元数据位置之前不会受到更改的影响。

编写者乐观地创建表元数据文件,假设在提交者提交之前不会更改当前版本。编写者创建更新后,它将通过将表的元数据文件指针从基本版本交换为新版本来进行提交。

如果更新所基于的快照不再是当前快照,则编写者必须根据新的当前版本重试更新。在明确定义的条件下,某些操作通过重新应用元数据更改并提交来支持重试。例如,如果所有重写的文件仍在表中,则可以将重写文件的更改应用于新的表快照。

写操作成功提交所需的条件决定了隔离级别。写入者可以选择要验证的内容,并可以做出不同的隔离保证。

序列号

数据和删除文件的相对寿命取决于分配给每个成功提交的序列号。为提交创建快照时,将乐观地为其分配下一个序列号,并将其写入快照的元数据中。如果提交失败并且必须重试,那么将重新分配序列号并将其写入新的快照元数据中。

为快照创建的所有清单,数据文件和删除文件都将继承快照的序列号。清单列表中的清单文件元数据存储清单的序列号。写入新数据和元数据文件条目null以代替序列号,该序列号在读取时将替换为清单的序列号。将数据或删除文件写入新清单(如“现有”)时,将写入继承的序列号,以确保它在第一次继承后不会更改。

从清单元数据继承序列号可以编写一次新清单,然后在提交重试中重用它。要更改重试的序列号,只需重写清单列表,无论如何都将使用最新的清单列表重写清单列表。

行级删除

行级删除存储在删除文件中。

有两种编码行级删除的方法:

  • 位置删除标记由数据文件路径和数据文件中的行位置删除的行

  • 等号删除标记被一个或多个列值删除的行,例如id = 5

与数据文件一样,删除文件也按分区进行跟踪。通常,必须将删除文件应用于具有相同分区的较早的数据文件。有关详细信息,请参见扫描计划。列指标可用于确定删除文件的行是否与数据文件的内容或扫描范围重叠。

文件系统操作

Iceberg 仅要求文件系统支持以下操作:

  • 就地写入–文件一旦写入就不会移动或更改。

  • 可搜索的读取–数据文件格式需要搜索支持。

  • 删除–表删除不再使用的文件。

这些要求与对象存储(例如 S3)兼容。

表不需要随机访问写入。一旦写入,数据和元数据文件将是不可变的,直到将其删除。

除使用原子重命名为新的元数据文件实现提交操作的表外,表不需要重命名。

规范

术语

  • 模式(Schema)–表中字段的名称和类型。

  • 分区规范(Partition spec)–定义如何从数据字段派生分区值的定义。

  • 快照(Snapshot)–某个时间点的表状态,包括所有数据文件的集合。

  • 清单列表(Manifest list)–列出清单文件的文件;每个快照一个。

  • 清单(Manifest)–列出数据或删除文件的文件;快照的子集。

  • 数据文件(Data file)–包含表行的文件。

  • 删除文件(Delete file)–对表中按位置或数据值删除的行进行编码的文件。

架构和数据类型

表的架构是命名列的列表。所有数据类型都是基元或嵌套类型,它们是映射,列表或结构。表模式也是结构类型。

有关这些类型在 Avro,ORC 和 Parquet 文件格式中的表示形式,请参阅附录 A。

嵌套类型

struct是类型值的元组。元组中的每个字段都被命名,并具有一个整数 ID,该 ID 在表模式中是唯一的。每个字段可以是可选字段,也可以是必填字段,这意味着值可以(或不能)为 null。字段可以是任何类型。字段可能具有可选的注释或文档字符串。

list是具有某些元素类型的值的集合。元素字段具有一个整数 ID,该 ID 在表架构中是唯一的。元素可以是可选的,也可以是必需的。元素类型可以是任何类型。

map是具有键类型和值类型的键值对的集合。键字段和值字段均具有在表模式中唯一的整数 ID。映射键是必需的,映射值可以是可选的或必需的。映射键和映射值都可以是任何类型,包括嵌套类型。

基本类型


笔记:

  1. 小数位数是固定的,不能通过架构演变(Schema evolution)进行更改。精度只能扩大。

  2. 所有时间和时间戳记值都以微秒为单位存储。带有时区的时间戳表示一个时间点:值存储为 UTC 且不保留源时区(2017-11-16 17:10:34 PST存储/检索为2017-11-17 01:10:34 UTC,这些值被视为相同)。不带时区的时间戳表示日期和时间,与时区无关:时间值与区域调整无关(2017-11-16 17:10:34始终以检索2017-11-16 17:10:34)。时间戳记值存储为一个长的值,该值编码从 unix 纪元开始的微秒。

  3. 字符串必须存储为 UTF-8 编码的字节数组。

有关如何将模式序列化为 JSON 的详细信息,请参见附录 C。

架构演进(Schema evolution)

模式演变仅限于类型升级以及在结构(嵌套结构和顶层模式的结构)中添加,删除和重命名字段。

有效的类型促销是:

  • int 到 long

  • float 到 double

  • decimal(P, S)decimal(P', S)if P' > P–扩展十进制类型的精度。

任何结构(包括顶级架构)都可以通过删除字段,添加新字段,重命名现有字段,对现有字段重新排序或使用有效的类型提升来提升基元来发展。添加一个新字段会为该字段和所有嵌套字段分配一个新的 ID。重命名现有字段必须更改名称,但不能更改字段 ID。删除字段会将其从当前架构中删除。除非字段可以为空或当前快照未更改,否则无法回滚字段删除。

分组一个结构的字段的子集到嵌套结构是允许的,也不是从嵌套结构移动字段插入它的直接父结构(struct<a, b, c> ↔ struct<a, struct<b, c>>)。不断发展的原语类型结构是允许的,也不是不断发展中的单个场结构的原语(map<string, int> ↔ map<string, struct<int>>)。

列投影

Iceberg 数据文件中的列是通过字段 ID 选择的。写入数据文件后,表模式的列名和顺序可能会更改,并且必须使用字段 ID 进行投影。如果数据文件中缺少字段 ID,则其每行的值应为null

例如,可以使用架构写入文件,1: a int, 2: b string, 3: c double并使用投影架构读取文件3: measurement, 2: name, 4: a。这必须选择文件列c(重命名为measurementb(现在称为name)和null值列a;以该顺序。

保留字段标识

Iceberg 表不得使用大于 2147483447(Integer.MAX_VALUE - 200)的字段 ID 。此 ID 范围保留给可在用户数据模式中使用的元数据列,例如_file保存行所在文件路径的列。

元数据列的集合是:

栏位编号,名称类型描述2147483646 _filestring存储行的文件的路径2147483645 _poslong行在源数据文件中的顺序位置2147483546 file_pathstring文件的路径,用于基于位置的删除文件2147483545 poslong行的序数位置,用于基于位置的删除文件中2147483544 rowstruct<...>删除的行值,用于基于位置的删除文件


Partitioning 分区

数据文件存储在清单中,其中包含分区值的元组,这些分区值在扫描中用于过滤出不能包含与扫描的过滤谓词相匹配的记录的文件。数据文件中存储的所有记录的数据分区值必须相同。(清单存储来自任何分区的数据文件,只要分区规范与数据文件相同即可。)

表配置有分区规范,该规范定义了如何从记录中生成分区值的元组。分区规范具有包含以下内容的字段列表:

  • 一个源列 ID 从表的模式

  • 一个分区字段 ID 用来识别分区领域,是一个分区规范中是唯一的。在 v2 表元数据中,它在所有分区规范中都是唯一的。

  • 一个变换被施加到源列,以产生分区值

  • 一个分区名

由 id 选择的源列必须是原始类型,不能包含在映射或列表中,但可以嵌套在结构中。有关如何将分区规范序列化为 JSON 的详细信息,请参见附录 C。

分区规范捕获从表数据到分区值的转换。除转换数据值外,它还用于将谓词转换为分区谓词。从表数据上的列谓词派生分区谓词用于将逻辑查询与物理存储区分开:分区可以更改,并且始终从列谓词派生正确的分区过滤器。这简化了查询,因为用户不必同时提供逻辑谓词和分区谓词。有关更多信息,请参见下面的扫描计划。

分区转换

转变描述来源类型结果类型identity原始值,未修改任何来源类型bucket[N]价值的散列,mod N(见下文)intlongdecimaldatetimetimestamptimestamptzstringuuidfixedbinaryinttruncate[W]值被截断为宽度W(请参见下文)intlongdecimalstring来源类型year提取日期或时间戳年份,以 1970 年为年份datetimestamp(tz)intmonth提取日期或时间戳记的月份,从 1970-01-01 开始的月份datetimestamp(tz)intday提取日期或时间戳记的日期,例如从 1970-01-01 开始的天数datetimestamp(tz)datehour提取一个时间戳记小时,从 1970-01-01 00:00:00 开始timestamp(tz)int


所有转换都必须返回null一个null输入值。

桶转换细节

桶分区转换使用源值的 32 位哈希值。32 位哈希实现是 32 位 Murmur3 哈希(x86 变体,种子为 0)。

转换由多个值区[1],...来参数化N。哈希模块N必须通过首先丢弃哈希值的符号位来产生一个正值。在伪代码中,该函数是:

  def bucket_N(x) = (murmur3_x86_32_hash(x) & Integer.MAX_VALUE) % N
复制代码

笔记:

  1. 通过扩展分区规范,可以随着表的增长而更改存储桶的数量。

有关按类型的哈希函数详细信息,请参见附录 B。

Truncate 转换细节

类型设定档截断规范例子intW, 宽度v - (v % W) 余数必须为正[1]W=101 → 0-1 → -10longW, 宽度v - (v % W) 余数必须为正[1]W=101 → 0-1 → -10decimalW,宽度(无比例尺)scaled_W = decimal(W, scale(v)) v - (v % scaled_W) [1, 2]W=50s=210.65 → 10.50stringL, 长度长度的子串Lv.substring(0, L)L=3iceberg → ice


笔记:

  1. 其余的v % W必须为正。对于%可能产生负值的语言,正确的截断函数为:v - (((v % W) + W) % W)

  2. W用来截断十进制值的宽度()是使用十进制列的小数位来应用的,以避免使用其他(可能有冲突的)参数。

Sorting

用户可以按列对分区中的数据进行排序以提高性能。可以按排序顺序针对每个数据或删除文件声明有关数据排序方式的信息。

排序顺序由排序顺序 ID 和排序字段列表定义。列表中排序字段的顺序定义了将排序应用于数据的顺序。每个排序字段包括:

  • 一个源列 ID 从表的模式

  • 变换被用于产生值上从源列进行排序。这与分区转换中描述的转换相同

  • 排序方向,即只能是任一ascdesc

  • 一个空顺序,描述排序时空值的顺序。只能是nulls-firstnulls-last

订单 ID0保留给未排序的订单。

排序浮点数应产生以下现象:-NaN-Infinity-value-00valueInfinityNaN。这与 Java 浮点类型比较的实现保持一致。

数据或删除文件通过清单中的排序顺序 ID 与排序顺序相关联。因此,该表必须声明所有排序顺序以进行查找。还可以为表配置默认排序顺序 ID,以指示默认情况下应如何对新数据进行排序。编写者应使用此默认排序顺序对写入时的数据进行排序,但如果默认顺序的开销过高,则不需要这样做,因为对于流写入而言,这是非常必要的。

Manifests

清单(manifest)是一个不变的 Avro 文件,其中列出了数据文件或删除文件,以及每个文件的分区数据元组,指标和跟踪信息。一个或多个清单文件用于存储快照,该快照在某个时间点跟踪表中的所有文件。每个清单快照的清单列表都会跟踪清单

清单是有效的 Iceberg 数据文件:文件必须使用有效的 Iceberg 格式,架构和列投影。

清单可以存储数据文件或删除文件,但不能同时存储这两个文件,因为在作业计划期间会首先扫描包含删除文件的清单。清单是数据清单还是删除清单存储在清单元数据中。

清单存储单个分区规范的文件。当表的分区规范更改时,旧文件将保留在较旧的清单中,而较新的文件将被写入新清单。这是必需的,因为清单文件的架构基于其分区规范(请参见下文)。每个清单的分区规范还用于将表数据行上的谓词转换为分区值的谓词,在作业计划期间使用该谓词从清单中选择文件。

清单文件必须将分区规范和其他元数据作为属性存储在 Avro 文件的键值元数据中:

v1v2 钥匙价值必需的必需的schema清单创建时表模式的 JSON 表示形式必需的必需的partition-spec用于编写清单的分区规范的 JSON 字段表示可选的必需的partition-spec-id用于将清单文件写为字符串的分区规范的 ID 可选的必需的format-version清单的表格格式版本号作为字符串必需的content清单跟踪的内容文件的类型:“数据”或“删除”


清单文件的模式是manifest_entry具有以下字段的结构:

v1v2 栏位编号,名称类型描述必需的必需的0 statusint 含义: 0: EXISTING 1: ADDED 2: DELETED用于跟踪添加和删除必需的可选的1 snapshot_idlong在文件添加或删除的快照 ID(状态为 2 时删除)。在为 null 时继承。可选的3 sequence_numberlong添加文件时的序列号。当为 null 时继承。必需的必需的2 data_filedata_file struct (见下文)文件路径,分区元组,指标等


data_file 是具有以下字段的结构:

v1v2 栏位编号,名称类型描述必需的134 contentint与意义:0: DATA1: POSITION DELETES2: EQUALITY DELETES数据文件存储的内容类型:数据,等号删除或位置删除(所有 v1 文件均为数据文件)必需的必需的100 file_pathstring具有 FS 方案的文件的完整 URI 必需的必需的101 file_formatstring字符串文件格式名称,avro,orc 或 parquet 必需的必需的102 partitionstruct<...>分区数据元组,基于分区规范的架构必需的必需的103 record_countlong该文件中的记录数必需的必需的104 file_size_in_byteslong总文件大小(以字节为单位)必需的105 block_size_in_byteslong不推荐使用。始终在 v1 中编写默认值。不要在 v2 中编写。可选的106 file_ordinalint不推荐使用。不要写。可选的107 sort_columnslist<112: int>不推荐使用。不要写。可选的可选的108 column_sizesmap<117: int, 118: long>从列 ID 映射到存储该列的所有区域在磁盘上的总大小。不包括读取其他列(如页脚)所需的字节。对于面向行的格式(Avro)留空可选的可选的109 value_countsmap<119: int, 120: long>从列 ID 映射到列中的值数(包括 null 和 NaN 值)可选的可选的110 null_value_countsmap<121: int, 122: long>从列 ID 映射到列中的空值数量可选的可选的137 nan_value_countsmap<138: int, 139: long>从列 ID 映射到列中的 NaN 值数可选的111 distinct_countsmap<123: int, 124: long>不推荐使用。不要写。可选的可选的125 lower_boundsmap<126: int, 127: binary>从列 ID 映射到序列化为二进制[1]的列的下限。每个值必须小于或等于文件[2]列中的所有非空,非 NaN 值。可选的可选的128 upper_boundsmap<129: int, 130: binary>从列 ID 映射到以二进制[1]序列化的列的上限。每个值都必须大于或等于文件[2]列中的所有非空,非 Nan 值。可选的可选的131 key_metadatabinary实施特定的密钥元数据进行加密可选的可选的132 split_offsetslist<133: long>分割数据文件的偏移量。例如,Parquet 文件中的所有行组偏移量。必须升序排列可选的135 equality_idslist<136: int>用于确定相等性删除文件中行相等性的字段 ID。在content=2且为 null 时为必需,否则为 null。此列中列出的 ID 字段必须存在于删除文件中可选的可选的140 sort_order_idint代表此文件[3]的排序顺序的 ID。


注意:

  1. 上下限的单值序列化在附录 D 中详细说明。

  2. 对于floatdouble,值-0.0必须在之前+0.0,如 IEEE 754totalOrder谓词中所述。

  3. 如果排序顺序 ID 缺失或未知,则假定该顺序未排序。仅数据文件和相等性删除文件应使用非空的订单 ID 写入。位置删除要求按文件和位置排序,而不是按表顺序排序,并且应将排序顺序 ID 设置为 null。读者必须忽略位置删除文件的排序顺序 ID。

partition结构存储每个文件的分区值的元组。其类型派生自用于写入清单文件的分区规范的分区字段。在 v2 中,分区结构的字段 ID 必须与分区规范中的 ID 相匹配。

过滤时选择列指标映射以选择数据和删除文件。对于删除文件,指标必须存储所有已删除行的界限和计数,或者必须省略。存储已删除行的度量标准可确保在作业计划期间可以使用这些值来查找在扫描期间必须合并的删除文件。

清单输入字段

清单条目字段用于跟踪在其中添加或逻辑删除文件的快照。该data_file结构嵌套在清单条目内部,因此可以在没有清单条目字段的情况下轻松将其传递到作业计划。

将文件添加到数据集中后,其清单条目应存储添加了文件的快照 ID,并将状态设置为 1(添加)。

从数据集中替换文件或从文件中删除文件时,其清单条目字段将存储删除该文件的快照 ID 和状态 2(已删除)。假设已经删除了较旧的快照,则在对已删除文件的快照进行垃圾回收时,也可以从文件系统中删除该文件[1]。

Iceberg v2 将序列号添加到条目中,并使快照 ID 为可选。当sequence_number和时snapshot_id,两个字段和均从清单元数据继承null。也就是说,如果该字段null用于条目,则该条目必须从清单文件[2]中存储的清单文件的元数据继承其值。

笔记:

  1. 从技术上讲,当垃圾回收最后一个包含“实时”数据的快照时,可以删除数据文件。但这很难检测,并且需要找到多个快照的差异。跟踪快照中删除哪些文件并在快照过期时将其删除更容易。

  2. 清单列表文件在 v2 中是必需的,因此sequence_numbersnapshot_id继承始终可用。

序列号继承

将数据或删除文件添加到表后,清单会跟踪序列号。

添加新文件时,其序列号设置为,null因为在成功提交快照之前才分配快照的序列号。读取时,通过替换null清单列表中清单的序列号来继承序列号。

将现有文件写入新清单时,序列号必须为非空,并设置为继承的序列号。

通过元数据树继承序列号允许在没有已知序列号的情况下编写新清单,因此清单可以一次写入并在提交重试中重用。要更改重试的序列号,仅清单列表必须被重写。

读取没有序列号列的 v1 清单时,所有文件的序列号必须默认为 0。

Snapshots

快照包含以下字段:

v1v2 场地描述必需的必需的snapshot-id唯一的长 ID 可选的可选的parent-snapshot-id快照父级的快照 ID。在没有父级的情况下省略任何快照必需的sequence-number单调增加的长度,用于跟踪表的更改顺序必需的必需的timestamp-ms创建快照时的时间戳,用于垃圾收集和表检查可选的必需的manifest-list此快照的清单列表的位置,该清单跟踪带有其他 Meadata 的清单文件可选的manifests清单文件位置列表。如果manifest-list存在,则必须省略可选的必需的summary概述快照更改的字符串映射,包括operation(请参见下文)


快照摘要的operation字段由某些操作(例如快照到期)用于跳过对某些快照的处理。可能的operation值为:

  • append –仅添加了数据文件,没有删除任何文件。

  • replace–在不更改表数据的情况下添加和删除了数据和删除文件;即压缩,更改数据文件格式或重新定位数据文件。

  • overwrite –在逻辑覆盖操作中添加和删除了数据和删除文件。

  • delete –删除了数据文件,并在逻辑上删除了它们的内容,和/或添加了删除文件以删除行。

快照的数据和删除文件可以存储在多个清单中。这样可以:

  • 追加可以添加新清单以最大程度地减少写入的数据量,而不是通过重写并附加到现有清单来添加新记录。(这称为“快速追加”。)

  • 表可以使用多个分区规范。例如,如果表的数据量发生变化,则表的分区配置可能会发生变化。每个清单使用单个分区规范,并且查询无需更改,因为分区过滤器是从数据谓词派生的。

  • 大表可以拆分为多个清单,因此实现可以并行化作业计划或降低重写清单的成本。

清单列表会跟踪快照的清单。

有效快照作为列表存储在表元数据中。有关序列化,请参见附录 C。

Manifest Lists

快照嵌入在表元数据中,但是快照的清单列表存储在单独的清单列表文件中。

每次尝试提交快照都会写入一个新的清单列表,因为清单列表始终会更改以生成新的快照。写入清单列表时,将为该列表跟踪的所有新清单文件写入快照的(乐观)序列号。

清单列表包含摘要元数据,可用于在计划表扫描时避免扫描快照中的所有清单。这包括已添加,现有和已删除文件的数量,以及用于写入清单的分区规范的每个字段的值摘要。

清单列表是有效的 Iceberg 数据文件:文件必须使用有效的 Iceberg 格式,架构和列投影。

清单列表文件 store manifest_file,一个具有以下字段的结构:

v1v2 栏位编号,名称类型描述必需的必需的500 manifest_pathstring清单文件的位置必需的必需的501 manifest_lengthlong清单文件的长度必需的必需的502 partition_spec_idint用于写入清单的分区规范的 ID;必须在表元数据中列出partition-specs必需的517 contentint含义:0: data1: deletes清单跟踪的文件类型,即数据文件或删除文件;所有 v1 清单均为 0 必需的515 sequence_numberlong将清单添加到表时的序列号;读取 v1 清单列表时使用 0 必需的516 min_sequence_numberlong清单中所有数据或删除文件的最小序号;读取 v1 清单列表时使用 0 必需的必需的503 added_snapshot_idlong添加清单文件的快照的 ID 可选的必需的504 added_files_countint假定状态为非零时清单中状态为ADDED(1)的条目数null可选的必需的505 existing_files_countint假定状态为非零时清单中状态为EXISTING(0)的条目数null可选的必需的506 deleted_files_countint假定状态为非零时清单中状态为DELETED(2)的条目数null可选的必需的512 added_rows_countlong排在所有有状态清单文件数ADDED,当null这个被假定为非零可选的必需的513 existing_rows_countlong排在所有有状态清单文件数EXISTING,当null这个被假定为非零可选的必需的514 deleted_rows_countlong排在所有有状态清单文件数DELETED,当null这个被假定为非零可选的可选的507 partitionslist<508: field_summary> (见下文)规范中每个分区字段的字段摘要列表。列表中的每个字段都与清单文件的分区规范中的一个字段相对应。


field_summary 是具有以下字段的结构:

v1v2 栏位编号,名称类型描述必需的必需的509 contains_nullboolean清单是否包含至少一个分区,该分区的字段为空值可选的可选的510 lower_boundbytes [1]分区字段中非 null,非 NaN 值的下限;如果所有值均为 null 或 NaN,则为 null [2]可选的可选的511 upper_boundbytes [1]分区字段中非 null,非 NaN 值的上限;如果所有值均为 null 或 NaN [2],则为 null


笔记:

  1. 下限和上限使用附录 D 中的单对象序列化序列化为字节。用于编码值的类型是分区字段数据的类型。

  2. 如果-0.0 是分区字段的值,则lower_bound不能为+0.0,如果+0.0 是分区字段的值,则upper_bound不能为-0.0。

扫描规划

通过读取当前快照的清单文件来计划扫描。扫描中不使用数据中的已删除条目和删除清单。

可以跳过使用文件计数或分区摘要确定的不包含匹配文件的清单。

对于每个清单,过滤数据行的扫描谓词将转换为过滤数据并删除文件的分区谓词。这些分区谓词用于选择数据并删除清单中的文件。此转换使用用于写入清单文件的分区规范。

使用包含式投影将扫描谓词转换为分区谓词:如果扫描谓词与某行匹配,则该分区谓词必须与该行的分区匹配。之所以称为包含性[1],是因为分区谓词可能会将与扫描谓词不匹配的行包括在扫描中。

例如,用户在时间戳列的范围内查询一个events表,该表的时间戳列ts被分区。包含的投影为,用于选择可能具有匹配行的文件。请注意,在大多数情况下,扫描之前将包括时间戳,因为文件包含与谓词匹配的行和与谓词不匹配的行。ts_day=day(ts)ts > Xts_day >= day(X)X

扫描谓词还用于使用列边界和计数(由清单 ID 中的字段 ID 存储)来过滤数据和删除文件。相同的过滤器逻辑可用于数据和删除文件,因为两者都存储插入或删除的行的度量。如果指标显示删除文件没有与扫描谓词匹配的行,则可以忽略该文件,就像忽略数据文件一样[2]。

扫描必须读取与查询过滤器匹配的数据文件。

必须在读取时将与查询过滤器匹配的删除文件应用于数据文件,使用以下规则将其限制在删除文件的范围之内。

  • 一个位置删除文件必须适用于当满足下列所有条件都为真数据文件:数据文件的序列号小于或等于删除文件的序列号数据文件的分区(规格值和分区值)都等于删除文件的分区

  • 一个平等删除文件必须适用于当满足下列所有条件都为真数据文件:数据文件的序号严格小于删除的序号数据文件的分区(规范和分区值)都等于删除文件的分区,或者删除文件的分区规范未分区

通常,删除仅应用于较旧且位于同一分区中的数据文件,但以下两种特殊情况除外:

  • 使用未分区规范存储的相等删除文件将用作全局删除。否则,删除文件不适用于其他分区中的文件。

  • 当数据和删除文件的序号相等时,位置删除文件必须应用于同一提交的数据文件。这允许删除在同一提交中添加的行。

笔记:

  1. 如果文件中的所有行都必须与扫描谓词相匹配,则可选的 striction projection 创建一个与文件相匹配的分区谓词。这些投影用于计算扫描中每个文件的剩余谓词。

  2. 例如,如果file_a行中包含id1 到 10 之间的行,而删除文件中包含id1 到 4 之间的行,则对的扫描id = 9可能会忽略该删除文件,因为所有删除操作都无法匹配将要选择的行。

表元数据

表元数据存储为 JSON。每个表元数据更改都会创建一个新的表元数据文件,该文件由原子操作提交。此操作用于确保表元数据的新版本替换其所基于的版本。这将产生表版本的线性历史记录,并确保并发写入不会丢失。

用于提交元数据的原子操作取决于如何跟踪表以及该规范未对其进行标准化。有关示例,请参见以下各节。

表元数据字段

表元数据包含以下字段:

v1v2 场地描述必需的必需的format-version格式的整数版本号。当前,它始终为 1。如果表的版本高于受支持的版本,则实现必须抛出异常。可选的必需的table-uuid创建表时生成的标识表的 UUID。如果在刷新元数据后表的 UUID 与预期的 UUID 不匹配,则实现必须抛出异常。必需的必需的location表格的基本位置。编写者使用它来确定将数据文件,清单文件和表元数据文件存储在何处。必需的last-sequence-number表格的最高分配序列号,其单调递增的长度跟踪表格中快照的顺序。必需的必需的last-updated-ms上次更新表时距 UNIX 纪元的时间戳(以毫秒为单位)。每个表元数据文件都应在写入之前更新此字段。必需的必需的last-column-id整数;该表的最高分配列 ID。这用于确保在演化模式时始终为列分配未使用的 ID。必需的必需的schema表的当前架构。必需的partition-spec该表的当前分区规范,仅存储为字段。请注意,编写者使用它来对数据进行分区,但在读取时不使用,因为读取使用清单文件中存储的规范。(已弃用:使用partition-specsdefault-spec-id代替)可选的必需的partition-specs分区规格列表,存储为完整的分区规格对象。可选的必需的default-spec-id编写者默认应使用的“当前”规范的 ID。可选的可选的properties表格属性的字符串到字符串映射。这用于控制影响读写的设置,并且不适用于任意元数据。例如,commit.retry.num-retries用于控制提交重试的次数。可选的可选的current-snapshot-idlong 当前表快照的 ID。可选的可选的snapshots有效快照列表。有效快照是文件系统中所有数据文件都存在的快照。直到最后一次列出列出的数据快照之前,都不能从文件系统中删除该数据文件。可选的可选的snapshot-log时间戳和快照 ID 对的列表(可选),该列表对表的当前快照的更改进行编码。每次更改 current-snapshot-id 时,都应添加一个带有 last-updated-ms 和 new current-snapshot-id 的新条目。当快照从有效快照列表中过期时,应删除快照过期之前的所有条目。可选的必需的sort-orders排序顺序列表,存储为完整的排序顺序对象。可选的必需的default-sort-order-id表的默认排序顺序 ID。请注意,这可以由编写者使用,但在读取时不使用,因为读取使用清单文件中存储的规范。


有关序列化的详细信息,请参见附录 C。

提交冲突解决并重试

当两个提交同时发生并且基于相同的版本时,只有一个提交将成功。在大多数情况下,可以将失败的提交应用于表元数据的新当前版本并进行重试。更新会验证可将其应用于新版本的条件,并在满足这些条件的情况下重试。

  • 追加操作没有要求,可以随时应用。

  • 替换操作必须验证要删除的文件是否仍在表中。替换操作的示例包括格式更改(将 Avro 文件替换为 Parquet 文件)和压缩(几个文件替换为包含相同行的单个文件)。

  • 删除操作必须验证要删除的特定文件是否仍在表中。始终可以应用基于表达式的删除操作(例如,时间戳<X)。

  • 表架构更新和分区规范更改必须验证架构在基本版本和当前版本之间没有更改。

文件系统表

原子交换可以在支持它的文件系统中使用原子重命名来实现,例如 HDFS 或大多数本地文件系统[1]。

表元数据的每个版本都使用包含版本号V:的文件命名方案存储在表基本位置下的元数据文件夹中v<V>.metadata.json。要提交新的元数据版本,V+1编写器将执行以下步骤:

  1. 阅读当前表的元数据版本V

  2. 根据 version 创建新的表元数据V

  3. 将新表的元数据写入唯一的文件:中<random-uuid>.metadata.json

  4. 将唯一文件重命名为版本的知名文件Vv<V+1>.metadata.json。如果重命名成功,则提交成功,并且V+1是表的当前版本如果重命名失败,请返回步骤 1。

笔记:

  1. 文件系统表方案在HadoopTableOperations中实现。

Metastore 表

提交表元数据的新版本所需的原子交换可以通过将指针存储在元存储或数据库中来实现,该元存储或数据库通过检查和放置操作进行更新[1]。检入和校验将验证写入所基于的表的版本仍然是当前版本,然后将写入中的新元数据设为当前版本。

表元数据的每个版本都使用包含版本和 UUID:的命名方案存储在表基本位置下的元数据文件夹中<V>-<uuid>.metadata.json。要提交新的元数据版本,V+1编写器将执行以下步骤:

  1. 基于当前元数据创建一个新的表元数据文件。

  2. 将新表的元数据写入唯一的文件:中<V+1>-<uuid>.metadata.json

  3. 要求 metastore 将表的元数据指针从的位置交换V到的位置V+1。如果交换成功,则提交成功。V仍是最新的元数据版本,并且其元数据文件V+1现在是当前元数据。如果交换失败,则已经创建了另一个编写器V+1。当前作者返回到步骤 1。

笔记:

  1. Metastore 表方案在BaseMetastoreTableOperations中部分实现。

删除格式

本节详细介绍如何在 Iceberg 删除文件中对行级删除进行编码。v1 不支持行级删除。

行级删除文件是有效的 Iceberg 数据文件:文件必须使用有效的 Iceberg 格式,架构和列投影。建议使用表的默认文件格式写入删除文件。

行级删除文件由清单(如数据文件)跟踪。清单文件的单独集合用于删除文件,但清单模式是相同的。

位置删除和相等删除均允许使用删除对已删除的行值进行编码。这可以用于重建对表的更改流。

位置删除文件

基于位置的删除文件按文件和一个或多个数据文件中的位置标识已删除的行,并且可以选择包含已删除的行。

如果在位置删除文件中有一个条目,该行的文件和位置在数据文件中(从 0 开始),则该数据行将被删除。

基于位置的删除文件 store file_position_delete,具有以下字段的结构:

栏位编号,名称类型描述2147483546 file_pathstring具有 FS 方案的数据文件的完整 URI。这必须与file_path清单条目中的目标数据文件的匹配2147483545 poslong目标数据文件中由标识的已删除行的顺序位置file_path,从开始02147483544 rowrequired struct<...> [1]删除的行值。当不存储已删除的行时,请忽略该列。


  1. 当存在于删除文件中时,这row是必需的,因为所有删除条目都必须包含行值。

存在已删除的行列时,其架构可以是表架构的任何子集,并且必须使用与表匹配的字段 ID。

为了确保统计信息的准确性,所有删除条目都必须包含行值,或者必须省略列(这就是列类型为的原因required)。

在删除文件中的行必须由进行排序file_path,然后position以优化而扫描滤波的行。

  • 排序依据file_path允许按列存储格式的文件进行过滤器下推。

  • 排序依据position允许在扫描时过滤行,以避免在内存中保留删除内容。

平等删除文件

相等删除文件通过一个或多个列值来标识数据文件集合中的已删除行,并且可以选择包含已删除行的其他列。

相等删除文件存储表列的任何子集,并使用表的字段 ID。在删除列是删除文件的列用于匹配的数据行。删除列由删除文件元数据列equality_ids中的 ID 标识。

如果数据行的值等于应用于该行的数据文件的相等删除文件中任何行的所有删除列,则该数据行将被删除(请参阅参考资料Scan Planning)。

删除文件的每一行都会产生一个相等谓词,该谓词与删除列相等的任何行匹配。可以将多列视为AND相等谓词。如果null删除列中的值null等于,则该行中的值与该行匹配col IS NULL

例如,具有以下数据的表:

 1: id | 2: category | 3: name-------|-------------|--------- 1     | marsupial   | Koala 2     | toy         | Teddy 3     | NULL        | Grizzly 4     | NULL        | Polar
复制代码

删除id = 3可以写为以下两个相等删除文件之一:

equality_ids=[1]
1: id------- 3
复制代码


equality_ids=[1]
1: id | 2: category | 3: name-------|-------------|--------- 3 | NULL | Grizzly
复制代码

删除id = 4 AND category IS NULL可以写为以下相等删除文件:

equality_ids=[1, 2]
1: id | 2: category | 3: name-------|-------------|--------- 4 | NULL | Polar
复制代码

如果稍后将等式删除文件中的删除列从表中删除,则在应用等式删除时仍必须使用该列。如果将列添加到表中,然后用作等价删除文件中的删除列,则使用常规投影规则(默认值为)读取较旧的数据文件的列值null

删除文件统计

清单具有与删除文件和数据文件相同的统计信息。对于删除文件,度量标准描述了已删除的值。


(20210330 翻译者:聚变)


用户头像

聚变

关注

还未添加个人签名 2017.10.18 加入

还未添加个人简介

评论

发布
暂无评论
IceBerg表规格(20210330翻译者:聚变)