PolarDB开源数据库进阶课17 集成数据湖功能

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 50GB
简介: 本文介绍了如何在PolarDB数据库中接入pg_duckdb、pg_mooncake插件以支持数据湖功能, 可以读写对象存储的远程数据, 支持csv, parquet等格式, 支持delta等框架, 并显著提升OLAP性能。

背景

穷鬼玩PolarDB RAC一写多读集群系列已经写了几篇:

本篇文章介绍一下如何在PolarDB数据库中接入pg_duckdb支持数据湖功能? 且OLAP性能数量级提升. 实验环境依赖 《在Docker容器中用loop设备模拟共享存储》 , 如果没有环境, 请自行参考以上文章搭建环境.

pg_duckdb 和 pg_mooncake 都依赖duckdb, 依赖的duckdb版本不同时可能导致冲突.

b站视频链接

Youtube视频链接

pg_duckdb

安装插件时, 需要在PolarDB集群的所有机器上都进行安装, 顺序建议先PolarDB Standby, 然后是所有的RO节点, 然后是RW节点. ( 将二进制安装在共享目录中, 软链使用时, 仅需安装一次即可. )

创建插件create extension xxx;则仅需在RW节点执行.

配置插件参数例如 postgresql.conf 里面的 xxx.xxx=xxx也需要在PolarDB集群的所有机器上都进行安装, 顺序建议先PolarDB Standby, 然后是所有的RO节点, 然后是RW节点.

下面仅演示安装插件.

1、安装pg_duckdb插件 ( 截止20250214最新版本为v0.3.1, 支持duckdb最新版本v1.2.0 )

cd /data  
git clone --depth 1 -b v0.3.1 https://github.com/duckdb/pg_duckdb  
cd pg_duckdb/third_party  
git clone --depth 1 -b v1.2.0 https://github.com/duckdb/duckdb  
cd /data/pg_duckdb  
sudo apt-get install -y ninja-build  
USE_PGXS=1 make install

2、配置 shared_preload_libraries

# 查询 shared_preload_libraries 当前配置      
$ psql  
psql (PostgreSQL 15.10 (PolarDB 15.10.2.0 build d4f5477d debug) on aarch64-linux-gnu)  
Type "help" for help.  
  
postgres=# show shared_preload_libraries ;   
        shared_preload_libraries          
----------------------------------------  
 $libdir/polar_vfs,$libdir/polar_worker  
(1 row)

配置 shared_preload_libraries

# 依次在对应容器中修改standby,ro,rw节点   
  
echo "  
shared_preload_libraries = '\$libdir/polar_vfs,\$libdir/polar_worker,\$libdir/pg_duckdb'  
" >> ~/standby/postgresql.conf    
  
echo "  
shared_preload_libraries = '\$libdir/polar_vfs,\$libdir/polar_worker,\$libdir/pg_duckdb'  
" >> ~/replica1/postgresql.conf    
  
echo "  
shared_preload_libraries = '\$libdir/polar_vfs,\$libdir/polar_worker,\$libdir/pg_duckdb'  
" >> ~/primary/postgresql.conf

3、重启PolarDB数据库

# 依次在对应容器中重启standby,ro,rw节点  
  
pg_ctl restart -m fast -D ~/standby  
  
pg_ctl restart -m fast -D ~/replica1  
  
pg_ctl restart -m fast -D ~/primary

4、在RW中创建pg_duckdb插件

$ psql  
psql (PostgreSQL 15.10 (PolarDB 15.10.2.0 build d4f5477d debug) on aarch64-linux-gnu)  
Type "help" for help.  
  
postgres=# create extension pg_duckdb ;   
CREATE EXTENSION  
    
postgres=# show duckdb.    
duckdb.allow_community_extensions                                duckdb.force_execution                                           duckdb.motherduck_enabled
duckdb.allow_unsigned_extensions                                 duckdb.max_memory                                                duckdb.motherduck_postgres_database
duckdb.autoinstall_known_extensions                              duckdb.max_workers_per_postgres_scan                             duckdb.motherduck_token
duckdb.autoload_known_extensions                                 duckdb.memory_limit                                              duckdb.postgres_role
duckdb.disabled_filesystems                                      duckdb.motherduck_background_catalog_refresh_inactivity_timeout  duckdb.threads
duckdb.enable_external_access                                    duckdb.motherduck_default_database                               duckdb.worker_threads

