发布日期:2022-10-25 VIP内容

CRUD操作的底层实现

了解了Iceberg表的不同组件以及访问Iceberg表中的数据的任何引擎或工具所采取的路径之后,接下来更深入地了解在Iceberg表上执行CRUD操作时,底层会发生什么。

1)CREATE TABLE

首先,在Iceberg Catalog的db1数据库(Iceberg中称为namespace)中创建一个分区表。代码如下:

CREATE TABLE db1.table1 (
    order_id BIGINT,
    customer_id BIGINT,
    order_amount DECIMAL(10, 2),
    order_ts TIMESTAMP
)
USING iceberg
PARTITIONED BY ( HOUR(order_ts) );

上面在数据库db1中创建了一个名为table1的表。该表有4列,并按order_ts时间戳列的小时粒度进行分区。

当执行上面的查询时,在元数据层中创建一个带有快照s0的元数据文件(快照s0没有指向任何清单列表,因为表中还没有数据)。然后,db1. table1的当前元数据指针的catalog条目被更新为指向这个新元数据文件的路径。在这个语句被执行之后,环境看起来如图11-32所示。

2)INSERT

现在,向表table1中添加一些数据,代码如下:

INSERT INTO table1 VALUES (
    123,
    456,
    36.17,
    '2021-01-26 08:10:23'
);

整个创建过程如图11-33所示。

当执行这个INSERT语句时,会发生以下过程:

(1) 首先创建Parquet文件形式的数据:table1/data/order_ts_hour=2021-01-26-08/00000-5-cae2d.parquet。

(2) 然后,创建一个指向该数据文件的manifest清单文件(包括额外的详细信息和统计信息) - table1/metadata/d8f9-ad19-4e.avro,每个数据文件都是manifest文件中的一条记录。

(3) 然后,创建一个指向这个清单文件的清单列表(manifests文件,包括额外的详细信息和统计信息) - table1/metadata/snap-2938-1-4103.avro。manifests文件中的每条记录都是所有manifest文件统计信息的当前集合。每个manifest清单文件都是manifests文件中的一个记录。

(4) 然后,基于以前的当前元数据文件创建一个新的元数据文件(快照文件),快照文件中记录与表Schema信息、分区的路径以及Manifests文件的路径相对应的快照,其中包含一个新的快照s1,并跟踪以前的快照s0,指向这个清单列表(包括额外的详细信息和统计信息) - table1/metadata/v2.metadata.json。

(5) 然后,用于db1.table1的当前元数据指针的值在catalog目录中被自动更新,现在指向这个新的元数据文件。

在所有这些步骤中,读取表的任何人都将继续读取第一个元数据文件,直到原子步骤#5完成,这意味着使用数据的任何人都不会看到表的状态和内容的不一致的视图(整个提交过程是一个事务执行,即ACID保证)。

3)SELECT

再次查看SELECT路径,但这次是在正在处理的Iceberg表上,如图11-34所示。

当这个SELECT语句被执行时,会发生以下过程:

(1) 查询引擎转到Iceberg catalog目录。

(2) 然后检索db1.table1的当前元数据文件位置条目。

(3) 然后,它打开这个元数据文件,并检索当前快照s2的清单列表位置的条目。

(4) 然后打开这个清单列表,检索唯一的清单文件的位置。

(5) 然后打开这个清单文件,检索两个数据文件的位置。

(6) 然后读取这些数据文件,因为它是一个SELECT *,所以将数据返回给客户端。

4)压缩

Iceberg设计的另一个关键功能是压缩,这有助于平衡写侧和读侧的权衡。

在Iceberg中,压缩是一个异步后台进程,它将一组小文件压缩成更少的大文件。因为它是异步的,并且是在后台运行,所以它不会对用户产生负面影响。实际上,它基本上是一种普通的Iceberg写作业,输入和输出具有相同的记录,但在写作业提交其事务后,文件大小和属性对分析有了很大的改善。

任何时候在处理数据时,都需要对所要实现的目标进行权衡,一般来说,写侧和读侧的动机是相反的:

(1) 在写操作方面,通常需要低延迟:使数据尽可能快地可用,这意味着我们希望在获得记录后立即写入数据,甚至可能不需要将其转换为柱状格式。但是,如果对每个记录都这样做,那么最终会形成每个文件一个记录(小文件问题的最极端形式)。

(2) 在读取端,通常需要高吞吐量:在一个文件中以柱状格式包含许多记录,这样与数据相关的可变成本(读取数据)将超过固定成本(保存记录、打开每个文件等的开销)。通常还需要最新的数据,但需要在读取操作中付出额外的开销。

压缩有助于平衡写侧和读侧的权衡-可以立即写入数据,在最极端的情况下,每个文件只有一行记录,读取器可以马上看到并使用,而后台压缩过程则周期性地将所有这些小文件合并成更少、更大、列状格式的文件。

通过压缩,读取器可以持续地以所需的高吞吐量形式获得99%的数据,但仍然可以以低延迟、低吞吐量的形式看到最近1%的数据。

对于这个用例,还需要注意的是,压缩作业的输入文件格式和输出文件格式可以是不同的文件类型。例如,从流写入中写入Avro,这些写入被压缩成更大的Parquet文件用于分析。

另一个重要的注意事项是,由于Iceberg不是引擎或工具,因此调度/触发和实际压缩工作是由与Iceberg集成的其他工具和引擎完成的。