DeepSeek总结的PostgreSQL与DuckDB联动过于混沌的现状

原文地址:https://zenn.dev/forcia_tech/articles/20251213_advent_calendar
探索PostgreSQL与DuckDB联动过于混沌的现状

发布于 2025/12/13

这是 PostgreSQL Advent Calendar 2025 12月13日的文章。

前言

我是工程师吉田。

在 Forcia,我们有很多机会将 PostgreSQL 用于分析场景,但由于 OLAP 相关的功能大多通过第三方扩展来补充,所以我经常成为一个"扩展收集狂",进行信息收集和验证。

近年来,一提到分析型数据库,DuckDB 的名字总是首先被提及。DuckDB 是一个嵌入式数据库,并且能够加载各种数据源,其特点在于应用可能性非常广泛。

另一方面,PostgreSQL 的扩展开发自由度也很高,可以实现各种功能。由于这两个都能"实现各种功能",将 PostgreSQL 与 DuckDB 联动的尝试也在这几年开始发展,但这一趋势似乎发展得太快,相关项目的涌现和频繁的更新让人感觉局面有些混沌。

因此,在本文中,我将介绍截至 2025 年 12 月的 PostgreSQL × DuckDB 联动的主要选项,并总结各自的用途和现状。

从 DuckDB 连接到 PostgreSQL

虽说都是联动,但根据连接方向的不同(谁连接谁),方法也有所不同。当以 DuckDB 为主体时,PostgreSQL 的角色是作为一个数据源。

也就是说,这个方向的联动是 DuckDB 连接到 PostgreSQL 服务器的形式。这可以通过官方提供的扩展来实现。

详细的使用方法如链接中所述,它将使您能够在 DuckDB 中操作已连接的 PostgreSQL 的表。

这个扩展可以理解为将 PostgreSQL 添加到 DuckDB 支持的各种格式之一。

它本质上是一个向 PostgreSQL 服务器发送查询的功能,从这个意义上说,它类似于 PostgreSQL 的 fdw 功能。

将 DuckDB 嵌入 PostgreSQL 内部

相反,当以 PostgreSQL 为主体时,可以将 DuckDB 视为提供分析功能的工具。

这才是本文的主题,即尝试将 DuckDB 嵌入到 PostgreSQL 内部。

通过嵌入 DuckDB 想要实现的目标,大体有以下三点:

  1. 计算优化:DuckDB 针对 OLAP 进行了优化,我们希望采用 DuckDB 的查询引擎,以提升执行复杂 SQL 时的性能。
  2. 存储优化:DuckDB 是适合海量数据分析的列式存储[1]数据库,并且支持 Parquet[2]、Iceberg[3] 等面向数据仓库的格式。我们希望通过采用 DuckDB 的存储引擎来实现数据布局的优化。
  3. 与现有功能的集成:我们并非要完全依赖 DuckDB 来提供所有数据库功能,而是希望将其与现有 PostgreSQL 功能结合使用,以兼顾 OLAP 和 OLTP 性能。

就目前而言,在我的观察范围内,还没有能完全实现这三点目标的扩展。不过,有几个强大的扩展已经接近这个目标,并且持续开发中。

截至 2025 年 12 月,我个人觉得更新频繁且值得关注的扩展有三个:pg_duckdbpg_mooncakepg_lake。下一节开始将详细介绍每一个。

pg_duckdb

pg_duckdb 是 DuckDB 官方开发的扩展功能。该扩展主要提供使用 DuckDB 查询引擎扫描 PostgreSQL 表的功能,以及扫描 DuckDB 支持的外部数据的功能。

此外,它还承担着提供将 DuckDB 嵌入 PostgreSQL 的核心实现这一角色,许多其他正在开发的扩展也以 pg_duckdb 为基础[4]。

使用示例

使用 Docker Hub 上的镜像启动服务器。

bash 复制代码
docker run -itd --name pgduckdb -e POSTGRES_PASSWORD=postgres pgduckdb/pgduckdb:17-v1.0.0

在服务器上执行以下命令,创建一个简单的测试表。

sql 复制代码
drop table if exists test;
create table test as select generate_series(1, 1000) as id, gen_random_uuid()::text as val;

为了切换查询引擎,启用 force_extension 参数。

sql 复制代码
set duckdb.force_execution = true;

现在查询引擎已切换为 duckdb。让我们看看执行计划。

