Apache Iceberg 简介

如今,Apache Iceberg 已成为实现数据湖的热门选择。它提供快照、隐藏分区和就地数据表演化等功能。

本教程将讨论Apache Iceberg,这是当今大数据领域流行的开放表格式。

我们将通过开源发行版的实际示例探索 Iceberg 的架构及其一些重要特性。

Apache Iceberg 的由来
Iceberg 是 Netflix 的 Ryan Blue 和 Dan Weeks 于 2017 年左右创建的。 它的出现主要是因为 Hive 表格式的限制。Hive的一个关键问题是,在缺乏稳定的原子事务的情况下,它无法保证正确性。

Iceberg 的设计目标是解决这些问题并提供三项关键改进:

  • 支持ACID事务,保证数据的正确性
  • 通过允许在文件级别进行细粒度的操作来提高性能
  • 简化和混淆表维护
Iceberg 后来开源并贡献给了 Apache 基金会,并于 2020 年成为基金会的顶级项目。因此,Apache Iceberg 已成为最受欢迎的表格格式开放标准。如今,大数据领域几乎所有主要参与者都支持 Iceberg 表。

Apache Iceberg 的架构
Iceberg 的一项 关键架构决策是跟踪表内(而不是目录中)的完整数据文件列表。这种方法有很多优点,例如查询性能更好。

这一切都发生在元数据层,这是冰山架构中的三层之一:

  • 当从 Iceberg 读取表时,它会使用当前快照 (s1) 加载表的元数据。如果我们更新此表,更新将乐观地使用新快照 (s2) 创建新的元数据文件。
  • 然后,当前元数据指针的值将自动更新为指向这个新的元数据文件。如果此更新所基于的快照 (s1) 不再是最新的,则必须中止写入操作。

目录层
目录层有几个功能,但最重要的是, 它存储了当前元数据指针的位置。任何希望操作 Iceberg 表的计算引擎都必须访问目录并获取这个当前元数据指针。

目录还支持在更新当前元数据指针时进行原子操作。这对于允许 Iceberg 表上的原子事务至关重要。

可用功能取决于我们使用的目录。例如,Nessie提供了受 Git 启发的数据版本控制。

元数据层
元数据层包含文件层次结构。最上面的是元数据文件,用于存储有关 Iceberg 表的元数据。它跟踪表的架构、分区配置、自定义属性、快照以及哪个快照是当前快照。

元数据文件指向清单列表,即清单文件列表。清单列表存储组成快照的每个清单文件的元数据,包括清单文件的位置以及添加到哪个快照等信息。

最后,清单文件跟踪数据文件并提供其他详细信息。清单文件允许 Iceberg 在文件级别跟踪数据,并包含有用的信息,可提高读取操作的效率和性能。

数据层
数据层是数据文件所在的位置,很可能位于 AWS S3 等云对象存储服务中。Iceberg 支持多种文件格式,例如Apache Parquet、Apache Avro和Apache ORC。

Parquet 是 Iceberg 中用于存储数据的默认文件格式。它是一种面向列的数据文件格式。其主要优点是存储效率高。此外,它还具有高性能压缩和编码方案。它还支持高效的数据访问,尤其是针对宽表中特定列的查询。

Apache Iceberg 的重要特性
Apache Iceberg 提供事务一致性,允许多个应用程序协同处理同一数据。

它还具有快照、完整模式演变和隐藏分区等功能。

快照
Iceberg 表元数据维护一个快照日志,该日志代表对表应用的更改。

因此,快照代表了表在某一时刻的状态。Iceberg 支持基于快照的读取隔离和时间旅行查询。

对于快照生命周期管理,Iceberg 还支持分支和标签,它们是对快照的命名引用。 

分支和标签可以有多种用例,例如保留重要的历史快照以供审计。

为表跟踪的架构在所有分支中均有效。但是,查询标签会使用快照的架构。

分区
Iceberg在写入时通过对相似的行进行分组来对数据进行分区。例如,它可以按日期对日志事件进行分区,并将它们分组到具有相同事件日期的文件中。这样,它可以跳过没有有用数据的其他日期的文件并加快查询速度。

有趣的是,Iceberg 支持 隐藏分区。这意味着它可以处理为表中的行生成分区值的繁琐且容易出错的任务。用户不需要知道表是如何分区的,并且 分区布局可以根据需要进行演变。

这与 Hive 等早期表格式支持的分区有着根本区别。使用 Hive,我们必须提供分区值。这会将我们的工作查询与表的分区方案联系起来,因此如果不破坏查询,就无法更改分区方案。

演化
Iceberg 无缝支持表演化,并将其称为“就地表演化”。例如,我们可以更改表模式,即使在嵌套结构中也是如此。此外,分区布局也可以随着数据量的变化而变化。

为了支持这一点,Iceberg 不需要重写表数据或迁移到新表。在后台,Iceberg 仅通过执行元数据更改来执行架构演变。因此,无需重写任何数据文件即可执行更新。

我们还可以更新现有表中的 Iceberg 表分区。使用较早分区规范写入的旧数据保持不变。但是,新数据将使用新的分区规范写入。每个分区版本的元数据都单独保存。