5、创建duckdb临时表(目前pg_duckdb 还是要带货motherduck云服务的. 不过代码都开源了, 未来支持本地存储持久化duckdb表应该也不是问题.)

postgres=# create table t(id int, c1 int, c2 float8, c3 text, c4 timestamp) using duckdb;    
ERROR:  Only TEMP tables are supported in DuckDB if MotherDuck support is not enabled    
    
    
postgres=# create temp table t(id int, c1 int, c2 float8, c3 text, c4 timestamp) using duckdb;    
CREATE TABLE

插入1000万数据

postgres=# insert into t select id::int,random()*100, random()*1000,md5(random()::text),now() from generate_series(1,10000000) t(id);    
    
    
postgres=# \d+ t    
                                                    Table "pg_temp_14.t"    
 Column |            Type             | Collation | Nullable | Default | Storage  | Compression | Stats target | Description     
--------+-----------------------------+-----------+----------+---------+----------+-------------+--------------+-------------    
 id     | integer                     |           |          |         | plain    |             |              |     
 c1     | integer                     |           |          |         | plain    |             |              |     
 c2     | double precision            |           |          |         | plain    |             |              |     
 c3     | text                        |           |          |         | extended |             |              |     
 c4     | timestamp without time zone |           |          |         | plain    |             |              |     
Access method: duckdb

简单的测试一下耗时

postgres=# \timing    
Timing is on.    
postgres=# select count(*) from t;    
  count       
----------    
 10000000    
(1 row)    
    
Time: 8.246 ms    
postgres=# select count(distinct id) from t;    
  count       
----------    
 10000000    
(1 row)    
    
Time: 224.928 ms
postgres=# explain verbose select count(distinct id) from t;    
                          QUERY PLAN                          
