宣布 DuckDB 0.9.0
DuckDB 团队很高兴宣布最新版本 DuckDB 0.9.0 发布。此版本名为 Undulata,其灵感来源于原产非洲的黄嘴鸭。
要安装新版本,请访问安装指南。完整的发布说明可在GitHub上找到。
0.9.0 版本的新特性
更改内容太多,无法逐一详细讨论,但我们想重点介绍几个特别令人兴奋的特性!
- 核外哈希聚合
- 存储改进
- 索引改进
- DuckDB-Wasm 扩展
- 扩展自动加载
- 改进的 AWS 支持
- Iceberg 支持
- Azure 支持
- PySpark 兼容 API
以下是这些新功能的总结和示例,首先是 SQL 方言的一项更改,旨在默认产生更直观的结果。
破坏性 SQL 更改
结构体自动转换。以前,在确定自动转换规则时,结构体条目的名称会被忽略。因此,结构体字段名称可能会被静默重命名。从本版本开始,这将改为导致错误。
CREATE TABLE structs (s STRUCT(i INTEGER));
INSERT INTO structs VALUES ({'k': 42});
Mismatch Type Error: Type STRUCT(k INTEGER) does not match with STRUCT(i INTEGER). Cannot cast STRUCTs with different names
使用 ROW
函数构建的匿名结构体仍可插入到结构体字段中。
INSERT INTO structs VALUES (ROW(42));
核心系统改进
核外哈希聚合和哈希聚合性能改进。处理大型数据集时,内存管理始终是一个潜在的痛点。通过使用流式执行引擎和缓冲区管理器,DuckDB 支持对大于内存的数据集执行许多操作。DuckDB 还旨在通过使用磁盘溢出技术来支持中间结果不适合内存的查询。
在此版本中,通过支持核外哈希聚合,进一步扩展了对磁盘溢出技术的支持。现在,在 GROUP BY
查询或 DISTINCT
操作期间,由于唯一组数量过多而无法容纳在内存中的哈希表将数据溢出到磁盘,而不是抛出内存不足异常。由于巧妙地使用了基数分区,性能下降是渐进的,避免了性能骤降。只有表中不适合内存的子集会被溢出到磁盘。
我们的哈希聚合性能也普遍得到了提高,特别是在有许多分组的情况下。例如,我们使用以下查询计算一个包含 3000 万行和 15 列的数据集中唯一行的数量
SELECT count(*) FROM (SELECT DISTINCT * FROM tbl);
如果我们将所有数据保留在内存中,查询应该使用大约 6 GB。但是,如果可用内存较少,我们仍然可以完成查询。在下表中,我们可以看到运行时如何受到降低内存限制的影响
内存限制 | v0.8.1 | v0.9.0 |
---|---|---|
10 GB | 8.52 秒 | 2.91 秒 |
9 GB | 8.52 秒 | 3.45 秒 |
8 GB | 8.52 秒 | 3.45 秒 |
7 GB | 8.52 秒 | 3.47 秒 |
6 GB | 内存溢出 | 3.41 秒 |
5 GB | 内存溢出 | 3.67 秒 |
4 GB | 内存溢出 | 3.87 秒 |
3 GB | 内存溢出 | 4.20 秒 |
2 GB | 内存溢出 | 4.39 秒 |
1 GB | 内存溢出 | 4.91 秒 |
压缩实体化。DuckDB 的流式执行引擎内存占用低,但对于分组聚合等操作需要更多内存。这些操作的内存占用可以通过压缩来减少。DuckDB 在其存储格式中已经使用了许多压缩技术,但其中许多技术在查询执行期间使用成本过高。然而,某些轻量级压缩技术非常廉价,以至于减少内存占用的好处超过了(解)压缩的成本。
在此版本中,我们增加了对字符串和整数类型进行压缩的支持,在数据进入分组聚合和排序操作符之前进行。通过使用统计信息,这两种类型都被压缩到最小可能的整数类型。例如,如果我们有以下表格
┌───────┬─────────┐
│ id │ name │
│ int32 │ varchar │
├───────┼─────────┤
│ 300 │ alice │
│ 301 │ bob │
│ 302 │ eve │
│ 303 │ mallory │
│ 304 │ trent │
└───────┴─────────┘
id
列使用 32 位整数。根据我们的统计数据,我们知道最小值为 300,最大值为 304。我们可以减去 300 并转换为 8 位整数,从而将宽度从 4 字节减少到 1 字节。
name
列使用我们内部的字符串类型,其宽度为 16 字节。然而,我们的统计数据显示,这里最长的字符串只有 7 字节。我们可以将其容纳到一个 64 位整数中,如下所示
alice -> alice005
bob -> bob00003
eve -> eve00003
mallory -> mallory7
trent -> trent005
这将宽度从 16 字节减少到 8 字节。为了支持压缩字符串的排序,我们在大端机器上翻转字节,以便我们的比较操作符仍然正确
alice005 -> 500ecila
bob00003 -> 30000bob
eve00003 -> 30000eve
mallory7 -> 7yrollam
trent005 -> 500tnert
通过减少查询中间结果的大小,我们可以防止/减少数据溢出到磁盘,从而减少对昂贵 I/O 操作的需求,进而提高查询性能。
窗口函数性能改进(#7831, #7996, #8050, #8491)。此版本对窗口函数性能进行了多项改进,这得益于代码向量化的增强、部分聚合的更多重用以及通过任务窃取改进的并行性。因此,窗口函数的性能显著提高,尤其是在没有或只有少数分区的场景中。
SELECT
sum(driver_pay) OVER (
ORDER BY dropoff_datetime ASC
RANGE BETWEEN
INTERVAL 3 DAYS PRECEDING AND
INTERVAL 0 DAYS FOLLOWING
)
FROM tripdata;
版本 | 运行时间 |
---|---|
v0.8.0 | 33.8 秒 |
v0.9.0 | 3.8 秒 |
存储改进
已删除行组的清理。从本版本开始,当使用 DELETE
语句删除数据时,整个已删除的行组将自动清理。还增加了在检查点时截断数据库文件的支持,这允许在删除数据后减小数据库文件的大小。请注意,这仅在已删除的行组位于文件末尾时发生。系统尚未移动数据以减小磁盘文件的大小。相反,文件中较早的空闲块会被重复利用来存储后续数据。
索引存储改进(#7930, #8112, #8437, #8703)。ART 索引的内存占用和磁盘占用都得到了许多改进。特别是为了维护 PRIMARY KEY
、UNIQUE
或 FOREIGN KEY
约束而创建的索引,其存储和内存占用都大幅减少。
CREATE TABLE integers (i INTEGER PRIMARY KEY);
INSERT INTO integers FROM range(10000000);
版本 | 大小 |
---|---|
v0.8.0 | 278 MB |
v0.9.0 | 78 MB |
此外,由于磁盘索引存储方式的改进,它们现在可以增量写入磁盘,而不再总是需要完全重写。这使得带有索引的表能够更快地进行检查点操作。
扩展
扩展自动加载。从本版本开始,DuckDB 支持自动安装和加载受信任的扩展。由于许多工作流依赖于未捆绑的核心扩展(例如 httpfs
),许多用户不得不记住预先加载所需的扩展。通过此更改,当在查询中使用时,扩展将自动加载(并可选择安装)。
例如,在 Python 中,以下代码片段现在无需显式加载 httpfs
或 json
扩展即可工作。
import duckdb
duckdb.sql("FROM 'https://raw.githubusercontent.com/duckdb/duckdb/main/data/json/example_n.ndjson'")
可自动加载的扩展集仅限于 DuckDB Labs 分发的官方扩展,可在此处找到。此行为也可以通过使用 autoinstall_known_extensions
和 autoload_known_extensions
设置,或者通过更通用的 enable_external_access
设置来禁用。请参阅配置选项。
DuckDB-Wasm 扩展。此版本增加了对 DuckDB-Wasm 可加载扩展的支持。以前,任何您想与 Wasm 客户端一起使用的扩展都必须内置。通过此版本,扩展可以动态加载。当加载扩展时,Wasm 包将被下载并启用扩展的功能。在我们的Wasm shell中试用一下吧。
LOAD inet;
SELECT '127.0.0.1'::INET;
AWS 扩展。此版本标志着 DuckDB AWS 扩展的推出。此扩展包含依赖于 AWS SDK 的 AWS 相关功能。目前,该扩展包含一个函数 LOAD_AWS_CREDENTIALS
,它使用 AWS 凭证提供者链来自动获取和设置凭证
CALL load_aws_credentials();
SELECT * FROM 's3://some-bucket/that/requires/authentication.parquet';
实验性 Iceberg 扩展。此版本标志着 DuckDB Iceberg 扩展的推出。此扩展增加了对读取以 Iceberg 格式存储的表的支持。
SELECT count(*)
FROM iceberg_scan('data/iceberg/lineitem_iceberg', allow_moved_paths = true);
实验性 Azure 扩展。此版本标志着 DuckDB Azure 扩展的推出。此扩展允许 DuckDB 以与读取 S3 上存储的数据类似的方式,原生读取存储在 Azure 上的数据。
SET azure_storage_connection_string = '<your_connection_string>';
SELECT * FROM 'azure://<my_container>/*.csv';
客户端
实验性 PySpark API。此版本为 Python 客户端增加了实验性 Spark API。该 API 旨在与 PySpark API 完全兼容,让您能够像熟悉 Spark API 一样使用它,同时利用 DuckDB 的强大功能。所有语句都使用我们的关系型 API 转换为 DuckDB 的内部计划,并使用 DuckDB 的查询引擎执行。
from duckdb.experimental.spark.sql import SparkSession as session
from duckdb.experimental.spark.sql.functions import lit, col
import pandas as pd
spark = session.builder.getOrCreate()
pandas_df = pd.DataFrame({
'age': [34, 45, 23, 56],
'name': ['Joan', 'Peter', 'John', 'Bob']
})
df = spark.createDataFrame(pandas_df)
df = df.withColumn(
'location', lit('Seattle')
)
res = df.select(
col('age'),
col('location')
).collect()
print(res)
#[
# Row(age=34, location='Seattle'),
# Row(age=45, location='Seattle'),
# Row(age=23, location='Seattle'),
# Row(age=56, location='Seattle')
#]
请注意,该 API 目前处于实验阶段,仍缺少一些功能。我们非常期待您的反馈。请通过 Discord 或 GitHub 报告您缺少的所有功能。
结语
完整的发布说明可在GitHub 上找到。我们衷心感谢所有贡献者为改进 DuckDB 所做的辛勤工作。