sql 复制代码
postgres=# explain analyze select * from test where val = val;

(此处为执行计划输出,格式为 DuckDB 风格,显示为 Custom Scan (DuckDBScan),其中包含 DuckDB 的执行计划详情。)

它以一种与熟悉的 PostgreSQL 执行计划截然不同的格式显示,这就是 DuckDB 的执行计划。其机制是在 Custom Scan 内部使用 DuckDB 引擎执行整个查询并返回结果。

关于使用 DuckDB 查询引擎的问题点

由于整个查询都由 DuckDB 执行,因此存在一个只能使用 DuckDB 支持的功能的问题。如果执行了 DuckDB 查询引擎无法处理的查询,则会回退到 PostgreSQL 的查询引擎。

要确认这个行为,最简单的方法是创建一个自定义函数并执行它。试着创建一个简单的函数,它以 text 为参数并原样返回该 text。

sql 复制代码
create or replace function sonomama(str text) returns text as $$ select str; $$ language sql strict immutable;

在查询中执行此函数时,会显示如下警告,并执行常规的 PostgreSQL 扫描。

sql 复制代码
postgres=# explain analyze select * from test where val = sonomama(val);
WARNING:  (PGDuckDB/CreatePlan) Prepared query returned an error: 'Catalog Error: Scalar Function with name sonomama does not exist!
Did you mean "json"?...
                                              QUERY PLAN
------------------------------------------------------------------------------------------------------
 Seq Scan on test  (cost=0.00..26.00 rows=1000 width=41) (actual time=0.011..0.125 rows=1000 loops=1)
   Filter: (val IS NOT NULL)
 Planning Time: 1.354 ms
 Execution Time: 0.180 ms
(4 rows)

从实用性角度考虑,这感觉是一个非常严重的问题。因为这意味着它无法与几乎所有提供自定义函数或自定义类型的第三方扩展共存。

看来目前很难实现将 PostgreSQL 丰富的扩展性与 DuckDB 强大的 OLAP 性能相结合的用法。

关于查询引擎带来的性能提升

pg_mooncake

pg_duckdb 可以处理的数据存储是 PostgreSQL 的堆表或外部数据,它并不直接在 PostgreSQL 内部保存列式存储。

相比之下,也有一些扩展为 PostgreSQL 添加了新的 Iceberg 格式的列式访问方法,并使用 DuckDB 的查询引擎来扫描 Iceberg 表。其中一个就是 pg_mooncake

!
pg_mooncake 是目前正在开发的扩展,其 v0.1 和 v0.2 版本之间存在重大规格变更。
以前它的定位是比较简单的列式访问方法,类似于 Citus Columnar 等。但现在它变成了为现有表创建列式副本的功能,竞争对手可能更像是物化视图(例如 pg_ivm)。

使用示例

使用 Docker Hub 上的镜像启动服务器。

bash 复制代码
docker run -itd --name pgmooncake -e POSTGRES_PASSWORD=postgres mooncakelabs/pg_mooncake:17-v0.2-preview

在服务器上执行以下命令,创建一个简单的测试表。

sql 复制代码
create extension pg_mooncake;
drop table if exists test;
create table test as select generate_series(1, 1000) as id, gen_random_uuid()::text as val;

在 pg_mooncake 中,通过复制现有的行式表来创建列式表。使用 mooncake.create_table 函数复制现有表。

从下面的情况可以看出,父表被添加到 pg_publication_tables 中,这表明它利用了逻辑复制的机制来实现复制。

sql 复制代码
postgres=# call mooncake.create_table('test_iceberg', 'test');
CALL
postgres=# select * from pg_publication_tables;
   pubname    | schemaname | tablename | attnames | rowfilter
--------------+------------+-----------+----------+-----------
 moonlink_pub | public     | test      | {id,val} |
(1 row)

postgres=# select count(*) from test_iceberg ;
 count
-------
  1000
(1 row)

test 表的操作会同步反映到复制的 test_iceberg 表中。

sql 复制代码
postgres=# insert into test values (1001, 'hoge');
INSERT 0 1
postgres=# select * from test_iceberg where val = 'hoge';
  id  | val
------+------
 1001 | hoge
(1 row)

postgres=# update test set val = 'fuga' where val = 'hoge';
UPDATE 1
postgres=# select * from test_iceberg where val = 'fuga';
  id  | val
------+------
 1001 | fuga
(1 row)