--------------------------------------------------------------
 Custom Scan (DuckDBScan)  (cost=0.00..0.00 rows=0 width=0)
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value
   DuckDB Execution Plan: 
 
 ┌───────────────────────────┐
 │    UNGROUPED_AGGREGATE    │
 │    ────────────────────   │
 │        Aggregates:        │
 │     count(DISTINCT #0)    │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         PROJECTION        │
 │    ────────────────────   │
 │             id            │
 │                           │
 │       ~10000000 Rows      │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         SEQ_SCAN          │
 │    ────────────────────   │
 │          Table: t         │
 │   Type: Sequential Scan   │
 │      Projections: id      │
 │                           │
 │       ~10000000 Rows      │
 └───────────────────────────┘
 
 
(28 rows)
Time: 10.283 ms

6、简单对比PostgreSQL本地表

更复杂的对比可以参考:

创建本地表

postgres=# set polar_force_unlogged_to_logged_table=off;  
postgres=# create unlogged table t_pg (like t);    
CREATE TABLE    
Time: 40.752 ms    
postgres=# \d+ t_pg    
                                                     Table "public.t_pg"    
 Column |            Type             | Collation | Nullable | Default | Storage  | Compression | Stats target | Description     
--------+-----------------------------+-----------+----------+---------+----------+-------------+--------------+-------------    
 id     | integer                     |           |          |         | plain    |             |              |     
 c1     | integer                     |           |          |         | plain    |             |              |     
 c2     | double precision            |           |          |         | plain    |             |              |     
 c3     | text                        |           |          |         | extended |             |              |     
 c4     | timestamp without time zone |           |          |         | plain    |             |              |     
Access method: heap

目前不支持从duckdb写入本地表, 未来可能会支持?

postgres=# insert into t_pg select * from t;    
ERROR:  DuckDB does not support modififying Postgres tables    
Time: 1.887 ms

写入1000万数据

postgres=# insert into t_pg select id::int,random()*100, random()*1000,md5(random()::text),now() from generate_series(1,10000000) t(id);    
INSERT 0 10000000    
postgres=# vacuum analyze t_pg;

本地表相比duckdb还是有很大差距的, 20倍以上吧. PolarDB的性能还取决于shared_buffer和work_mem, hash_mem_multiplier, parallel...等相关参数的配置.

postgres=# select count(*) from t_pg;    
  count       
----------    
 10000000    
(1 row)    
    
Time: 303.805 ms    
  
postgres=# select count(distinct id) from t_pg;    
  count       
----------    
 10000000    
(1 row)    
    
Time: 4685.524 ms (00:04.686)
postgres=# explain verbose select count(distinct id) from t_pg;    
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Aggregate  (cost=251481.60..251481.61 rows=1 width=8)
   Output: count(DISTINCT id)
   ->  Seq Scan on public.t_pg  (cost=0.00..223918.08 rows=11025408 width=4)
         Output: id, c1, c2, c3, c4
 JIT:
   Functions: 3
   Options: Inlining false, Optimization false, Expressions true, Deforming true
(7 rows)
Time: 8.926 ms

7、本地表JOIN duckdb temp table

postgres=# explain verbose select count(distinct t.id) from t join t_pg on (t.id=t_pg.id);
                          QUERY PLAN                          
--------------------------------------------------------------
 Custom Scan (DuckDBScan)  (cost=0.00..0.00 rows=0 width=0)
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value
   DuckDB Execution Plan: 
 
 ┌───────────────────────────┐
 │    UNGROUPED_AGGREGATE    │
 │    ────────────────────   │
 │        Aggregates:        │
 │     count(DISTINCT #0)    │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         PROJECTION        │
 │    ────────────────────   │
 │             id            │
 │                           │
 │       ~11025408 Rows      │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         HASH_JOIN         │
 │    ────────────────────   │
 │      Join Type: INNER     │
 │    Conditions: id = id    ├──────────────┐
 │                           │              │
 │       ~11025408 Rows      │              │
 └─────────────┬─────────────┘              │
 ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
 │       POSTGRES_SCAN       ││         SEQ_SCAN          │
 │    ────────────────────   ││    ────────────────────   │
 │        Table: t_pg        ││          Table: t         │
 │      Projections: id      ││   Type: Sequential Scan   │
 │                           ││      Projections: id      │
 │                           ││                           │
 │       ~11025408 Rows      ││       ~10000000 Rows      │
 └───────────────────────────┘└───────────────────────────┘
 
 
(36 rows)
Time: 13.930 ms
postgres=# select count(distinct t.id) from t join t_pg on (t.id=t_pg.id);
  count   
----------
 10000000
(1 row)
Time: 83322.344 ms (01:23.322)

oss + pg_duckdb

DuckDB的优势除了AP性能强, 另外就是对接对象存储非常方便, 容易实现数据湖架构, 共享多个实例的数据、冷热分离存储等.

pg_duckdb手册中的例子

-- Session Token is Optional    
INSERT INTO duckdb.secrets    
(type, key_id, secret, session_token, region)    
VALUES ('S3', 'access_key_id', 'secret_access_key', 'session_token', 'us-east-1');       
    
    
COPY (SELECT user_id, item_id, price, purchased_at FROM purchases)    
TO 's3://your-bucket/purchases.parquet;    
    
    
SELECT SUM(price) AS total, item_id    
FROM read_parquet('s3://your-bucket/purchases.parquet')    
  AS (price float, item_id int)    
GROUP BY item_id    
ORDER BY total DESC    
LIMIT 100;

使用阿里云OSS的配置例子, duckdb secret语法:

D CREATE SECRET my_secret (      

     TYPE S3,      

     KEY_ID 'xxx',      

     SECRET 'xxx',      

     endpoint 's3.oss-cn-shanghai.aliyuncs.com'      

 );      

┌─────────┐      

│ Success │      

boolean │      

├─────────┤      

│ true    │      

└─────────┘      

     

D create table a(id int, info text);        

D insert into a select range, md5(random()::text) from range(1,1000000);      

D copy a to 's3://otpawu20240715105432/a.parquet';    

session token: https://help.aliyun.com/zh/oss/developer-reference/use-temporary-access-credentials-provided-by-sts-to-access-oss

除了使用PolarDB Table Access Method接口来获得DuckDB的能力, PolarDB PG v15 还能通过plpython, duckdb_fdw插件来获得DuckDB的能力, 请参考如下文章:

pg_mooncake

pg_duckdb 在PolarDB/PostgreSQL中只能建临时表, 略显小气, 不过使用pg_mooncake可以避免这个问题.

Only TEMP tables are supported in DuckDB if MotherDuck support is not enabled

截止20250214 pg_mooncake最新版本为0.1.2, 已经好用到爆, 后续的版本还将推出几个非常值得期待的特性, 让pg_mooncake列存储表更容易适配更多工作负载:

https://www.mooncake.dev/blog/pgmooncake-v02discussion

1、列存储表的完整Postgres表访问方法(TAM)

目前所有列存储查询都通过DuckDB执行。虽然这保证了性能,但意味着无法使用Postgres的某些特性,例如数据修改型CTE(如删除行、返回结果并插入到其他表)和触发器。

在v0.2版本中,我们将通过完整的Postgres表访问方法暴露列存储表,允许Postgres直接读写。DuckDB仍将作为快速执行引擎,但在需要时会无缝回退到Postgres执行。

在查询计划阶段,DuckDB将继续作为高效执行引擎,我们会尽可能将计算下推给它执行。无法通过DuckDB执行的算子将回退到Postgres处理。

2、逻辑复制到列存储表

用户通常在现有Postgres表遇到慢查询时采用pg_mooncake。通过逻辑复制功能,我们可以更轻松地加速分析查询——只需将数据从堆表复制到列存储表,无需复杂写入模式。

Postgres原生的逻辑复制接收器主要通过调用table_insert和table_insert_multi等高级函数实现,这些功能已通过我们的完整表访问方法(TAM)支持。初期阶段,pg_mooncake的逻辑复制将支持仅追加型工作负载。

该功能显著提升了pg_mooncake的部署灵活性,无论您的Postgres实例部署在何处。您可以在现有Postgres部署旁运行pg_mooncake,并将数据无缝从堆表复制到列存储表。

感觉特别合适时序表(物联网、应用日志、金融行业数据等), 这些表可以自动转换为列存表, 加速此类表的查询.

3、小批量插入的行存储缓冲区

逻辑复制带来一个挑战:如何在不为每个事务生成新Parquet文件和Delta Lake日志的情况下处理频繁的小批量插入?

v0.2版本中,小批量插入会暂存到行存储表,而大批量插入则直接写入Parquet文件。查询列存储表时,Postgres会同时扫描行存储和Parquet文件。当行存储数据达到阈值后,将批量刷写到Parquet。这种方法还将Parquet写入与事务解耦,显著降低写放大。

这是关键性突破,使得列存储表无需批量插入即可作为主存储方案——特别适用于日志记录和时间序列型工作负载。

DEMO

冲突问题 , 不能同时使用pg_duckdb v0.3.1 和 pg_mooncake v0.1.2

在进行测试之前, 先保存一下 tmp_polardb_pg_15_base/lib/postgresql/libduckdb.so , 因pg_mooncake接下来依赖的duckdb版本和前面pg_duckdb依赖的版本不同. 并且把pg_duckdb preload去掉.

# 禁用pg_duckdb
# 备份pg_duckdb依赖的 libduckdb.so  
cd ~/tmp_polardb_pg_15_base/lib/postgresql 
mv libduckdb.so bak_pg_duckdb_libduckdb.so  
 
# RW , RO , standby  
vi postgresql.conf
# shared_preload_libraries = '$libdir/polar_vfs,$libdir/polar_worker,$libdir/pg_duckdb'   
# 重启 polardb

安装pg_mooncake

cd /data  
git clone --depth 1 -b v0.1.2 https://github.com/Mooncake-Labs/pg_mooncake      
  
cd pg_mooncake/third_party      
git clone --depth 1 -b v1.1.3 https://github.com/duckdb/duckdb      
  
cd ..    
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh    
. "$HOME/.cargo/env"    
  
make release -j$(nproc)    
make install

创建插件

CREATE EXTENSION pg_mooncake;    
    
    
postgres=# \dA+  
                               List of access methods  
    Name     | Type  |       Handler        |              Description                 
-------------+-------+----------------------+----------------------------------------  
 brin        | Index | brinhandler          | block range index (BRIN) access method  
 btree       | Index | bthandler            | b-tree index access method  
 columnstore | Table | columnstore_handler  |   
 gin         | Index | ginhandler           | GIN index access method  
 gist        | Index | gisthandler          | GiST index access method  
 hash        | Index | hashhandler          | hash index access method  
 heap        | Table | heap_tableam_handler | heap table access method  
 spgist      | Index | spghandler           | SP-GiST index access method  
(8 rows)

创建列存表

CREATE TABLE user_activity(    
  user_id BIGINT,    
  activity_type TEXT,    
  activity_timestamp TIMESTAMP,    
  duration INT    
) USING columnstore;

写入数据

INSERT INTO user_activity VALUES    
  (1, 'login', '2024-01-01 08:00:00', 120),    
  (2, 'page_view', '2024-01-01 08:05:00', 30),    
  (3, 'logout', '2024-01-01 08:30:00', 60),    
  (4, 'error', '2024-01-01 08:13:00', 60);

查询

SELECT * from user_activity;

执行计划

postgres=# explain (verbose) select * from user_activity where user_id=1;  
                             QUERY PLAN                               
--------------------------------------------------------------------  
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)  
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value  
   DuckDB Execution Plan:   
   
 ┌───────────────────────────┐  
 │     COLUMNSTORE_SCAN      │  
 │    ────────────────────   │  
 │         Function:         │  
 │      COLUMNSTORE_SCAN     │  
 │                           │  
 │        Projections:       │  
 │          user_id          │  
 │       activity_type       │  
 │     activity_timestamp    │  
 │          duration         │  
 │                           │  
 │          Filters:         │  
 │  user_id=1 AND user_id IS │  
 │          NOT NULL         │  
 │                           │  
 │          ~1 Rows          │  
 └───────────────────────────┘

列存文件位置

postgres=# SELECT * FROM mooncake.columnstore_tables;  
  table_name   |                                        path                                           
---------------+-------------------------------------------------------------------------------------  
 user_activity | /home/postgres/primary/mooncake_local_tables/mooncake_postgres_user_activity_57420/  
(1 row)  
  
  
$ ll /home/postgres/primary/mooncake_local_tables/mooncake_postgres_user_activity_57420/  
total 16  
drwx------ 3 postgres postgres 4096 Feb 14 11:24 ./  
drwx------ 4 postgres postgres 4096 Feb 14 11:23 ../  
-rw------- 1 postgres postgres  696 Feb 14 11:24 c8d6bea5-f905-4913-8e8d-673e4ef00589.parquet  
drwx------ 2 postgres postgres 4096 Feb 14 11:24 _delta_log/

pg_mooncake, pg_duckdb这类插件最常见的存储是对象存储, 实现数据湖架构.

共享 NAS / NFS

使用PolarDB时, 为了让RW和RO节点都可以访问列存表, 我们可以考虑把mooncake的本地存储目录放到NAS或NFS中, 然后使用软链, 那么从RW写入的parquet文件, 在RO节点都可以查询.

查看RW节点本地实例数据目录 $PGDATA/~/primary 可以看到以下几个pg_mooncake相关目录:

mooncake_local_cache  
mooncake_local_tables  
duckdb_extensions

目前这几个目录不能配置路径, 所以使用软链来实现, 给mooncake提了一个issue, 希望未来通过guc可以配置这些路径.

停止PolarDB RW和RO节点

pg_ctl stop -m fast -D ~/primary/  
pg_ctl stop -m fast -D ~/replica1/

在RW节点把这些目录移动到宿主机共享给容器的目录中

mkdir /data/pgmooncake_local_data  
mv ~/primary/mooncake_local_cache /data/pgmooncake_local_data  
mv ~/primary/mooncake_local_tables /data/pgmooncake_local_data  
mv ~/primary/duckdb_extensions /data/pgmooncake_local_data  
  
# 建立软链  
ln -s /data/pgmooncake_local_data/mooncake_local_cache ~/primary/  
ln -s /data/pgmooncake_local_data/mooncake_local_tables ~/primary/  
ln -s /data/pgmooncake_local_data/duckdb_extensions ~/primary/

在RO节点建立软链

mkdir ~/primary  
ln -s /data/pgmooncake_local_data/mooncake_local_cache ~/primary/  
ln -s /data/pgmooncake_local_data/mooncake_local_tables ~/primary/  
ln -s /data/pgmooncake_local_data/duckdb_extensions ~/primary/

启动PolarDB RW和RO节点

pg_ctl start -D ~/primary/  
pg_ctl start -D ~/replica1/

在RW节点多弄点数据进去

$ psql  
psql (PostgreSQL 15.10 (PolarDB 15.10.3.0 build bbc102d8 debug) on aarch64-linux-gnu)  
Type "help" for help.  
  
postgres=# insert into user_activity select * from user_activity ;  
WARNING:  `/home/postgres/primary/duckdb_extensions` is not directory.  
INSERT 0 4  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 8  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 16  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 32  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 64  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 128  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 256  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 512  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 1024  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 2048  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 4096  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 8192  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 16384  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 32768  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 65536  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 131072  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 262144  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 524288  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 1048576  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 2097152  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 4194304  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 8388608  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 16777216  
postgres=# \timing  
Timing is on.  
postgres=# insert into user_activity select * from user_activity ;  
INSERT 0 67108864  
Time: 6584.372 ms (00:06.584)

体验速度

postgres=# select count(*) from user_activity ;  
   count     
-----------  
 134217728  
(1 row)  
  
Time: 123.214 ms  
postgres=# explain (verbose) select count(*) from user_activity ;  
                             QUERY PLAN                               
--------------------------------------------------------------------  
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)  
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value  
   DuckDB Execution Plan:   
   
 ┌───────────────────────────┐  
 │    UNGROUPED_AGGREGATE    │  
 │    ────────────────────   │  
 │        Aggregates:        │  
 │        count_star()       │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │     COLUMNSTORE_SCAN      │  
 │    ────────────────────   │  
 │         Function:         │  
 │      COLUMNSTORE_SCAN     │  
 │                           │  
 │         ~104 Rows         │  
 └───────────────────────────┘  
   
   
