演进
演进
Iceberg 支持就地表演进。你可以像使用 SQL 一样演进表结构——甚至可以深入嵌套结构中进行修改——也可以在数据量发生变化时更改分区布局。Iceberg 不需要诸如重写表数据或迁移到新表之类的高成本操作。
例如,Hive 表的分区方式无法更改,因此从按天分区的布局改为按小时分区的布局就需要新建一张表。而且由于查询依赖于分区,查询也必须针对新表重写。在某些情况下,即便只是重命名列这样简单的更改,也可能不受支持,或者引发数据正确性问题。
架构演进
Iceberg 支持以下架构演进变更:
- 添加——向表或嵌套的 struct 中添加新列
- 删除——从表或嵌套的 struct 中移除已有列
- 重命名——重命名已有列或嵌套 struct 中的字段
- 更新——拓宽列、struct 字段、map 键、map 值或列表元素的类型
- 重排序——更改列或嵌套 struct 中字段的顺序
Iceberg 的架构更新属于元数据变更,因此执行更新无需重写任何数据文件。
请注意,map 键不支持添加或删除会改变相等性的 struct 字段。
正确性
Iceberg 保证架构演进变更是相互独立且无副作用的,无需重写文件:
- 添加的列绝不会从其他列读取已有值。
- 删除某一列或字段不会改变其他任何列中的值。
- 更新某一列或字段不会改变其他任何列中的值。
- 更改 struct 中列或字段的顺序不会改变与列名或字段名相关联的值。
Iceberg 使用唯一 ID 来跟踪表中的每个列。当你添加一个列时,它会被分配一个新的 ID,因此现有数据绝不会被误用。
- 按名称跟踪列的格式在重复使用某个名称时,可能会意外地取消删除该列,这违反了第 1 条。
- 按位置跟踪列的格式在删除列时无法避免更改每列所使用的名称,这违反了第 2 条。
分区演进
Iceberg 表的分区方式可以在已有表中更新,因为查询不会直接引用分区值。
当你演进分区规范(partition spec)时,此前使用旧规范写入的旧数据保持不变。新数据会使用新的规范写入新的布局中。每个分区版本的元数据分别保存。因此,当你开始执行查询时,会得到拆分规划(split planning)。也就是说,每个分区布局都会使用其针对该特定分区布局推导出的过滤条件,分别进行文件规划。下面是一个虚构示例的可视化展示:
2008 年的数据按月分区。从 2009 年起,表被更新为按天对数据进行分区。两种分区布局可以在同一个表中并存。
Iceberg 使用隐藏分区,因此你不需要针对特定的分区布局编写查询才能获得高性能。相反,你可以编写查询来选择所需的数据,Iceberg 会自动裁剪掉不包含匹配数据的文件。
分区演进是一项元数据操作,不会预先重写文件。
Iceberg 的 Java 表 API 提供了 updateSpec API 来更新分区规范。例如,以下代码可用于更新分区规范,添加一个新的分区字段,将 id 列的值放入 8 个分桶中,并移除现有的 category 分区字段:
Table sampleTable = ...;
sampleTable.updateSpec()
.addField(bucket("id", 8))
.removeField("category")
.commit();Spark 通过其 ALTER TABLE SQL 语句支持更新分区规范,详见 Spark SQL。
排序顺序演进
与分区规范类似,Iceberg 的排序顺序也可以在已有表中更新。当你演进排序顺序时,之前按照旧排序顺序写入的旧数据保持不变。当排序代价过高时,引擎始终可以选择按照最新排序顺序写入数据,或者不排序写入。
Iceberg 的 Java 表 API 提供了 replaceSortOrder API 来更新排序顺序。例如,下面的代码可用于创建一个新的排序顺序,其中 id 列按升序排序且空值排在最后,category 列按降序排序且空值排在最前:
Table sampleTable = ...;
sampleTable.replaceSortOrder()
.asc("id", NullOrder.NULLS_LAST)
.dec("category", NullOrder.NULL_FIRST)
.commit();Spark 通过其 ALTER TABLE SQL 语句支持更新排序规则,详情参见 Spark SQL。
评论
登录后参与评论
KnowForge