本章内容包括:
- 搭建 Apache Iceberg 环境
- 在 Spark 中创建 Iceberg tables
- 在 Dremio 中读取 Iceberg tables
- 构建商业智能 dashboard
我们已经探索了 Apache Iceberg lakehouse 背后的关键思想,包括它的架构、组件和取舍。本章会把我们从理论带入动手实践。我们的目标不只是构建一个能跑的东西,而是感受一个 lakehouse 如何端到端组合起来。这样,等我们在本书后续章节逐个考察每个组件时,你会对各个部分如何配合有一个具体图景。
在本章的动手练习中,你将在自己的笔记本电脑上搭建一个可运行的 lakehouse 环境。你会先配置环境,然后创建 tables、运行 queries,并可视化结果。在这个过程中,你会创建 Iceberg tables,使用 Dremio 查询它们,并在商业智能(BI)dashboard 中探索数据。到本章结束时,你会拥有一个可以实验的环境,并且在第 2 部分深入各个独立组件时,可以随时回到这个环境作为参考。
3.1 示例场景
本章使用 Dremio 和 Nessie,因为它们是开始使用 lakehouse 的简单方式,并且能让我们专注 Iceberg concepts,而不是基础设施。它们也是我非常熟悉的工具,这能帮助我把 walkthrough 保持得清晰且实用。在真实部署中,lakehouses 通常会使用更广泛的 engines 和 frameworks,例如 Apache Spark、Apache Flink 和 Trino。我们会在后续章节探索这些选项,以及它们在 lakehouse architecture 中的位置。
在这个场景中,我们会使用一家时尚零售连锁企业的 sales transaction data——这是大多数组织已经拥有的那类 operational data。该 dataset 包含 product 和 category details、store locations、customer demographics 和 marketing campaigns。在真实世界中,这些数据可能来自多个来源:它可能被直接写入 PostgreSQL database,通过 Kafka 进行 streaming 并被多个系统消费,或与 operational data 一起加载到 analytical storage 中。
在我们走过基础 lakehouse workflow 时,会使用 PostgreSQL database 作为一个简单且熟悉的 source,见图 3.1。我们会从 PostgreSQL 抽取数据,并使用 Apache Spark 将其写入 Apache Iceberg tables。然后在 Dremio 中整理这些 tables,并定义 virtual views,以按 region、customer segment 和 campaign performance 分析 sales trends。随后,Apache Superset dashboards 会消费这些 views,并为 marketing、sales 和 merchandising teams 提供交互式洞察。
图 3.1:一个动手实践 lakehouse workflow,从 sales data 到 BI dashboards
这个示例有意保持简单,这样你可以专注于 lakehouse experience,而不是各种可能的 ingestion architectures。无论你的数据来自 batch jobs、streaming pipelines,还是 event-driven systems,这些 patterns 都适用;我们会在后续章节覆盖这些内容。你可以在以下地址找到本章代码:github.com/AlexMercedC…。
让我们深入进去,亲手实践 Apache Iceberg!
3.2 搭建 Apache Iceberg 环境
为了使用 Iceberg tables,我们会搭建一个本地、端到端的 lakehouse environment。使用 Docker Compose,我们会启动一组代表 sample analytics stack 的关键服务:
Operational database——PostgreSQL,作为 transactional system。
Data lake storage layer——MinIO,S3-compatible object store。
Ingestion and processing layer——Apache Spark。
Catalog layer——Nessie,用于跟踪 Iceberg tables。
Query engine——Dremio,用于运行快速 SQL queries。
BI and visualization——Apache Superset,用于 dashboards。
Database 会作为我们的数据源,而列出的其他组件则是在 Iceberg lakehouse 中发挥关键作用的组件。把它们在本地搭起来,可以让我们观察它们如何在真实场景中协同工作。后续讨论 Iceberg lakehouse architecture 的每一层时,我们会考察这些角色的其他选项。
图 3.2 展示了第 1 章和第 2 章讨论过的 Iceberg lakehouse 环境组件。
图 3.2:Apache Iceberg 的组件,一个使用 Apache Iceberg table format 构建的数据湖仓
3.2.1 前置条件:安装 Docker
开始之前,你需要在电脑上安装 Docker Desktop。如果还没有,可以从 docker.com 免费下载并安装。Docker 会在称为 containers 的虚拟环境中运行软件,这使你可以使用 Nessie、Apache Spark 和 Dremio,而不必直接把它们安装到电脑上。
安装 Docker 后,在 terminal 中运行以下命令,确认它正在运行:
docker --version
图 3.3:检查 Docker 是否已安装时的预期输出
你应该会看到类似图 3.3 的输出,这说明 Docker 已安装。
3.2.2 创建 Docker Compose 文件
现在我们来定义环境,包括在 host machine 上运行 data lakehouse 所需的服务:catalog(Nessie)、storage layer(MinIO)、integration / federation tool(Dremio)、ingestion tool(Spark)和 consumption tool(Superset)。我们还需要指定要使用哪些 Docker images,以及为每个 container 映射哪些 network ports,这样才能在本地运行一个完整的 lakehouse environment。
在一个空目录中,创建名为 docker-compose.yml 的文件,并添加以下配置。完整片段请参考 GitHub repo:github.com/AlexMercedC…。
version: "3" #1
services: #2
nessie: #3
image: projectnessie/nessie:latest
container_name: nessie
networks:
lakehouse-net:
ports:
- 19120:19120
#1 正在使用的 Docker Compose 文件版本
#2 构成环境的 services 列表
#3 第一个 service,也就是 Nessie catalog
这个 Docker Compose 配置定义了一个 service,用来运行使用 in-memory storage backend 的 Project Nessie catalog server。该 service 名为 nessie,使用最新的 Project Nessie image。它被分配到名为 lakehouse-net 的自定义 network,使其可以与同一 network 上的其他 services 通信,例如 compute engines、Apache Spark 和 Dremio。Container 会在 host 和 container 内暴露端口 19120,从而可以从外部访问 Nessie 的 REST API。由于该配置使用 in-memory storage,它适合 development 或 testing environments,也就是不需要在重启后保留持久状态的场景。
下面的 minio Docker Compose service 定义了一个 MinIO server。MinIO 是一个高性能 object storage system,并且兼容 Amazon S3 API。该配置指定 environment variables,以配置 root user credentials 和 region,也就是本地的 us-east-1。Container 在 lakehouse-net network 中运行,因此可以与 lakehouse architecture 中的其他组件通信。它暴露两个端口:9000 用于 S3-compatible API access,9001 用于访问 MinIO web-based console。Command 指示 MinIO 使用 /data 作为 storage directory,并将 admin console 绑定到端口 9001,使其可以通过浏览器访问。这个配置适合在 Iceberg-compatible environment 中测试或 prototype object storage layers:
minio: #1
image: minio/minio:latest
container_name: minio
environment:
- MINIO_ROOT_USER=admin
- MINIO_ROOT_PASSWORD=password
- MINIO_REGION=us-east-1
networks:
lakehouse-net:
ports:
- 9001:9001
- 9000:9000
command: ["server", "/data", "--console-address", ":9001"]
#1 使用最新 MinIO image 的 data lake storage service
下面的 dremio Docker Compose service 定义了一个运行 Dremio 的 container。Dremio 是一个面向 lakehouse architectures 的开源高性能 SQL query engine。它连接到 lakehouse-net network,因此可以与 MinIO 这样的 storage 组件,或 Nessie 这样的 catalog management 组件交互。Container 暴露三个端口:9047 用于访问 Dremio UI 和 REST API,31010 用于 JDBC client connections,32010 用于 Dremio distributed engine 内部通信。有了这个 setup,Dremio 会成为 central interactive query platform,可以跨多个 data sources 进行 federation,并在 Iceberg tables 之上提供统一 SQL interface:
dremio: #1
image: dremio/dremio-oss:latest
container_name: dremio
networks:
lakehouse-net:
ports:
- 9047:9047
- 31010:31010
- 32010:32010
#1 Analytics engine service,也就是 Dremio,用于运行 analytical queries
下面的 spark Docker Compose service 会启动一个 Apache Spark container,用于 development 和 data lakehouse experimentation。它使用 alexmerced/spark35nb:latest image,其中包含 Spark 3.5 和 Jupyter Notebook,适合 interactive development。Container 名为 spark,连接到 lakehouse-net network,因此可以与 MinIO 和 Nessie 等 services 交互。它暴露三个端口:8080 用于 Spark master web UI,7077 用于 Spark master port,也就是 workers 和 drivers 使用的端口,8888 用于访问 Jupyter Notebook。Environment variables 会被设置为 AWS S3-compatible storage 的访问凭证,使 Spark 可以通过 S3 API 顺畅读写 MinIO。这个 setup 允许用户运行 Spark jobs,或通过 Jupyter 交互式探索 Iceberg tables,非常适合本地测试和 prototyping:
spark: #1
image: alexmerced/spark35nb:latest
container_name: spark
networks:
lakehouse-net:
ports:
- 8080:8080 # Master Web UI
- 7077:7077 # Master Port
- 8888:8888 # Jupyter Notebook
environment:
- AWS_ACCESS_KEY_ID=admin
- AWS_SECRET_ACCESS_KEY=password
- AWS_DEFAULT_REGION=us-east-1
- AWS_REGION=us-east-1
#1 使用 Apache Spark 的 ingestion engine service,我们会用它将数据写入 Iceberg tables
下面的 postgres service 会配置一个 PostgreSQL container,在 lakehouse architecture 示例中作为 operational data source。该 service 使用最新官方 PostgreSQL image,并设置一个名为 mydb 的 database,同时通过 environment variables 指定 user credentials。它运行在共享的 lakehouse-net network 上,使 Spark 或 ingestion tools 等其他 services 可以连接它以抽取数据。Container 内部暴露端口 5432,并映射到 host 的 5435,以避免与其他本地 PostgreSQL instances 冲突。简而言之,它模拟一个 transactional data system,结构化数据可以从这里定期或持续摄入 lakehouse,用于下游 analytics。
postgres: #1
image: postgres:latest
container_name: postgres
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
networks:
lakehouse-net:
ports:
- "5435:5432"
#1 Database service,代表 operational data source
superset Docker Compose service 会设置 Apache Superset,这是一个开源 BI platform,预配置为与 Dremio 协作,用于 lakehouse environment 中的 visual analytics。它使用 alexmerced/dremio-superset image,简化与 Dremio 作为 backend SQL engine 的集成,并包含所需 Dremio dependencies。Container 连接到 lakehouse-net network,使其可以与 Dremio 和 Iceberg catalog 等 services 无缝连接。该 service 暴露端口 8088,用于访问 Superset Web interface,以构建 dashboards 和探索 datasets。这个配置允许用户通过 Dremio 查询 Iceberg tables,并在 Superset 中可视化结果,从而完成端到端 analytics workflow:
superset: #1
image: alexmerced/dremio-superset
container_name: superset
networks:
lakehouse-net:
ports:
- 8088:8088
Networks: #2
lakehouse-net:
#1 使用 Apache Superset 的 BI tool service,用于基于数据创建 dashboards
#2 声明共享 network,使所有 services 都能通信
3.2.3 运行环境
现在已经有 Docker Compose 文件了,我们来启动 lakehouse environment。在 terminal 中进入保存 docker-compose.yml 的目录,并运行:
docker-compose up -d
这会:
- 启动 Nessie catalog,用于跟踪 Iceberg tables
- 启动 MinIO,作为 Iceberg metadata 和 data files 的 object store
- 部署 Dremio,一个基于 SQL 的 query engine
- 设置 Apache Spark,将数据移动到 Iceberg tables
- 创建 PostgreSQL database,用来模拟 operational system
- 初始化 Apache Superset,用来构建 BI dashboards
验证所有服务是否正在运行
Docker Compose 完成后,检查正在运行的 containers:
docker ps
你应该会看到类似这样的输出:
CONTAINER ID IMAGE STATUS PORTS
xxxxxxxxxx projectnessie/nessie:latest Up 5 minutes 19120/tcp
xxxxxxxxxx minio/minio:latest Up 5 minutes 9000-9001/tcp
xxxxxxxxxx dremio/dremio-oss:latest Up 5 minutes 9047/tcp, 31010-
32010/tcp
xxxxxxxxxx alexmerced/spark35notebook Up 5 minutes 7077/tcp, 8888/tcp
xxxxxxxxxx postgres:latest Up 5 minutes 5435->5432/tcp
xxxxxxxxxx alexmerced/dremio-superset Up 5 minutes 8088/tcp
如果所有 containers 都在运行,你的 Iceberg lakehouse 就准备好了。图 3.4 展示了 Docker 创建的 containers network。
图 3.4:本练习中的 container network,每个 container 都有自己的 IP address
我们来看这些 containers 如何协同工作。当我们想摄入新数据时,会运行一个 Python script 来提交 Spark job。在我们的场景中,Spark 从 Postgres 读取数据,并写入 Iceberg lakehouse。这个 job 将 Nessie 配置为 catalog,将 MinIO 配置为 storage layer,因此 Spark 知道任何 data 和 metadata files 都应该写入 MinIO 中的配置位置。随后,catalog 会被更新,以包含正在创建或更新的 table 的新 metadata。Dremio 使用相同的 Nessie 和 MinIO 配置,因此你可以浏览 datasets,并创建 logical views 来清洗和准备符合 use case 的数据。最后,你可以将 Apache Superset 等 BI tool 连接到 Dremio,并直接基于数据构建 dashboards。
3.2.4 访问服务
现在,我们验证每个 service 是否工作:
MinIO(object storage) ——打开 http://localhost:9001,用 admin/password 登录。
Dremio(query engine) ——打开 http://localhost:9047,创建 admin account。
Spark(data processing) ——打开 http://localhost:8888,访问 Jupyter Notebook。
PostgreSQL(operational DB) ——使用 PostgreSQL client 连接 localhost:5435,用户名 myuser,密码 mypassword。
Superset(BI dashboards) ——打开 http://localhost:8088,使用 admin/admin 登录。我们稍后会初始化 Superset。
现在 lakehouse environment 已经启动并运行,让我们开始使用它。
3.3 在 Spark 中创建 Iceberg Tables
现在 Apache Iceberg lakehouse environment 已经搭建好,可以开始处理真实世界数据了。在这个阶段,我们会模拟通常存在于 operational systems 中的数据,例如 transactional PostgreSQL database。本练习使用 retail sales transactions,其中包括 product details、sales amounts、customer demographics 和 store locations。这个 dataset 足够有深度,可以支持下游 analytics,让我们探索 customer behavior、campaign effectiveness 和 regional performance 的趋势。
第一步是从 PostgreSQL 中抽取数据。在真实系统中,你会连接到支持业务应用的生产数据库,例如 point-of-sale systems、CRMs 或 e-commerce platforms。这里,我们会使用本地 PostgreSQL instance 并填入 sample sales data 来模拟该环境。执行这一步时,请思考数据变化频率,以及你会使用 batch loads、micro-batches 还是 streaming ingestion。这些选择会影响你的架构决策。我们会在后续章节重新讨论 ingestion。
接下来,我们会使用 Apache Spark 将数据 transform 并 load 到 Apache Iceberg tables。Iceberg 支持 schema evolution、time travel 和 partitioning,这些特性在你管理数据湖仓中的大规模、analytics-ready datasets 时非常有帮助。
加载数据后,检查它是否正确存储在 object-storage backend,也就是 MinIO 中,以及 Nessie Iceberg catalog 是否记录了每次操作。Nessie 是跟踪和治理 Iceberg tables 的 catalog,因此其他 tools 可以发现并使用同一批 tables,而无需额外集成工作。这反映了现代 lakehouse architectures 中团队常见的工作方式:他们将数据从 transactional sources 摄入开放、受治理且可扩展的格式中,然后用于 analytics、reporting 或 AI/ML training pipelines。
3.3.1 填充 PostgreSQL Database
由于 docker-compose up 命令,我们的 PostgreSQL container 已经在运行。现在向其中加载一些 sample sales data,类似 operational system 可能生成的数据。然后,我们会将这些数据移动到更适合分析的系统中,也就是 Iceberg lakehouse。
NOTE
你可以在本书 GitHub repository 中找到本章所有 SQL statements:github.com/AlexMercedC…。
在 terminal 中运行以下命令,进入 Postgres shell:
docker exec -it postgres psql -U myuser mydb
进入后,使用以下 SQL 创建 fashion_sales_data table,并填入 mock data:
CREATE TABLE fashion_sales (
id SERIAL PRIMARY KEY,
product_name VARCHAR(255),
category VARCHAR(50),
sales_amount DECIMAL(10, 2),
sales_date DATE,
store_location VARCHAR(100),
customer_age_group VARCHAR(50),
campaign_name VARCHAR(100)
); #1
INSERT INTO fashion_sales (product_name, category, sales_amount, sales_date, store_location, customer_age_group, campaign_name)
VALUES
('Slim Fit Jeans', 'Denim', 89.99, '2024-03-01', 'New York', '18-24', 'Spring Launch'),
('Leather Jacket', 'Outerwear', 249.99, '2024-03-01', 'Los Angeles', '25-34', 'Spring Launch'),
('Graphic T-Shirt', 'Tops', 39.99, '2024-03-02', 'Chicago', '18-24', 'March Madness'),
('Summer Dress', 'Dresses', 129.99, '2024-03-03', 'New York', '35-44', 'March Madness'),
('Casual Sneakers', 'Footwear', 99.99, '2024-03-03', 'Los Angeles', '25-34', 'Spring Launch'); #2
#1 创建用于保存数据的 fashion_sales table
#2 向 fashion_sales table 添加几条数据记录
使用以下命令退出 Postgres shell:
\q
现在,我们的 PostgreSQL database 已经有了一个准备摄入 Iceberg tables 的 dataset。
3.3.2 启动 Apache Spark
现在我们会使用 Apache Spark 从 PostgreSQL 拉取数据,并将其写入 Iceberg tables。
首先,我们需要一个存储这些 tables 的地方。我们会使用 MinIO 作为 object storage backend。打开浏览器并访问 http://localhost:9001,进入 MinIO console。用用户名 admin 和密码 password 登录。登录后,创建一个名为 warehouse 的新 bucket。这就是 Spark 写入 Iceberg table data 的位置。
Spark 已经运行在我们 Docker 环境中,并包含 Jupyter Notebook,用于交互式数据处理。打开浏览器并访问 http://localhost:8888。创建一个新的 Python notebook 来运行 PySpark script,也就是基于 Python 的 Spark code。
你需要 MinIO container 的 IP address。在 terminal 中运行以下命令查找:
docker network ls
这会列出 networks。找到正确的 network name,它通常由 Docker Compose 文件所在目录名和该文件底部定义的 network name 组合而成。然后运行:
docker network inspect <network_name>
找到 MinIO container 的 IPv4 address,如图 3.5 所示,并记录下来供下一步使用。它是该 container 在 Docker network 上的唯一地址。通常,使用 container name 就足够了,例如 http://minio:9001。但有时 Docker domain name system(DNS)可能无法正确解析 container names,这在本练习的 MinIO 中容易发生。为了解决这个问题,可以使用 IP address 将 PySpark script 连接到 MinIO container。
图 3.5:检查 Docker network,以查找不同 containers 的 IP addresses
3.3.3 为 Iceberg 配置 Apache Spark
Python 是编写 Spark code 最流行的语言,而不是使用 Spark 原本的 Scala 和 Java APIs。在下面的 Python code 中,我们会加载 Spark libraries,并为 catalog 和 storage layer 的 network addresses,以及希望存储数据的 S3 location 设置变量。接下来,我们会使用这些 catalog 和 storage settings 配置 Spark session 并启动 session,这样就可以告诉 Spark 该 job 要做什么。
首先,在 Jupyter notebook 中粘贴并运行以下 Python code,在指定位置替换 MinIO IP address:
import pyspark
from pyspark.sql import SparkSession #1
## DEFINE VARIABLES #2
CATALOG_URI = "http://nessie:19120/api/v1"
WAREHOUSE = "s3://warehouse/"
STORAGE_URI = "http://<minio_ip>:9000"
#1 导入 pyspark library,用于向 Spark 发送指令
#2 定义 Iceberg catalog configuration 中会使用的变量
NOTE
完整代码请访问本书 GitHub repository:github.com/AlexMercedC…。
这段 Python snippet 会为使用 Apache Iceberg lakehouse setup 的 PySpark data pipeline 初始化配置变量:
import pyspark 和 from pyspark.sql import SparkSession 会引入创建和管理 Spark sessions 所需的 PySpark libraries。Spark sessions 是 Spark DataFrame operations 的入口。
CATALOG_URI 定义 Nessie catalog service 的 REST endpoint,Nessie 负责跟踪 Iceberg tables 的 metadata 和 versions。该 URI 指向本地 Docker network 上的 Nessie container。
WAREHOUSE 指定 object storage 中 Iceberg tables 物理存放的根位置。它使用 S3-compatible path syntax,并指向名为 warehouse 的 bucket。假设该 bucket 托管在本地 MinIO service 上。
STORAGE_URI 定义访问 MinIO object store 的 base address。这让 Spark 可以通过 S3 API 连接 MinIO,以读写 table data。这里需要使用前面获取到的 MinIO service IP address。
这些变量共同告诉 Spark 使用 Nessie 做 table versioning,并使用 MinIO 作为 Iceberg-compatible storage layer。
下一段代码配置 Spark,将数据摄入 Apache Iceberg tables,并使用 Project Nessie 作为 versioned catalog、MinIO 作为 S3-compatible storage backend:
conf = (
pyspark.SparkConf()
.setAppName('Iceberg Ingestion')
.set('spark.jars.packages',
'org.postgresql:postgresql:42.7.3,'
'org.apache.iceberg:iceberg-spark-runtime-3.5_2.12:1.5.0,'
'org.projectnessie.nessie-integrations:nessie-spark-extensions-3.5_2.12:0.77.1,'
'software.amazon.awssdk:bundle:2.24.8,'
'software.amazon.awssdk:url-connection-client:2.24.8')
.set('spark.sql.extensions',
'org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions,'
'org.projectnessie.spark.extensions.NessieSparkSessionExtensions')
.set('spark.sql.catalog.nessie', 'org.apache.iceberg.spark.SparkCatalog')
.set('spark.sql.catalog.nessie.uri', CATALOG_URI)
.set('spark.sql.catalog.nessie.ref', 'main')
.set('spark.sql.catalog.nessie.authentication.type', 'NONE')
.set('spark.sql.catalog.nessie.catalog-impl', 'org.apache.iceberg.nessie.NessieCatalog')
.set('spark.sql.catalog.nessie.s3.endpoint', STORAGE_URI)
.set('spark.sql.catalog.nessie.warehouse', WAREHOUSE)
.set('spark.sql.catalog.nessie.io-impl', 'org.apache.iceberg.aws.s3.S3FileIO')
) #1
#1 创建 Spark configuration,设置 Apache Iceberg catalog
这段代码创建一个 SparkConf object,并将 application name 设置为 “Iceberg Ingestion”。spark.jars.packages property 会加载一组必需 libraries:PostgreSQL JDBC driver,用于从 PostgreSQL source 读取数据;Iceberg 的 Spark runtime,用于 table operations;Nessie 的 Spark extensions,用于 catalog integration;以及 AWS SDK components,用于与 MinIO 等 S3-compatible storage 交互。
接下来,代码会注册 Iceberg 和 Nessie 所需的 Spark session extensions。然后定义一个名为 nessie 的 logical catalog,并告诉 Spark 使用 Iceberg SparkCatalog class 以及 Nessie-specific settings。这些设置包括 Nessie server 的 REST URI、reference branch,也就是 main,以及 authentication mode,为简化起见这里禁用。Catalog 被配置为使用 Nessie-specific implementation for Iceberg,并通过 AWS S3-compatible S3FileIO interface 访问 storage,指向前面定义的 MinIO endpoint 和 warehouse directory。
这些设置共同使 Spark job 能够将数据写入由 Nessie 管理的 Iceberg tables,并存储在 MinIO 中,同时完整支持 time travel、branching 和 atomic commits 等特性。
下一段代码使用前面定义的 custom configuration conf 启动 Spark session,其中包含与 Apache Iceberg、Project Nessie 和 MinIO 交互所需的所有设置:
spark = SparkSession.builder.config(conf=conf).getOrCreate()
print("Spark Running")
SparkSession.builder.config(conf=conf).getOrCreate() 调用会在没有现有 session 时创建新的 Spark session,如果已有 session,则返回已有 session。这个 session 是与 Spark APIs 交互的主要入口,支持从 Iceberg tables 读取和写入等操作。print("Spark Running") 用于确认 Spark session 已成功启动,表明环境已准备好执行数据处理任务。
3.3.4 将数据从 PostgreSQL 加载到 Iceberg
现在,连接 PostgreSQL,读取 sales_data table,并将其写入 data lake 中的 Iceberg table。这里,MinIO 会充当 data lake 的 storage layer,类似生产环境中的 distributed filesystems,例如 HDFS,或 cloud object storage。
Jupyter Notebook 已经运行在你之前设置的 container 环境中。打开浏览器,访问 http://localhost:8888 进入 Jupyter interface。然后在 project directory 中创建一个新的 notebook。
将以下代码粘贴到 notebook 的新 cell 中,并运行:
# Define the JDBC connection properties
jdbc_url = "jdbc:postgresql://postgres:5432/mydb" #1
properties = {
"user": "myuser",
"password": "mypassword",
"driver": "org.postgresql.Driver"
} #2
# Read the sales_data table from Postgres into a Spark DataFrame
sales_df = spark.read.jdbc(url=jdbc_url, table="fashion_sales", properties=properties) #3
# Show the first few rows of the dataset
sales_df.show() #4
#1 将连接 Postgres database 的 connection string 保存到变量中
#2 数据库连接属性
#3 使用该连接将 dataset 拉取到 DataFrame 中
#4 打印 dataset,确认已正确加载
你应该会看到 PostgreSQL sales data 的输出,如图 3.6 所示。
图 3.6:将 source system 中的数据加载到 dataframe 中
现在,将这些数据写入由 Nessie 跟踪的 Apache Iceberg table。创建 table 前,需要先定义 namespace。Namespace 是 Iceberg catalogs 用来组织 tables 的逻辑分组。它类似传统数据仓库中的 database 或 schema,也类似用于分组相关 datasets 的 folder,但它纯粹存在于 catalog level,而不是 storage 中的物理目录。
下面命令会先在 Nessie 中创建一个 namespace,用于存放我们的 table。随后,它们会从 PostgreSQL 将数据加载到 Spark DataFrame,并将其写出为 Iceberg table。这个过程会把底层 data files 写入 MinIO,并将 table metadata 注册到 Nessie。因此,Nessie 会知道该 table 的存在、schema 和 metadata 位置,使其他 tools 能够一致地发现和查询它:
#Create a namespace
spark.sql("CREATE NAMESPACE IF NOT EXISTS nessie.sales;") #1
# Write the DataFrame to an Iceberg table in the Nessie catalog
sales_df.writeTo("nessie.sales.fashion_sales").createOrReplace() #2
# Verify that the data was written to Iceberg by reading the table
spark.read.table("nessie.sales.fashion_sales").show() #3
#1 创建名为 “sales” 的 namespace,用于组织 datasets
#2 将 Postgres 数据写入该 namespace 下名为 “fashion_sales” 的新 table
#3 打印新的 Iceberg table,确保它已正确创建
如果成功,你应该能看到同样的 sales data 已存储在 Iceberg 中,如图 3.7 所示。
图 3.7:向 Iceberg table 写入数据并从中读取数据
3.3.5 验证 MinIO 中的数据存储
我们使用 MinIO 作为 object storage,因此需要确认数据已经正确存储:
- 打开
http://localhost:9001。 - 用
admin/password登录。 - 点击
warehousebucket。 - 进入
sales/sales_data_UUID/。
你应该会看到包含数据的 Parquet files,以及 JSON 和 Avro 格式的 metadata files,如图 3.8 所示。Parquet files 表示数据本身;JSON files 是 Iceberg metadata structure 顶层的 metadata files,这一点在第 2 章已经讲过。Avro files 是该结构中的 manifest 和 manifest list files。Spark 已将数据摄入 Iceberg tables,并以 Parquet 形式存储到 MinIO object store 中。
图 3.8:在 MinIO data lake 中查看 Iceberg table data
3.4 使用 Dremio 读取 Iceberg Tables
现在我们已经使用 Spark 将数据摄入 Apache Iceberg,接下来用 Dremio 查询 Iceberg tables。Dremio 是一个 lakehouse query engine,能够在数据湖上提供 analytics。本书中,我们还会考察其他与 Apache Iceberg 协作的 engines,包括 Trino 和 DuckDB,同时探索 Iceberg 生态,并为每个 lakehouse layer 选择工具。
本节会将 Dremio 连接到 Nessie catalog,这样你就可以访问并探索 Apache Iceberg tables。然后,我们会使用 Dremio interface 中的标准 SQL 查询前面摄入的 sales dataset。你会看到,Iceberg tables 感觉很像传统数据仓库中的 tables,但你无需为了构建 warehouse model 而移动或复制数据。
运行 queries 时,你会看到 Apache Iceberg 如何通过 metadata pruning 等特性提升性能。Iceberg 存储详细 table metadata,包括 partition information 和 file-level statistics,这使 Dremio 可以在 query execution 时跳过不必要数据。即使 dataset 扩大,queries 也会运行得更快。本节结束时,你会看到 Iceberg 如何让你直接在 data lake 上运行快速 analytics,获得类似 warehouse 的性能,同时避免数据复制的复杂性和成本。
3.4.1 启动 Dremio
Dremio 已经作为 Docker Compose setup 的一部分运行。要访问它,请执行以下步骤:
- 打开浏览器并访问
http://localhost:9047。 - 使用之前创建的 admin account 登录。
这会打开 Dremio UI。在 Dremio 中,你可以连接不同 data sources,在它们的 datasets 上运行 SQL,甚至跨 sources 进行查询,也就是 federated queries。Dremio 也可以连接 Nessie 这样的 lakehouse catalogs,因此它能快速找到并处理 catalog 跟踪的 Iceberg tables。
3.4.2 将 Dremio 连接到 Nessie Catalog
要查询 Iceberg tables,Dremio 必须连接到 Iceberg catalog,例如本练习中的 Nessie。为了简化练习,我们运行的 Nessie container 不配置 authorization。在生产中,你应选择 catalog,并启用所需 governance features,以控制用户访问。我们会在后续章节覆盖 Iceberg catalogs 和可用选项。
按照以下步骤配置 Nessie catalog:
图 3.9:配置 Nessie 的 General settings
图 3.10:配置 Nessie 的 Storage settings
-
点击 Add Source。
-
从可用 source types 中选择 Nessie。
-
输入以下 General settings,见图 3.9:
- Source name:
Nessie - Nessie endpoint URL:
http://nessie:19120/api/v2 - Authentication type:
None
- Source name:
-
输入以下 Storage settings,见图 3.10:
- AWS root path:
warehouse - AWS access key:
admin - AWS secret key:
password - 取消勾选 Encrypt Connection,因为我们没有使用 SSL
- AWS root path:
-
输入以下 connection properties,见图 3.11。这些 properties 让 Dremio 可以连接到 MinIO 等其他 S3-compliant storage layers,并找到文件:
fs.s3a.path.style.access = truefs.s3a.endpoint = minio:9000dremio.s3.compat = true
-
点击 Save 添加 Nessie source。
添加 source 后,Dremio 会自动检测注册在 Nessie 中的 Iceberg tables,如图 3.12 所示。
图 3.11:配置 Nessie 的 connection properties
图 3.12:在 Dremio 平台中看到我们的 Iceberg tables
3.4.3 在 Dremio 中查询 Iceberg Tables
现在 Dremio 已连接到 Nessie,我们来查询 Iceberg sales dataset。在 Dremio SQL Editor 中运行以下命令:
SELECT * FROM nessie.sales.fashion_sales;
你应该会看到我们最初从 PostgreSQL 摄入的同样 sales data,现在它已经作为 Iceberg table 存储在 MinIO 中。
Iceberg 的一个关键优势是 metadata-driven pruning,它通过跳过不必要的数据扫描来加速 queries。Table metadata 会提供 per-file statistics,使 Dremio 这样的 engines 可以跳过不包含相关数据的文件,而不是扫描每个文件中的全部数据。例如,如果你在扫描 sales_amount 为 35 的 records,engine 可以跳过某个 metadata 显示该列范围为 38 到 40 的文件,因为它不可能包含匹配 records。
检查 Table Metadata
要查看 Iceberg metadata tables,运行以下语句。这是 Dremio-specific syntax,其他 engines 的语法请参考附录 A:
SELECT * FROM TABLE( table_snapshot( 'nessie.sales.fashion_sales' ) )
这个输出会列出 snapshots,也就是 table 的 historical versions,如图 3.13 所示。你可以在 time-travel queries 中使用这些 snapshot IDs,查询 table 的早期版本。
图 3.13:使用 Dremio 查询 table 的 snapshot history
使用 Iceberg Snapshots 进行 Time Travel
要使用 snapshot ID 做 time-travel,可以使用如下语句:
SELECT * FROM nessie.sales.sales_data AT SNAPSHOT <snapshot_id>;
以下语句会基于特定 timestamp 做 time-travel:
SELECT * FROM nessie.sales.sales_data AT TIMESTAMP <timestamp>;
你可以使用 time-travel queries 检查 table 的历史版本,从而帮助隔离错误。Time travel 对 auditing 和 compliance 也很有用,因为它可以让历史数据可用于审查。
3.5 从 Iceberg Tables 创建 BI Dashboard
现在,我们已经将数据摄入 Apache Iceberg,并使用 Dremio 查询它,接下来更进一步,构建 BI dashboard 来可视化洞察。本节中,我们会将 Apache Superset 连接到 Dremio,从 Iceberg table 构建 dataset,并创建交互式 charts 和 dashboards。完成后,你会拥有一个由 Dremio 和 Apache Iceberg 驱动的实时 BI dashboard,展示 lakehouse analytics 能做什么。
3.5.1 启动 Apache Superset
Apache Superset 是一个开源 BI tool,允许用户从基于 SQL 的 data sources 创建 interactive dashboards 和 visualizations。如果 Superset 尚未运行,使用以下命令启动它:
docker-compose up -d superset
然后使用以下命令初始化 Superset,这会设置默认 users、metadata tables 和 roles:
docker exec -it superset superset init
初始化完成后,打开浏览器并访问 http://localhost:8088。使用以下 credentials 登录:
- Username:
admin - Password:
admin
这会进入 Superset home page,你可以在其中连接 Dremio。
3.5.2 将 Superset 连接到 Dremio
我们需要将 Dremio 配置为 Superset 中的数据源。按照以下步骤:
- 点击 Settings > Database Connections。
- 点击 + Add Database。
- 选择 Other 作为 database type。
- 对于 Dremio URI,输入以下内容,将
USERNAME和PASSWORD替换为你的 Dremio credentials:
dremio+flight://USERNAME:PASSWORD@dremio:32010/?UseEncryption=false
5. 点击 Test Connection,确保它正常工作。 6. 点击 Save。
Dremio 会连接到 Superset,如图 3.14 所示。这个 URI 使用 Dremio 的 Arrow Flight protocol 访问数据,通信协议会在第 8 章进一步介绍。本地 setup 中禁用了 encryption,但在生产中应启用 UseEncryption=true 以保证连接安全。
图 3.14:从 Superset 连接到 Dremio
3.5.3 从 Iceberg Tables 创建 Dataset
现在已经连接成功,接下来从 Iceberg sales_data table 创建 dataset,如图 3.15 所示:
图 3.15:在 Superset 中从 Iceberg table 创建新 dataset
- 点击右上角的 + 按钮 → Create Dataset。
- 选择 Dremio 作为 database。
- 导航到
nessie.sales.fashion_sales。 - 点击 Create Dataset。
这样,我们的 Iceberg table 就可以用于 charts 和 dashboards。
3.5.4 构建 Charts 和 Dashboards
现在用一些交互式 charts 来可视化 Iceberg data。Apache Superset 可以轻松从 sources 添加 datasets,基于它们创建 charts,并将所有内容组合成一个 dashboard,分享给需要查看数据的人:
-
点击 + 按钮 → Create Chart。
-
选择
fashion_salesdataset。 -
选择 Line Chart。
-
设置如下:
- X-Axis:
sales_date - Metric:
SUM(sales_amount)
- X-Axis:
-
点击 Run Query 预览 chart。
-
点击 Save,命名为
Sales by Day,并添加到新 dashboard,见图 3.16。
图 3.16:基于 Iceberg data 创建的 line chart
再创建一个 chart:
-
点击 + Create Chart。
-
选择
fashion_sales作为 dataset。 -
选择 Pie Chart。
-
设置如下:
- Dimension:
category - Metric:
SUM(sales_amount)
- Dimension:
-
点击 Run Query 生成 chart。
-
点击 Save,命名为
Sales by Category,并添加到 dashboard,见图 3.17。
图 3.17:Superset 中基于 Iceberg data 创建的 pie graph
现在我们已经创建了几个像图 3.17 中看到的 charts,接下来把它们组合到一个 dashboard 中:
-
点击 Dashboards → + Create Dashboard。
-
命名为
Sales Analytics。 -
点击 + Add Chart,然后选择:
Sales by DaySales by Category
-
调整并排列 charts。
-
点击 Save & Publish。
现在,你已经在 data lake 上拥有了一个功能完整的 BI dashboard,类似图 3.18!你可能已经注意到,使用 Apache Superset 这样的工具,与使用数据库或数据仓库中的任何表并没有区别,而这正是重点所在。这些 tables 以开放格式融入你已有 workflows,并且可以在整个企业中使用,而不用改变你的数据消费方式。由于它是 Iceberg,你可以在任意步骤替换不同工具,这体现了 Apache Iceberg 模块化生态的价值。
图 3.18:基于 Superset 中 Iceberg data 构建的 dashboard
完成后,关闭环境并运行以下命令:
docker compose down -v
该命令中的 -v flag 会指示 Docker 清理为每个 container 创建的 storage volumes,从而释放运行这些 containers 时可能占用的存储空间。
下一章中,我们会退一步评估你的数据基础设施。我们也会开始规划如何在真实世界中采用 Iceberg——从定义架构需求,到选择合适组件,以实现成功落地。
小结
你可以使用 Docker Compose 搭建本地 Apache Iceberg lakehouse environment,并部署 MinIO、Apache Spark、Nessie、Dremio、Superset 和 PostgreSQL 等 services。
MinIO 作为 Iceberg table data files 和 metadata files 的 object storage layer,在本地模拟 cloud-based storage。
Project Nessie 充当 catalog,跟踪 Iceberg tables,使 Apache Spark 和 Dremio 能够发现它们。
Apache Spark 会从 PostgreSQL operational database 中抽取 sales data,对其进行 transform,并将其加载到 Nessie 跟踪的 Iceberg tables 中。
Dremio 会连接到 Nessie catalog,直接从 object storage 查询 Iceberg tables,为 analytics workloads 提供支持。
Apache Superset 可以连接到 Dremio,创建交互式 BI dashboards,用来可视化 Iceberg lakehouse environment 中的洞察。