postgres=# delete from test where val = 'fuga';
DELETE 1
postgres=# select count(*) from test_iceberg;
 count
-------
  1000
(1 row)

另外,即使删除父表,复制的表也会保留。但是,由于不支持直接更新 Iceberg 表,所以它将是只读表(尝试直接更新会出错)。

sql 复制代码
postgres=# drop table test;
DROP TABLE
postgres=# select count(*) from test_iceberg;
 count
-------
  1000
(1 row)

postgres=# insert into test_iceberg values (1001, 'hoge');
ERROR:  (PGDuckDB/CreatePlan) Prepared query returned an error: 'Not implemented Error: PlanInsert not implemented

关于查询引擎的处理

在 pg_mooncake 中,对于包含扫描 Iceberg 表的查询,整个查询会由 DuckDB 的查询引擎执行。因此,与 pg_duckdb 类似,它无法与 DuckDB 不支持的功能一起使用。

(此处为示例:执行计划显示使用 DuckDBScan 成功执行了 DuckDB 可处理的查询。)

如果查询包含 DuckDB 无法处理的元素,查询本身就会出错。

sql 复制代码
-- DuckDB 无法处理的查询
postgres=# create or replace function sonomama(str text) returns text as $$ select str; $$ language sql strict immutable;
CREATE FUNCTION
postgres=# explain analyze select * from test_iceberg where val = sonomama(val);
ERROR:  (PGDuckDB/CreatePlan) Prepared query returned an error: 'Catalog Error: Scalar Function with name sonomama does not exist!
Did you mean "json"?

这个问题本身已经被认识到,并且似乎有计划在未来的版本中改进,但给人的印象是实现起来相当困难。

pg_lake

pg_lake 是数据仓库 Snowflake 的开发公司最近公开的扩展,与 pg_mooncake 类似,它使您能够在 PostgreSQL 上操作 Iceberg 表。

在提供 Iceberg 访问方法、利用 DuckDB 查询引擎等方面,pg_lake 的定位与 pg_mooncake 相似。然而,实现这一目标的架构却大不相同。

pg_lake 最大的特点是,DuckDB 的查询引擎作为完全独立的进程运行。

从其架构图来看,似乎是将 Iceberg 表存储在 PostgreSQL 本体中,而查询则由一个名为 pgduck_server 的独立于 PostgreSQL 本体的进程来执行。

使用示例

由于尚未提供官方镜像,因此按照仓库 docker 目录下的指南[6] 构建环境。

从 docker-compose.yml 可以看出,正如架构图所示,除了主容器外,还需要一个名为 pgduck-server 的容器。

在 pg_lake-postgres 容器上,如下所示,指定 iceberg 作为表访问方法来创建表。

也可以直接对表进行 CRUD 操作。

sql 复制代码
postgres=# create table test_iceberg using iceberg as select generate_series(1, 1000) as id, gen_random_uuid()::text as val;
CREATE TABLE AS

postgres=# insert into test_iceberg values (1001, 'hoge');
INSERT 0 1
postgres=# select * from test_iceberg where val = 'hoge';
  id  | val
------+------
 1001 | hoge
(1 row)

postgres=# update test_iceberg set val = 'fuga' where val = 'hoge';
UPDATE 1
postgres=# select * from test_iceberg where val = 'fuga';
  id  | val
------+------
 1001 | fuga
(1 row)

postgres=# delete from test_iceberg where val = 'fuga';
DELETE 1
postgres=# select count(*) from test_iceberg ;
 count
-------
  1000
(1 row)

关于查询引擎的处理

与之前的两个扩展类似,创建一个自定义函数,并尝试在 Iceberg 表上使用它。

sql 复制代码
postgres=# create or replace function sonomama(str text) returns text as $$ select str; $$ language sql strict immutable;
CREATE FUNCTION

postgres=# explain analyze select * from test_iceberg where val = sonomama(val);
                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Foreign Scan on test_iceberg  (cost=100.00..120.00 rows=1000 width=0) (actual time=2.094..2.395 rows=1000.00 loops=1)
   Engine: DuckDB
   ->  READ_PARQUET
         Filters: (val IS NOT NULL)
         Function: READ_PARQUET
         Estimated Cardinality: 200
   Buffers: shared hit=5
 Planning Time: 0.126 ms
 Execution Time: 3.461 ms
(9 rows)

