alitrack

duckdb_fdw 支持Postgres 17 与 DuckDB 1.1.2了

duckdb_fdw 支持Postgres 17 与 DuckDB 1.1.2了

PostgreSQL 17 与 DuckDB 1.1.2 都出来很久了,我的新版本 duckdb_fdw 才姗姗来迟。更新 DuckDB 到V1.1.2 版本没有碰到问题。更新PG 到17碰到了些小问题。

下面简单介绍下编译PG 17支持版本碰到的问题

tuplestore_donestoring

PG 17 去除了 tuplestore_donestoring 函数,搜索了下PG 16里的 tuplestore_donestoring 实现,
# tuplestore.h
/* Backwards compatibility macro */
#define tuplestore_donestoring(state) ((void) 0)
出于好奇,我想看看它是从哪个版本开始就不再有意义,经过一番搜索发现,从大佬 Tom Lane 2003-03-09 的 commit( dd04e958c8b03c0f0512497651678c7816af3198 [1] ) 开始,
tuplestore_donestoring() isn't needed anymore, but provide a no-op
macro definition so as not to create compatibility problems.

该 commit 对应的tag是7.4

git tag --contains dd04e958c8b03c0f0512497651678c7816af3198
#返回
REL7_4
REL7_4_1
...


从 PG 的百科Foreign_data_wrappers 页面 [2] 得知,PG第一个fdw的出现时间是2011年, 在 2003 年,SQL 标准中新增了一项名为 SQL/MED [3] (“SQL 外部数据管理”)的规范。这为 SQL 数据库访问远程对象提供了一种标准化的方法。2011 年,PostgreSQL 9.1 发布,支持该标准的只读功能,而在 2013 年,PostgreSQL 9.3 则增加了对写入的支持。

原来 tuplestore_donestoring 是一个从FDW诞生之日起就没意义的函数,那又是谁引入的这个无用的函数的呢(我是抄的 sqlite_fdw),继续挖掘,找到了,是 Martin Pihlak 和 Peter Eisentraut 的锅,是他们在实现 SQL/MED [4] 时引入的。

create_foreignscan_path 等

create_foreignscan_path、create_foreign_join_path、create_foreign_upper_path 三个函数从PG 17开始都额外增加了一个参数 List *fdw_restrictinfo 。 从PG 12 开始 [5] , 将 create_foreignscan_path() 拆分成三个不同的函数,在此之前,postgres_fdw 使用 create_foreignscan_path() 来生成基础关系的路径,外部联接以及外部上层关系的路径。这是不正确的,因为 create_foreignscan_path() 调用了 get_baserel_parampathinfo(),该函数只对基础关系有效。
函数名 功能描述 使用场景
create_foreignscan_path 创建外部扫描路径,用于访问外部表数据。 查询外部数据源的基本扫描操作。
create_foreign_upper_path 创建外部上层路径,处理聚合或排序等操作。 处理上层的复杂查询需求。
create_foreign_join_path 创建外部连接路径,连接多个外部表。 需要从多个外部数据源中获取数据时。

duckdb_fdw_version

自动从 duckdb.hpp 的三个宏计算得到
    return (DUCKDB_MAJOR_VERSION * 10000) +
                         (DUCKDB_MINOR_VERSION * 100) +
                         std::stoi(DUCKDB_PATCH_VERSION);
🤔:是不是返回字符型的 DUCKDB_VERSION 更好?或者再加上 DUCKDB_SOURCE_ID
v1.1.2 f680b7d08f

简单速度测试

数据集

使用 Kaggle 上下载的 Transparency in Hospital Prices [6] 数据集,转为parquet格式了。

准备工作

create extension duckdb_fdw;

CREATE SERVER DuckDB_server FOREIGN DATA WRAPPER duckdb_fdw OPTIONS (database ':memory:');
SELECT duckdb_execute('duckdb_server','CREATE VIEW hospital_prices AS SELECT *  FROM ''/Users/m2max/py/notebook/duckdb/hospital_prices.parquet''');

IMPORT FOREIGN SCHEMA public LIMIT TO(hospital_prices) FROM SERVER DuckDB_server INTO public;

create  unlogged table hospital_prices_pg as 
select * from hospital_prices  limit 0;

-- 导入时间比较长,请耐心等待
COPY hospital_prices_pg
(cms_certification_num, 
payer, 
code, 
internal_revenue_code, 
units, 
description, 
inpatient_outpatient, 
price, 
code_disambiguator)
FROM '/Users/m2max/py/notebook/duckdb/hospital_prices.csv'
DELIMITER ','
CSV HEADER;

空间占用对比

格式 大小(GB)
csv 31
parquet 1.4
pg (unlogged) 42

hostpital_prices 在PG上的空间占用

hostpital_prices各种格式下文件大小

速度对比, duckdb_fdw vs PG unlogged table

postgres=# \timing on
Timing is on.
postgres=# select count(*) from hospital_prices;
   count
-----------
 296210747
(1 row)

Time: 90.963 ms
postgres=# select count(*) from hospital_prices_pg;
   count
-----------
 296210747
(1 row)

Time: 28250.129 ms (00:28.250)
postgres=# select 28250.129/90.963;
       ?column?
----------------------
 310.5672526191968163
(1 row)

Time: 0.791 ms
postgres=# select code,count(*) from hospital_prices_pg group by 1 limit 3;
    code     | count
-------------+-------
            +|    35
 00406915076 |
            +|   134
 51754500105 |
 #VALUE!     |     8
(3 rows)

Time: 73079.285 ms (01:13.079)
postgres=# select code,count(*) from hospital_prices group by 1 limit 3;
      code       | count
-----------------+-------
 HCPCS/CPT 81240 |  2002
 HCPCS/CPT 81401 |  1240
 HCPCS/CPT 81406 |   816
(3 rows)

Time: 587.465 ms
postgres=# select 73079.285/587.465;
       ?column?
----------------------
 124.3976832662371375
(1 row)

Time: 0.271 ms

duckdb_fdw vs pg unlogged table

DuckDB下运行时间

D SELECT count(*) from 'hospital_prices.parquet' ;
┌──────────────┐
│ count_star() │
│    int64     │
├──────────────┤
│    296210747 │
└──────────────┘
Run Time (s): real 0.043 user 0.073575 sys 0.014529
D SELECT code,count(*) from 'hospital_prices.parquet' GROUP BY 1 LIMIT 3;
┌─────────┬──────────────┐
│  code   │ count_star() │
│ varchar │    int64     │
├─────────┼──────────────┤
│ 62322   │        11236 │
│ 62368   │         7206 │
│ 63057   │         1239 │
└─────────┴──────────────┘
Run Time (s): real 0.616 user 4.821924 sys 0.332667

duckdb

引用链接

[1] dd04e958c8b03c0f0512497651678c7816af3198: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=dd04e958c8b03c0f0512497651678c7816af3198
[2] PG 的百科Foreign_data_wrappers 页面: https://wiki.postgresql.org/wiki/Foreign_data_wrappers
[3] SQL/MED: https://wiki.postgresql.org/wiki/SQL/MED
[4] SQL/MED: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=cae565e503c42a0942ca1771665243b4453c5770
[5] 从PG 12 开始: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=34ea1ab7fd305afe1124a6e73ada0ebae04b6ebb
[6] Transparency in Hospital Prices: https://www.kaggle.com/datasets/jpmiller/healthcare