DuckDB 原生支持 Delta Lake ,基于 DuckDB的湖仓来了
DuckDB 原生支持 Delta Lake
过去几个月,DuckDB Labs 与 Databricks 合作,利用新的 delta-kernel-rs 项目在 DuckDB 中添加了对 Delta Lake 的原生支持。在本文中,我们将简要概述 Delta Lake、Delta Kernel,当然,还会介绍新的 DuckDB Delta 扩展。
如果你已经熟悉 Delta Lake 和 Delta Kernel,或者只是想了解如何使用它们,可以跳过[关于如何在 DuckDB 中使用 Delta 的部分](#如何在 DuckDB 中使用 Delta),直接进入关键部分。
简介
Delta Lake[1] 是一种开源存储框架,能够构建湖仓一体架构。因此,要理解 Delta Lake,我们需要了解湖仓一体架构是什么。湖仓一体是一种数据管理架构,它致力于将廉价的存储成本效益与智能管理层相结合。简而言之,湖仓一体架构是各种格式文件的集合,并在其之上添加了一些额外的元数据层。这些元数据层旨在为原始文件集合提供额外的功能,例如 ACID 事务、时间旅行、分区和模式演变、统计信息等等。
湖仓一体架构允许运行各种数据密集型应用程序,例如数据分析和机器学习应用程序,直接在大量结构化、半结构化和非结构化数据上进行操作,而无需中间的数据仓库步骤。如果你准备好深入研究,我们建议阅读 CIDR 2021 论文《湖仓一体:下一代开放平台,统一数据仓库和高级分析[2]》 (Michael Armbrust 等人著)。但是,如果你(可以理解地)不愿意深入研究密集的科学文献,这张图片可以很好地概括它:
Delta Lake
现在让我们有请今天的主角,Delta Lake。Delta Lake(或简称为“Delta”)是目前领先的开源湖仓一体格式之一,与Apache Iceberg™[4] 和 Apache HUDI™[5] 齐名。最简单地理解 Delta 表的方法是将其视为“带有元数据的 Parquet 文件集合”。带着这个略微简化的理解,我们将创建一个 Delta 表并检查创建的文件,以加深我们的理解。为此,我们将使用 Python 以及以下软件包:duckdb[6]、pandas[7] 和 deltalake[8]:
pip install duckdb pandas deltalake==0.17.4然后,我们使用 DuckDB 创建一些带有测试数据的 DataFrames,并使用 deltalake 包将其写入 Delta 表:
import duckdb
from deltalake importDeltaTable, write_deltalake
con = duckdb.connect()
df1 = con.query("SELECT i AS id, i % 2 AS part, 'value-' || i AS value FROM range(0, 5) tbl(i)").df()
df2 = con.query("SELECT i AS id, i % 2 AS part, 'value-' || i AS value FROM range(5, 10) tbl(i)").df()
write_deltalake(f"./my_delta_table", df1, partition_by=["part"])
write_deltalake(f"./my_delta_table", df2, partition_by=["part"], mode='append')运行完这段脚本后,我们就创建了一个包含 10 行的简单 Delta 表,分为两个分区,我们在两个独立的步骤中添加了这些分区。为了再次确认一切按计划进行,让我们使用 DuckDB 查询该表:
SELECT*
FROM delta_scan('./my_delta_table')
ORDERBY id;| id | part | value |
| 0 | 0 | value-0 |
| 1 | 1 | value-1 |
| 2 | 0 | value-2 |
| 3 | 1 | value-3 |
| 4 | 0 | value-4 |
| 5 | 1 | value-5 |
| 6 | 0 | value-6 |
| 7 | 1 | value-7 |
| 8 | 0 | value-8 |
| 9 | 1 | value-9 |
看起来不错!所有预期数据都在这里。现在让我们看看实际创建了哪些文件,使用 tree 命令:
tree ./my_delta_table`my_delta_table
├── _delta_log
│├──00000000000000000000.json
│└──00000000000000000001.json
├── part=0
│├──0-f45132f6-2231-4dbd-aabb-1af29bf8724a-0.parquet
│└──1-76c82535-d1e7-4c2f-b700-669019d94a0a-0.parquet
└── part=1
├──0-f45132f6-2231-4dbd-aabb-1af29bf8724a-0.parquet
└──1-76c82535-d1e7-4c2f-b700-669019d94a0a-0.parquettree 输出显示了两种不同类型的文件。虽然 Delta 表可以包含各种其他类型的文件,但这些文件构成了任何 Delta 表的基础。
💡, macOS 下需要额外安装tree命令,
brew install tree首先是 数据文件,以 Parquet 格式存储。数据文件包含存储在表中的所有数据。这与使用 DuckDB 写入Parquet 分区文件[9]时存储数据的方式非常相似。
其次是 Delta 文件,以 JSON 格式存储。Delta 文件包含对表所做的更改的日志。通过回放此日志,读取器可以构建表的有效视图。为了说明这一点,让我们简单地查看第一个 Delta 日志文件之一:
cat my_delta_table/_delta_log/00000000000000000000.json...
{"add":{
"path":"part=1/0-f45132f6-2231-4dbd-aabb-1af29bf8724a-0.parquet",
"partitionValues":{"part":"1"}
},
...
}
{"add":{
"path":"part=0/0-f45132f6-2231-4dbd-aabb-1af29bf8724a-0.parquet",
"partitionValues":{"part":"0"},
},
...
}
...正如我们所看到的,此日志文件包含两个 add 对象,描述了一些数据分别被添加到 1 和 0 分区中。还要注意,分区值本身是显式地存储在这些 Delta 文件中的,因此即使文件结构看起来与Hive 风格[10]的分区方案非常相似,文件夹名称实际上并没有被 Delta 内部使用。相反,分区值是从元数据中读取的。
现在,通过这个简单的例子,我们展示了 Delta 的基本工作原理。为了更深入地了解内部机制,我们建议参考官方 Delta 规范[11],它在协议规范标准中非常易于阅读。官方规范详细描述了 Delta 如何处理每一个细节,从这里描述的基础知识到更复杂的事项,例如检查点、删除、模式演变等等。
实现
Delta Kernel
支持像 Delta 这样相对复杂的协议,需要大量的开发和维护工作。因此,当希望将对这种协议的支持添加到引擎时,合乎逻辑的选择是寻找一个现成的库来处理这些工作。对于 Delta Lake,例如,我们可以选择 delta-rs 库[12]。
但是,当要实现本地的 DuckDB Delta 扩展时,就会出现问题:如果我们要使用 delta-rs 库来实现 DuckDB 扩展,那么与 Delta 表的所有交互都将通过 delta-rs 库进行。但是请记住,Delta 表实际上只是“一堆带有元数据的 Parquet 文件”。因此,这意味着当 DuckDB 想要读取 Delta 表时,数据文件将通过 delta-rs Parquet 读取器进行读取,使用 delta-rs 文件系统。但这样做很麻烦:DuckDB 自带了一个出色的 Parquet 读取器[13]。此外,DuckDB 已经支持多种[14] 文件系统[15],并带有自己的凭据管理系统[16]。通过使用像 delta-rs 这样的库来实现 DuckDB 的 Delta 扩展,实际上会遇到各种问题:
• 扩展二进制文件大小增加
•
delta_scan和read_parquet之间用户体验不一致• 维护工作量增加
现在,为了解决这些问题,我们更希望有一个库只实现 Delta 协议,而让 DuckDB 处理它已经知道如何处理的所有事情。
幸运的是,这样的库确实存在,它被称为 Delta Kernel 项目[17]。Delta Kernel 是一个“用于构建 Delta 连接器的库集,可以读取和写入 Delta 表,而无需理解 Delta 协议的细节”。这是通过公开两个相对简单的 API 集来实现的,引擎将实现这些 API,如下面的图片所示:
有关 delta-kernel-rs 项目的更多详细信息,我们建议参考这篇精彩的博客文章[18],它深入探讨了内部机制和设计原理。
虽然 delta-kernel-rs 库目前还处于实验阶段,但它最近发布了 v0.1.0 版本[19],并且已经提供了很多功能。此外,由于 delta-kernel-rs 公开了 C/C++ 外部函数接口,因此将它集成到 DuckDB 扩展中非常简单。
DuckDB Delta 扩展
现在我们准备深入了解 DuckDB Delta 扩展的内部细节。首先,Delta 扩展目前实现了一个表函数:delta_scan。这是一个简单但强大的函数,可以扫描 Delta 表。
要了解此函数是如何实现的,我们首先需要确定涉及的四个主要组件:
| 组件 | 描述 |
| Delta Kernel | delta-kernel-rs 库 |
| Delta 扩展 | DuckDB 的可加载Delta 扩展[20] |
| Parquet 扩展 | DuckDB 的可加载Parquet 扩展[21] |
| DuckDB | 超酷的鸭子主题分析型数据库 |
此外,我们需要理解涉及的四个主要 API:
| API | 描述 |
FileSystem | DuckDB 的 I/O API(用于本地文件、Azure[22]、S3[23] 等) |
TableFunction | DuckDB 的表函数 API(例如,read_parquet[24]、read_csv[25]) |
MultiFileReader | DuckDB 的多文件扫描处理 API |
| Delta Kernel C/C++ FFI | Delta Kernel FFI[26] 用于 Delta Lake |
现在我们已经有了所有的链接,让我们把它们连接起来。当用户运行包含 delta_scan 表函数的查询时,DuckDB 将使用 TableFunction API 调用 Delta 扩展中的 delta_scan 函数。但是,delta_scan 表函数实际上只是 read_parquet 函数的完全复制。
为了将 read_parquet 更改为 delta_scan,它将使用一个自定义的 DeltaMultiFileReader 替换 parquet_scan 的常规 MultiFileReader(它只是扫描文件列表或通配符),该 DeltaMultiFileReader 将根据 Delta 表元数据生成一个文件列表。最后,每当 Parquet 扩展需要任何 I/O 时,它将使用 FileSystem API 调用 DuckDB 来处理 I/O。下面这个图表展示了整个交互过程。
在这个图表中,我们可以看到参与处理包含 delta_scan 表函数的查询的四个组件。箭头表示组件之间通过四个 API 进行的通信。现在,当读取 Delta 表时,我们可以看到元数据是在右侧通过 Delta Kernel 处理的。在左侧,我们可以看到 Parquet 数据是如何通过 Parquet 扩展流动的。
虽然这里显然还有一些重要的细节缺失,例如删除向量和列映射的处理,但我们已经涵盖了 DuckDB Delta 扩展的基本概念。此外,我们还展示了当前实现如何实现非常自然的逻辑分离,通过连接明确定义的 API 来抽象掉组件内部细节。通过这样做,实现获得了以下关键属性:
1. Delta 协议的细节在很大程度上对任何 DuckDB 组件都是不透明的。 与 Delta 协议内部机制的唯一接触点是 Delta Kernel 公开的狭窄 FFI。这完全由 Delta 扩展处理,它的唯一工作是将它翻译成本地的 DuckDB API。
2. 完全复用 DuckDB 现有的 Parquet 扫描逻辑,无需扩展之间任何代码复用或编译时依赖。因为 Delta 和 Parquet 扩展之间的所有交互都是通过正在运行的 DuckDB 实例在 DuckDB API 上完成的,所以扩展只通过
TableFunction和MultiFileReaderAPI 进行接口。这也意味着对 Parquet 扩展所做的任何未来优化都将自动在 Delta 扩展中可用。3. 所有 I/O 都将通过 DuckDB 的
FileSystemAPI 进行。 这意味着所有可用于 DuckDB 的文件系统(Azure[27]、S3[28] 等)都可以用于扫描。这意味着任何可以读取和列出文件的 DuckDB 文件系统都可以用于 Delta。这在 DuckDB-WASM 中也很有用,因为其中使用了自定义文件系统实现。注意,这里需要补充两点。首先,目前 DuckDB Delta 扩展仍然允许 Delta Kernel 通过内部文件系统库处理一小部分 I/O,这是因为 FFI 还没有公开FileSystemAPI,但这种情况很快就会改变。其次,虽然 Delta 扩展的架构设计考虑到了 DuckDB-WASM,但 WASM 版本的扩展目前还不可用。
如何在 DuckDB 中使用 Delta
在 DuckDB 中使用 Delta 扩展非常简单,因为它作为 DuckDB 的核心扩展之一发布,并且可以自动加载[29]。这意味着你只需启动 DuckDB(使用 v0.10.3 或更高版本)并运行:
SELECT*FROM delta_scan('./my_delta_table');DuckDB 将自动安装并加载 Delta 扩展。然后它将查询本地 Delta 表 ./my_delta_table。
如果你的 Delta 表位于 S3 上,你可能需要设置一些 S3 凭据。如果这些凭据已经存在于默认位置[30](例如环境变量或 ~/.aws/credentials 文件中)?只需运行:
CREATE SECRET delta_s1 (
TYPE S3,
PROVIDER CREDENTIAL_CHAIN
)
SELECT*FROM delta_scan('s3://some-bucket/path/to/a/delta/table');你更喜欢记住你的 AWS 令牌,并希望将它们直接输入?那就使用:
CREATE SECRET delta_s2 (
TYPE S3,
KEY_ID,'AKIAIOSFODNN7EXAMPLE',
SECRET,'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
REGION 'eu-west-1'
)
SELECT*FROM delta_scan('s3://some-bucket/path/to/a/delta/table');你有多个 Delta 表,使用不同的凭据?没问题,你可以使用作用域密码:
CREATE SECRET delta_s3 (
TYPE S3,
KEY_ID,'AKIAIOSFODNN7EXAMPLE1',
SECRET,'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY1',
REGION 'eu-west-1',
SCOPE's3://some-bucket1'
)
CREATE SECRET delta_s4 (
TYPE S3,
KEY_ID,'AKIAIOSFODNN7EXAMPLE2',
SECRET,'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY2',
REGION 'us-west-1',
SCOPE's3://some-bucket2'
)
SELECT*FROM delta_scan('s3://some-bucket1/table1');
SELECT*FROM delta_scan('s3://some-bucket2/table2');最后,你的表是公开的,但不在默认的 AWS 区域内?请确保使用空的 S3 秘密设置区域:
CREATE SECRET delta_s5 (
TYPE S3,
REGION 'eu-west-2'
)
SELECT*FROM delta_scan('s3://some-public-bucket/table1');Delta 扩展的当前状态
目前,Delta 扩展仍被认为是实验性的。这部分是因为 Delta 扩展本身还很新,但也是因为其依赖的 delta-kernel-rs 项目仍处于实验阶段。尽管如此,当前版本的 Delta 扩展已经支持 Delta 扫描的核心功能,例如:
• 所有数据类型
• 过滤和投影下推
• 基于过滤下推的文件跳过
• 删除向量
• 分区表
• 完全并行扫描
从架构的角度来看,Delta 扩展可在 linux_amd64、linux_amd64_gcc4、osx_amd64 和 osx_arm64 平台上使用。对其余核心平台的支持即将到来。此外,我们将继续与 Databricks 合作,进一步改进 Delta 扩展,添加更多功能,例如:
• 写入支持
• 列映射
• 时间旅行
• 变体、行 ID
• WASM 支持
有关新添加功能的详细信息,请关注 Delta 扩展的文档[31] 和存储库[32]。
结论
在本文中,我们介绍了 DuckDB 的新 Delta 扩展,它允许你从 DuckDB 环境中轻松地与 Delta Lake 进行交互。为此,我们通过创建一个 Delta 表并使用 DuckDB 对其进行分析,展示了 Delta Lake 格式的具体内容。
我们想强调,通过使用 delta-kernel-rs[33] 库来实现 Delta 扩展,DuckDB 和 Delta 扩展都保持了相对简单,并且在很大程度上与 Delta 协议的内部机制无关。
我们希望你能试用Delta 扩展[34],并期待社区的任何反馈!此外,如果你参加了2024 Databricks 数据 + AI 峰会[35],请务必关注 DuckDB 联合创始人Hannes Mühleisen[36] 在周四的主题演讲和深入的分组讨论[37](同样在周四举行),以了解有关 DuckDB-Delta 集成的更多详细信息。
原文:https://duckdb.org/2024/06/10/delta.html
引用链接
[1] Delta Lake: https://delta.io/[2] 湖仓一体:下一代开放平台,统一数据仓库和高级分析: https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf[3] Armburst 等人,CIDR 2021: https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf#page=2[4] Apache Iceberg™: https://iceberg.apache.org/[5] Apache HUDI™: https://hudi.apache.org/[6] duckdb: https://pypi.org/project/duckdb/[7] pandas: https://pypi.org/project/pandas/[8] deltalake: https://pypi.org/project/deltalake/[9] Parquet 分区文件: https://duckdb.org/docs/data/partitioning/partitioned_writes[10] Hive 风格: https://duckdb.org/docs/data/partitioning/hive_partitioning[11] 官方 Delta 规范: https://github.com/delta-io/delta/blob/master/PROTOCOL.md[12] delta-rs 库: https://github.com/delta-io/delta-rs[13] 出色的 Parquet 读取器: https://duckdb.org/docs/data/parquet/overview[14] 多种: https://duckdb.org/docs/extensions/httpfs/hugging_face[15] 文件系统: https://duckdb.org/docs/extensions/azure[16] 凭据管理系统: https://duckdb.org/docs/configuration/secrets_manager[17] Delta Kernel 项目: https://delta.io/blog/delta-kernel[18] 精彩的博客文章: https://delta.io/blog/delta-kernel/[19] 最近发布了 v0.1.0 版本: https://github.com/delta-incubator/delta-kernel-rs/releases/tag/v0.1.0[20] Delta 扩展: https://duckdb.org/docs/extensions/delta[21] Parquet 扩展: https://duckdb.org/docs/extensions/parquet[22] Azure: https://duckdb.org/docs/extensions/azure[23] S3: https://duckdb.org/docs/extensions/httpfs/s3api[24] read_parquet: https://duckdb.org/docs/data/parquet/overview[25] read_csv: https://duckdb.org/docs/guides/file_formats/csv_import[26] FFI: https://github.com/delta-incubator/delta-kernel-rs[27] Azure: https://duckdb.org/docs/extensions/azure[28] S3: https://duckdb.org/docs/extensions/httpfs/s3api[29] 自动加载: https://duckdb.org/docs/extensions/overview#autoloading-extensions[30] 默认位置: https://github.com/aws/aws-sdk-cpp/blob/main/docs/Credentials_Providers.md[31] 文档: https://duckdb.org/docs/extensions/delta[32] 存储库: https://github.com/duckdb/duckdb_delta[33] delta-kernel-rs: https://github.com/delta-incubator/delta-kernel-rs[34] Delta 扩展: /docs/extensions/delta[35] 2024 Databricks 数据 + AI 峰会: https://www.databricks.com/dataaisummit[36] Hannes Mühleisen: https://hannes.muehleisen.org/[37] 分组讨论: https://www.databricks.com/dataaisummit/session/delta-lake-meets-duckdb-delta-kernel