Apache Iceberg 实践
Apache Iceberg 被设计为一个开放的社区标准。它是现代数据架构中的热门选择,并且可以与许多数据工具互操作。

在本节中,我们将通过在Minio存储上部署Iceberg REST 目录并以Trino作为查询引擎来了解 Apache Iceberg 的实际运行。

安装
我们将使用Docker镜像来部署和连接 Minio、Iceberg REST 目录和 Trino。最好使用Docker Desktop或Podman之类的解决方案来完成这些安装。

让我们首先在 Docker 中创建一个网络:

docker network create data-network

本教程中的命令适用于 Windows 计算机。其他操作系统可能需要进行更改。

现在让我们使用持久存储部署 Minio(将主机目录“数据”挂载为卷):

docker run --name minio --net data-network -p 9000:9000 -p 9001:9001 \
  --volume .\data:/data quay.io/minio/minio:RELEASE.2024-09-13T20-26-02Z.fips \
  server /data --console-address ":9001"

下一步,我们将部署 Iceberg Rest 目录。这是一个Tabular贡献镜像,带有一个瘦服务器,用于公开由现有目录实现支持的 Iceberg Rest 目录服务器端实现:

docker run --name iceberg-rest --net data-network -p 8181:8181 \
  --env-file ./env.list \
  tabulario/iceberg-rest:1.6.0

在这里,我们将环境变量作为文件提供,其中包含 Iceberg REST 目录与 Minio 配合使用所需的所有配置:

CATALOG_WAREHOUSE=s3://warehouse/
CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO
CATALOG_S3_ENDPOINT=http:
//minio:9000
CATALOG_S3_PATH-STYLE-ACCESS=true
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_REGION=us-east-1

现在,我们将部署 Trino 以与 Iceberg REST 目录配合使用。我们可以通过提供属性文件作为卷挂载来配置 Trino 以使用我们之前部署的 REST 目录和 Minio:

docker run --name trino --net data-network -p 8080:8080 \
  --volume .\catalog:/etc/trino/catalog \
  --env-file ./env.list \
  trinodb/trino:449

属性文件包含 REST 目录和 Minio 的详细信息:

connector.name=iceberg 
iceberg.catalog.type=rest 
iceberg.rest-catalog.uri=http://iceberg-rest:8181/
iceberg.rest-catalog.warehouse=s3:
//warehouse/
iceberg.file-format=PARQUET
hive.s3.endpoint=http:
//minio:9000
hive.s3.path-style-access=true

与以前一样,我们还将环境变量作为包含 Minio 访问凭据的文件提供:

AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_REGION=us-east-1

Minio 需要属性hive.s3.path-style-access,但如果我们使用 AWS S3,则不需要该属性。

数据操作
我们可以使用 Trino 对 REST 目录执行不同的操作。Trino带有内置 CLI,可让我们更轻松地完成此操作。让我们首先从 Docker 容器内访问 CLI:

docker exec -it trino trino

这应该为我们提供了一个类似 shell 的提示符来提交我们的SQL 查询。正如我们之前所见,Iceberg 的客户端需要首先访问目录。让我们看看是否有任何可用的默认目录:

trino> SHOW catalogs;
 Catalog
<hr>
 iceberg
 system
(2 rows)

我们将使用iceberg。首先在 Trino 中创建一个模式(在 Iceberg 中转换为命名空间):

trino> CREATE SCHEMA iceberg.demo;
CREATE SCHEMA

现在,我们可以在这个模式中创建一个表:

trino> CREATE TABLE iceberg.demo.customer (
    -> id INT,
    -> first_name VARCHAR,
    -> last_name VARCHAR,
    -> age INT);
CREATE TABLE

让我们插入几行:

trino> INSERT INTO iceberg.demo.customer (id, first_name, last_name, age) VALUES
    -> (1, 'John', 'Doe', 24),
    -> (2, 'Jane', 'Brown', 28),
    -> (3, 'Alice', 'Johnson', 32),
    -> (4, 'Bob', 'Williams', 26),
    -> (5, 'Charlie', 'Smith', 35);
INSERT: 5 rows

我们可以查询表来获取插入的数据:

trino> SELECT * FROM iceberg.demo.people;
 id | first_name | last_name | age
----+------------+-----------+-----
  1 | John       | Doe       |  24
  2 | Jane       | Brown     |  28
  3 | Alice      | Johnson   |  32
  4 | Bob        | Williams  |  26
  5 | Charlie    | Smith     |  35
(5 rows)

如我们所见,我们可以使用熟悉的 SQL 语法来处理高度可扩展且开放的表格式,以处理海量数据。

查看文件
让我们看看我们的存储中生成了什么类型的文件。

Minio 提供了一个控制台,我们可以通过http://localhost:9001访问。我们在warehouse/demo下发现两个目录:

  • 数据和
  • 元数据
我们首先看一下元数据目录:
  • 它包含元数据文件(*.metadata.json)、清单列表(snap-*.avro)和清单文件(*.avro、*.stats)。. stats文件包含有关用于提高查询性能的表数据的信息。

现在,让我们看看数据目录中有什么:

  • 它有一个 Parquet 格式的数据文件,其中包含我们通过查询创建的实际数据。