(20 rows)  
  
Time: 15.596 ms

在RO节点也可以查询该列存表

$ psql  
psql (PostgreSQL 15.10 (PolarDB 15.10.3.0 build bbc102d8 debug) on aarch64-linux-gnu)  
Type "help" for help.  
  
postgres=# explain (verbose) select count(*) from user_activity ;  
                             QUERY PLAN                               
--------------------------------------------------------------------  
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)  
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value  
   DuckDB Execution Plan:   
   
 ┌───────────────────────────┐  
 │    UNGROUPED_AGGREGATE    │  
 │    ────────────────────   │  
 │        Aggregates:        │  
 │        count_star()       │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │     COLUMNSTORE_SCAN      │  
 │    ────────────────────   │  
 │         Function:         │  
 │      COLUMNSTORE_SCAN     │  
 │                           │  
 │         ~104 Rows         │  
 └───────────────────────────┘  
   
   
(20 rows)  
  
postgres=# \timing  
Timing is on.  
postgres=# select count(*) from user_activity ;  
   count     
-----------  
 134217728  
(1 row)  
  
Time: 124.732 ms  
postgres=# select count(distinct user_id) from user_activity ;  
 count   
-------  
     4  
(1 row)  
  
Time: 460.696 ms  
postgres=# explain verbose select count(distinct user_id) from user_activity ;  
                             QUERY PLAN                               