查询竟然成功了。从执行计划来看,sql 函数似乎被解析了,并替换成了 DuckDB 查询引擎内部可以执行的形式。

让我们再进一步测试一下。作为 DuckDB 无法处理的类型的例子,可以创建包含 range 类型的 Iceberg 表吗?

sql 复制代码
postgres=# create table range_test using iceberg as select generate_series(1,1000) as id, int4range(1, 100) as val;
CREATE TABLE AS

成功创建了。那么,让我们尝试对这个表进行筛选。

sql 复制代码
postgres=# explain analyze select * from range_test where id < 100 and val && int4range(10, 20);
                                                   QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
 Foreign Scan on range_test  (cost=100.00..122.50 rows=10 width=0) (actual time=2.715..2.787 rows=99.00 loops=1)
   Filter: (val && '[10,20)'::int4range)
   Engine: DuckDB
   ->  READ_PARQUET
         Filters: id<100
         Function: READ_PARQUET
         Estimated Cardinality: 200
   Buffers: shared hit=5
 Planning Time: 0.147 ms
 Execution Time: 4.234 ms
(10 rows)

像这样,扫描成功完成,没有问题。令人惊讶的是,可以看出针对 id 的筛选是在 DuckDB 侧执行的,而针对 val 的筛选是在 PostgreSQL 侧执行的。这正是 pg_mooncake 所追求的行为,从与现有 PostgreSQL 功能集成的角度来看,我认为这可能是决定性的特点。

另一方面,整个查询中只有 Foreign Scan 内部使用了 DuckDB 的查询引擎,似乎不适合需要在整个查询范围内使用高度优化的 DuckDB 查询引擎的用途。

总结

在我观察的范围内,介绍了几个在 PostgreSQL × DuckDB 联动中值得关注的扩展。

无论介绍的哪个扩展,我认为在将 Parquet 或 Iceberg 的应用、查询引擎的替换等 OLAP 功能引入 PostgreSQL 方面,都是非常有用的扩展。然而,将两个完全不同的数据库结合起来尝试,确实存在相当大的难度,我也感受到绝对正确的答案仍在探索之中。

不过,正如开头所述,这是一个仍在飞速发展的领域,我认为我们用户能够充分利用两者协同效应的未来似乎并不遥远。本文仅限于对各扩展的基本介绍,今后希望继续关注,包括进行性能验证等。

本文作者

吉田 侑弥

在撰写有意义的博客这一点上,最近完全变成了与 AI 的较量,每次都倍感艰难。

这次向 ChatGPT 询问关于 pg_duckdb 的事情时,它回答了关于 duckdb 与 PostgreSQL 联动的内容,所以我才能安心地撰写这篇文章。

脚注

  1. 这是一种将数据中相同列集中存储的格式。在对特定列进行计算时,数据获取效率高,并且可以按列进行高效压缩等优点,但缺点是不擅长更新。 ↩︎
  2. 用于存储数据的存储格式。它是列式且高压缩的,对于大规模数据分析非常有用。 ↩︎
  3. 一种将文件集合作为一个表来处理的表格式。它是在考虑到处理大规模数据的基础上设计的。 ↩︎
  4. 相反,可以预见提供与 pg_duckdb 相同功能的扩展会逐渐整合。实际上,像 pg_analytics 这样曾经很有影响力但后来被归档的扩展也存在。 ↩︎
  5. 特别是在 PostgreSQL 相对不擅长的查询比较中,实际上也并非在所有场景下都同样逊色。 ↩︎
  6. 在笔者的环境中,docker build 失败了,因此使用了 Docker Hub 上公开的非官方镜像。PostgreSQL 版本为 18,pg_lake 版本为 v1.0.0。 ↩︎
相关推荐
小高不会迪斯科7 小时前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
e***8907 小时前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t7 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
失忆爆表症9 小时前
03_数据库配置指南:PostgreSQL 17 + pgvector 向量存储
数据库·postgresql
AI_56789 小时前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
SQL必知必会10 小时前
SQL 窗口帧:ROWS vs RANGE 深度解析
数据库·sql·性能优化
Gauss松鼠会10 小时前
【GaussDB】GaussDB数据库开发设计之JDBC高可用性
数据库·数据库开发·gaussdb
+VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
识君啊11 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
一个天蝎座 白勺 程序猿11 小时前
破译JSON密码:KingbaseES全场景JSON数据处理实战指南
数据库·sql·json·kingbasees·金仓数据库