DuckDB 2.0 打破了我对 DuckDB 的认知模型
DuckDB 凭借直接运行在应用程序内部而广受欢迎。2.0 版本突破了这一模型,并带来了你在升级前应当检查的 Python 变更。
DuckDB 之所以流行,部分原因在于你不需要数据库服务器。
安装 Python package,打开 notebook 或脚本,让 DuckDB 指向几个 Parquet 文件,然后就可以开始查询。不需要部署单独的数据库服务。Python 代码与查询引擎之间也不需要网络连接。
DuckDB 直接运行在你的应用程序内部。
随着 DuckDB 2.0 的发布,这一模型并不会消失。
但它不再是全部。
现在,一个 DuckDB 实例可以通过 HTTP 向另一个 DuckDB 实例提供数据和计算能力。除此之外,DuckDB 2.0 还带来了异步 I/O、对半结构化数据更深入的支持、新的 SQL parser,以及多项 Python API 变更。
这里有一个重要的注意事项:server protocol 仍处于 beta 阶段。
所以,不,现在还不是将 PostgreSQL 全面替换为 DuckDB 的时候。
但很明显,DuckDB 正在突破最初定义它的边界。
如果你只想知道是否应该升级,请向下滚动到 现在该怎么做。
DuckDB 现在可以跨越单个进程
理解这一变化为何重要,最简单的方法是将 DuckDB 与传统数据库服务器进行比较。
使用 PostgreSQL 时,你的 Python 应用通常会连接到一个独立的 server process。DuckDB 采取了不同的方法:数据库引擎直接运行在你的应用程序内部。
这是它的超能力之一。
但这也形成了一个自然边界。
想象一下,有一台强大的机器,位于数百 GB 分析数据附近。你的笔记本电脑希望针对这些数据运行查询。
与其将所有源数据传输到笔记本电脑,不如将计算移到数据所在的位置,并且只返回结果,这样会好得多。
这正是 Quack 的用武之地。
Quack 允许 DuckDB 实例通过 HTTP 进行通信。一个 DuckDB 实例充当 server,另一个则成为 client。
有趣的地方并不只是 DuckDB 可以使用 HTTP 进行通信。执行计算的 DuckDB 实例可以继续靠近数据,而另一个 DuckDB 实例则负责消费结果。
不过需要进行一个重要更正:Quack 本身并不是 2.0 中完全全新的功能。
它首次出现在 DuckDB 1.5.3 中。DuckDB 2.0 进一步发展了 server 能力,并新增了 CONNECT 语句,用于将 session 指向一个已附加的远程数据库。
因此,将 DuckDB 2.0 描述成突然转变为传统 server database,会产生误导。
更准确的描述是:
DuckDB 的 in-process 模型依然存在,只是它的世界正在变得更大。
一个小例子可以让这一点更直观
你可以通过两个 DuckDB 2.0 CLI session 来了解这一想法。
在第一个 session 中:
INSTALL quack;
LOAD quack;
CREATE TABLE shared_result AS SELECT 42 AS answer;
CALL quack_serve(
'quack:localhost',
token = 'local-demo-token'
);
保持该 session 运行。
在另一个 DuckDB session 中:
INSTALL quack;
LOAD quack;
ATTACH 'quack:localhost'
AS remote (TOKEN 'local-demo-token');
CONNECT remote;
SELECT * FROM shared_result;
结果是 42。
有趣之处在于,shared_result 只存在于第一个 DuckDB 实例的内存中。第二个实例通过 Quack 访问到了它。
将第一个实例移动到靠近分析数据的机器上,这一想法就会变得实用得多。
注意事项:Quack 仍处于 beta 阶段
DuckDB 2.0 本身是一个 LTS 版本。
但是,Quack 当前的 FAQ 仍将该 protocol 描述为 beta,不适用于生产环境。protocol 细节、函数名称和默认值都可能发生变化。
它也不会将 DuckDB 变成分布式查询引擎。连接多个 client 并不会创建一个能够将单个查询拆分到多台机器上的 cluster。
目前,我认为 Quack 更适合用于尝试,而不是作为替换成熟生产数据库的理由。
但它让 DuckDB 的发展方向变得有趣得多。
Async I/O 可能更快产生影响
对于许多现有 DuckDB 用户来说,2.0 的另一项变更可能会产生更直接的影响:异步 I/O。
想象一下,在 S3 上查询 Parquet 文件。
即使查询引擎速度很快,也可能会花费大量时间等待远程读取。DuckDB 2.0 可以将 I/O 并发与查询处理并发分离,在处理已经到达的数据的同时,让更多读取操作保持 in-flight 状态。
最终版本包含针对 Parquet、CSV 和原生 DuckDB storage 的异步读取支持。
这并不意味着每个查询都会变快。
但如果你的 Python pipeline 花费了大量时间查询远程文件,DuckDB 2.0 就给了你一个重新运行该 workload 并进行测量的充分理由。
灵活数据也得到了改进
DuckDB 2.0 还进一步增强了对 VARIANT 的支持。VARIANT 专为不同 row 之间结构可能发生变化的数据而设计。
想想 event 或 API 数据:其中一行可能包含用户信息,而另一行则包含坐标或完全不同的 payload。
与其立即将所有内容强制纳入一个严格固定的 schema,不如使用 VARIANT 保留这些差异。
这种 type 早于 2.0 就已存在。新增之处在于更深入的集成,包括更好的 columnar execution 和 Parquet 支持。
对于日志、event 以及结构混乱的 API 数据而言,这是一个有用的发展方向,同时不要求你将所有内容都视为不透明的 JSON 字符串。
Python 开发者应检查自己的代码
这是我在升级前绝不会跳过的部分。
DuckDB 2.0 要求使用 Python 3.11 或更高版本。在 Python 3.10 上,未锁定版本的安装将改为选择 1.x 版本。
Arrow 和 Polars 结果相关的 API 也发生了变化。
execute() 结果中的多个 Arrow 和 Polars 方法已被移除。对于这些输出,现在文档建议通过 relation 获取:
import duckdb
con = duckdb.connect()
result = con.sql("SELECT 42 AS answer").pl()
对于 Arrow 输出,请使用 relation 的
to_arrow_table() 或 to_arrow_reader() 方法。
但不要盲目地将每个 execute() 调用都替换为 sql()。
如果你的代码使用了 fetchone() 或 fetchmany() 等增量结果访问方式,请继续在 con.execute(query) 上调用这些方法。它们已从 relation 中移除。
这是一个看似很小但影响足够大的区别,可能会导致现有 notebook 和 pipeline 失效。
升级前请搜索你的代码。
底层还有一项变化
DuckDB 2.0 将新的、基于 PEG 的 SQL parser 设为默认 parser,替代此前源自 PostgreSQL 的 parser。
对于大多数 Python 用户来说,这本身并不是升级的理由。你可能会注意到更好的错误位置提示和建议。
对于 extension 开发者而言,这一点更有趣,因为 extension 可以添加 grammar rules。
这再次表明,DuckDB 并不只是在现有基础上添加功能。引擎的部分基础也正在发生变化。
留意 storage format
如果你使用持久化 DuckDB database,这一点可能比任何闪亮的新功能都更加重要。
使用 DuckDB 2.0 创建的新 database 默认采用 2.0 storage format。
较旧版本的 DuckDB 无法读取这些文件。
如果多个应用共享 DuckDB 文件,而只有其中一个应用完成升级,这就会带来问题。
现有 database 更安全:当 DuckDB 2.0 打开由旧版本创建的 database 时,在对其进行读写时会保留该 database 的 storage version。DuckDB 还提供了兼容性设置,用于创建面向旧 reader 的文件。
不过,不要等到升级生产环境后才发现 rollback 存在问题。
请先备份 database,并盘点所有会消费这些 database 的 process。
现在该怎么做
如果你对 DuckDB 2.0 感兴趣,请在独立环境中安装它,并运行自己的 workload:
pip install duckdb==2.0.0
如果 server 这一想法令你感兴趣,可以在本地尝试 Quack。如果异步 I/O 对你很重要,请测试实际的 S3 或远程文件查询。
对于 Python pipeline,请查看 migration guide,并测试你的 Arrow 和 Polars 转换、fetchone() 与 fetchmany() 调用、extension 以及其他结果处理代码。
对于持久化 database,请先备份,并在创建新的 2.0-format 文件之前测试 rollback path。
对于任务关键型生产系统,率先升级并没有任何奖励。
DuckDB 建议先在 shadow system 上进行测试,并考虑等待 patch release,再迁移关键 workload。目前的 release calendar 将 DuckDB 2.0.1 安排在 11 月 18 日发布,不过该日期可能会发生变化。
DuckDB 1.5 将持续获得支持,直到 2027 年 3 月。
你有足够的时间来充分测试。
DuckDB 原有的超能力不会消失
DuckDB 2.0 并没有突然变成 PostgreSQL。
你仍然可以在 Python 内部安装它,查询一个 Parquet 文件,而且完全不需要部署 database server。
这依然是它最大的优势之一。
变化在于,这个简单模型能够延伸到多远。
DuckDB 现在可以跨越单个进程,更有效地处理远程 I/O,并以更自然的方式处理不规则数据。与此同时,它的 Python 和 storage interface 发展得足够快,现有用户应当在升级前进行测试。
Quack 仍处于 beta 阶段。
但这一发展方向非常令人着迷。
DuckDB 通过让 analytical database 变得异常简单而流行起来。2.0 版本开始探索:这种简单性还能延伸多远。
如果你喜欢实用的 Python 见解、有用的技巧、benchmark,以及对新版本的清晰解读,欢迎关注我,获取更多内容。
官方来源
DuckDB 2.0 announcement and release highlights
DuckDB 2.0 GitHub release notes
Quack overview
Quack readiness FAQ
DuckDB migration guide
Python migration guide
Release calendar and support policy