--------------------------------------------------------------------  
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)  
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value  
   DuckDB Execution Plan:   
   
 ┌───────────────────────────┐  
 │    UNGROUPED_AGGREGATE    │  
 │    ────────────────────   │  
 │        Aggregates:        │  
 │     count(DISTINCT #0)    │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │         PROJECTION        │  
 │    ────────────────────   │  
 │          user_id          │  
 │                           │  
 │         ~104 Rows         │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │     COLUMNSTORE_SCAN      │  
 │    ────────────────────   │  
 │         Function:         │  
 │      COLUMNSTORE_SCAN     │  
 │                           │  
 │    Projections: user_id   │  
 │                           │  
 │         ~104 Rows         │  
 └───────────────────────────┘  
   
   
(29 rows)  
  
Time: 11.540 ms

本地表和列存表的JOIN

postgres=# create table localtbl(id int8, info text);  
CREATE TABLE  
postgres=# insert into localtbl select generate_series(1,1000), md5(random()::text);  
INSERT 0 1000  
  
postgres=# explain select count(distinct t2.user_id) from localtbl t1 join user_activity t2 on (t1.id=t2.user_id);  
                             QUERY PLAN                               
--------------------------------------------------------------------  
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)  
   DuckDB Execution Plan:   
   
 ┌───────────────────────────┐  
 │    UNGROUPED_AGGREGATE    │  
 │    ────────────────────   │  
 │        Aggregates:        │  
 │     count(DISTINCT #0)    │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │         PROJECTION        │  
 │    ────────────────────   │  
 │          user_id          │  
 │                           │  
 │         ~1000 Rows        │  
 └─────────────┬─────────────┘  
 ┌─────────────┴─────────────┐  
 │         HASH_JOIN         │  
 │    ────────────────────   │  
 │      Join Type: INNER     │  
 │                           │  
 │        Conditions:        ├──────────────┐  
 │        id = user_id       │              │  
 │                           │              │  
 │         ~1000 Rows        │              │  
 └─────────────┬─────────────┘              │  
 ┌─────────────┴─────────────┐┌─────────────┴─────────────┐  
 │     POSTGRES_SEQ_SCAN     ││     COLUMNSTORE_SCAN      │  
 │    ────────────────────   ││    ────────────────────   │  
 │         Function:         ││         Function:         │  
 │     POSTGRES_SEQ_SCAN     ││      COLUMNSTORE_SCAN     │  
 │                           ││                           │  
 │      Projections: id      ││    Projections: user_id   │  
 │                           ││                           │  
 │         ~1000 Rows        ││         ~104 Rows         │  
 └───────────────────────────┘└───────────────────────────┘  
   
   
(38 rows)  
  
Time: 25.376 ms  
  
  
postgres=# select count(distinct t2.user_id) from localtbl t1 join user_activity t2 on (t1.id=t2.user_id);  
 count   
-------  
     4  
(1 row)  
  
Time: 95722.228 ms (01:35.722)

parquet和polardb本地表JOIN还有优化空间.

但是 mooncake tbl1 join mooncake tbl2 , 也很慢, 是怎么回事? 发了issue给pg_mooncake后已得到快速的回复, 报给duckdb优化器的评估行数不准, 导致使用了大表作为hashtable. 感谢pg_mooncake开源项目, 应该会很快解决. issue: Mooncake-Labs/pg_mooncake#124

create table moontbl(id int8, info text) USING columnstore ;
insert into  moontbl select x, md5(random()::text) from generate_series(1,1000) x; 
explain verbose select count(distinct t2.user_id) from moontbl t1 join user_activity t2 on (t1.id=t2.user_id);
                             QUERY PLAN                             
--------------------------------------------------------------------
 Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)
   Output: duckdb_scan.explain_key, duckdb_scan.explain_value
   DuckDB Execution Plan: 
 
 ┌───────────────────────────┐
 │    UNGROUPED_AGGREGATE    │
 │    ────────────────────   │
 │        Aggregates:        │
 │     count(DISTINCT #0)    │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         PROJECTION        │
 │    ────────────────────   │
 │          user_id          │
 │                           │
 │         ~1000 Rows        │
 └─────────────┬─────────────┘
 ┌─────────────┴─────────────┐
 │         HASH_JOIN         │
 │    ────────────────────   │
 │      Join Type: INNER     │
 │                           │
 │        Conditions:        ├──────────────┐
 │        id = user_id       │              │
 │                           │              │
 │         ~1000 Rows        │              │
 └─────────────┬─────────────┘              │
 ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
 │     COLUMNSTORE_SCAN      ││     COLUMNSTORE_SCAN      │
 │    ────────────────────   ││    ────────────────────   │
 │         Function:         ││         Function:         │
 │      COLUMNSTORE_SCAN     ││      COLUMNSTORE_SCAN     │
 │                           ││                           │
 │      Projections: id      ││    Projections: user_id   │
 │                           ││                           │
 │         ~1000 Rows        ││         ~108 Rows         │
 └───────────────────────────┘└───────────────────────────┘
 
 
(39 rows)
// mooncake table join mooncake table also slow!
select count(distinct t2.user_id) from moontbl t1 join user_activity t2 on (t1.id=t2.user_id);

pg_duckdb和pg_mooncake切换使用

pg_duckdb和pg_mooncake切换使用, ( 因重启时会加载依赖libduckdb, 只能有1个extension被创建, 如果要使用另一个, 先drop 相对的extension. )

# 备份pg_mooncake依赖的 libduckdb.so  
cd ~/tmp_polardb_pg_15_base/lib/postgresql   
mv libduckdb.so bak_pg_mooncake_libduckdb.so

使用pg_duckdb

# 使用pg_duckdb
cd ~/tmp_polardb_pg_15_base/lib/postgresql
rm -f libduckdb.so 
ln -s bak_pg_duckdb_libduckdb.so libduckdb.so
# RW , RO , standby  
vi postgresql.conf
shared_preload_libraries = '$libdir/polar_vfs,$libdir/polar_worker,$libdir/pg_duckdb'   
# 重启 polardb

使用pg_mooncake

# 使用pg_mooncake
cd ~/tmp_polardb_pg_15_base/lib/postgresql
rm -f libduckdb.so  
ln -s bak_pg_mooncake_libduckdb.so libduckdb.so  
# RW , RO , standby  
vi postgresql.conf
# shared_preload_libraries = '$libdir/polar_vfs,$libdir/polar_worker,$libdir/pg_duckdb'   
# 重启 polardb

参考

《穷鬼玩PolarDB RAC一写多读集群系列 | 在Docker容器中用loop设备模拟共享存储》

《穷鬼玩PolarDB RAC一写多读集群系列 | 如何搭建PolarDB容灾(Standby)节点》

《穷鬼玩PolarDB RAC一写多读集群系列 | 共享存储在线扩容》

《穷鬼玩PolarDB RAC一写多读集群系列 | 计算节点 Switchover》

《穷鬼玩PolarDB RAC一写多读集群系列 | 在线备份》

《穷鬼玩PolarDB RAC一写多读集群系列 | 在线归档》

《穷鬼玩PolarDB RAC一写多读集群系列 | 实时归档》

《穷鬼玩PolarDB RAC一写多读集群系列 | 时间点恢复(PITR)》

《穷鬼玩PolarDB RAC一写多读集群系列 | 读写分离》

《穷鬼玩PolarDB RAC一写多读集群系列 | 主机全毁, 只剩共享存储的PolarDB还有救吗?》

《穷鬼玩PolarDB RAC一写多读集群系列 | 激活容灾(Standby)节点》

《穷鬼玩PolarDB RAC一写多读集群系列 | 将“共享存储实例”转换为“本地存储实例”》

《穷鬼玩PolarDB RAC一写多读集群系列 | 将“本地存储实例”转换为“共享存储实例”》

《穷鬼玩PolarDB RAC一写多读集群系列 | 升级vector插件》

《穷鬼玩PolarDB RAC一写多读集群系列 | 使用图数据库插件AGE》

《穷鬼玩PolarDB RAC一写多读集群系列 | 接入私有化大模型服务》

《穷鬼玩PolarDB RAC一写多读集群系列 | 接入PostGIS插件全功能》

《猛料! 月饼(pgmooncake)进化了, 超过 pg_duckdb》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
打赏
0
1
1
0
20690
分享
相关文章
喜报|PolarDB开源社区荣获“2024数据库国内活跃开源项目”奖
喜报|PolarDB开源社区荣获“2024数据库国内活跃开源项目”奖
PolarDB开源数据库进阶课18 通过pg_bulkload适配pfs实现批量导入提速
本文介绍了如何修改 `pg_bulkload` 工具以适配 PolarDB 的 PFS(Polar File System),从而加速批量导入数据。实验环境依赖于 Docker 容器中的 loop 设备模拟共享存储。通过对 `writer_direct.c` 文件的修改,替换了一些标准文件操作接口为 PFS 对应接口,实现了对 PolarDB 15 版本的支持。测试结果显示,使用 `pg_bulkload` 导入 1000 万条数据的速度是 COPY 命令的三倍多。此外,文章还提供了详细的步骤和代码示例,帮助读者理解和实践这一过程。
48 0
docker拉取MySQL后数据库连接失败解决方案
通过以上方法,可以解决Docker中拉取MySQL镜像后数据库连接失败的常见问题。关键步骤包括确保容器正确启动、配置正确的环境变量、合理设置网络和权限,以及检查主机防火墙设置等。通过逐步排查,可以快速定位并解决连接问题,确保MySQL服务的正常使用。
116 82
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
205 42
MySQL生产环境迁移至YashanDB数据库深度体验
这篇文章是作者将 MySQL 生产环境迁移至 YashanDB 数据库的深度体验。介绍了 YashanDB 迁移平台 YMP 的产品相关信息、安装步骤、迁移中遇到的各种兼容问题及解决方案,最后总结了迁移体验,包括工具部署和操作特点,也指出功能有优化空间及暂不支持的部分,期待其不断优化。

相关产品

  • 云原生数据库 PolarDB