基于fdw的跨Greenplum集群数据库查询实现
《Greenplum走进全国》系列技术研讨会在告别西安后,于7月3日,携原厂、社区和合作伙伴的讲师们走进山东济南。活动中,我们与当地的Greenplumer进行了深入的交流,并带来了四个精彩主题演讲。为了能让更多社区的小伙伴学习相关内容,我们将陆续把主题演讲整理成文章,欢迎关注!
今天和大家分享的主题是《基于fdw的跨Greenplum集群数据库查询实现》。其实这个标题并不100%精确,今天分享的内容并不只局限于跨集群的查询,还适用于跨数据库的查询场景。实际环境中用户常常会有跨Greenplum集群或者跨数据库的查询需求,Greenplum的新版本很快将会支持这项功能。通过这个功能,用户可以无缝透明地跨数据库或者跨集群运行SQL查询。这项功能基于postgres_fdw技术以及GP独有的segment级别并行cursor技术。本话题将介绍该功能相关的技术实现。
这一功能的需求主要来自工业界关于数据库联邦的需求以及Greenplum用户的通用需求。
Postgres跨节点数据库查询
在介绍Greenplum是如何做到跨集群查询之前,我们先来看看Postgres (PG)是如何做到跨节点数据库查询的。众所周知Greenplum是基于PostgreSQL的MPP数据库,做了大量的优化和增强来满足用户的需求。PostgreSQL上跨节点数据库查询主要可以通过两个组件来完成。
- foreign data wrapper(fdw)
- dblink
这两个组件,fdw相对dblink使用更透明,语法更加标准,性能更好。此外,PG社区一直在对fdw做优化,每个PG版本性能都有所提高或者提供了更多的功能。其中postgres_fdw模块是用来连接postgres与postgres的fdw模块。postgres_fdw代码在Postgres代码仓库中,所以它的功能一直和PG核心代码一样在保持演进。
典型的postgres_fdw使用方法如下,
- create extension postgres_fdw; -- 装载postgres_fdw模块
- CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');
- CREATE USER MAPPING FOR bob SERVER myserver OPTIONS (user 'bob', password 'secret');
- CREATE FOREIGN TABLE foreign_table (id integer NOT NULL, data text)SERVER myserver;
- 或者 IMPORT FOREIGN SCHEMA
- 如何使用postgres_fdw?和正常表一样操作(读甚至写)foreign table。
Postgres社区在持续不断提高fdw的功能和性能。下图是PostgreSQL的不同版本所做的fdw相关的增强。
(来源于http://www.interdb.jp/pg/pgsql04.html)
Postgres 13和今年下半年要发布的Postgres 14做了更多fdw相关的功能或性能增强。其中PostgreSQL 13中所做的功能包括Passwordless,ssl认证等,具体可以参考链接:
https://www.percona.com/blog/2020/09/30/postgresql_fdw-authentication-changes-in-postgresql-13/
PostgreSQL 14中所做的功能包括parallel Foreign scan, bulk insert, truncate等,具体可以参考链接
https://www.percona.com/blog/2021/06/02/postgres_fdw-enhancement-in-postgresql-14/
那么在PostgreSQL跨节点数据库查询中,postgres_fdw如何读取呢?
首先Foreign Scan执行器节点代码会
- 创建一个Cursor来读取远端的表
- 调用Fetch语句返回上层执行器tuples
Postgres_fdw代码提供各种fdw接口的callback,比如用于Scan操作的:
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
routine->ReScanForeignScan = postgresReScanForeignScan;
routine->EndForeignScan = postgresEndForeignScan;
fdw提供丰富的接口函数以满足多种fdw需求。
跨Greenplum集群cluster查询
讲完PostgreSQL跨节点数据库查询,我们现在来看看跨Greenplum集群查询是怎么做的。
Greenplum集群的查询还是借助于postgres_fdw模块。理论上使用QD(coordinator或者master上后端进程)到QD的postgres_fdw其实就可以做了,但是走coordinator通常会很慢。最好的办法是根据查询计划,本地segments通过各自的postgres_fdw读取远端的segments的数据,但是注意不要求N:N的本地和远端segment数量一样,要允许异构。
在设计跨Greenplum集群cluster查询时,
- 首先我们设计了新的cursor类型语法:Parallel Retrieve Cursor
- 修改了postgres_fdw,支持和使用这种cursor
- 本地segments通过各自的cursor连接远端一个segment来做fdw的查询。
那么为什么我们需要parallel retrieve cursor,而不用local的cursor呢?主要原因是可以在coordinator上统一认证、统一管理、全局事务。要注意的一点是:Parallel Retrieve Cursor可以不局限于跨Greenplum查询使用,这个功能有很多的使用想象空间;
接下来,我们来看一个Parallel Retrieve Cursor使用的demo。
首先我们启动一个到Greenplum集群的链接,然后运行下面的SQL语句
# BEGIN;
# DECLARE c1 PARALLEL RETRIEVE CURSOR FOR SELECT * FROM t1;
# SELECT * FROM gp_endpoints() WHERE cursorname='c1';
gp_segment_id | auth_token | cursorname | sessionid | hostname | port | userid | state | endpointname
---------------+----------------------------------+------------+-----------+----------+------+--------+-------+--------------
0 | a612d36ac7bfb6b4a1f147cfd283dea2 | c1 | 3586 | host67 | 7002 | 10 | READY | c100000e02
1 | d39e6b62200a9e19ba5ec21ac1fc8cab | c1 | 3586 | host67 | 7003 | 10 | READY | c100000e02
2 | a4e6fcd468039e77f91e62578deb40b9 | c1 | 3586 | host67 | 7004 | 10 | READY | c100000e02
(3 rows)
Endpoint代表这个cursor在某个执行节点的内部单元(根据计划,endpoint可能在某个节点或者多个节点)。
接着我们启动retrieve类型连接到某个segment读取数据。
$ PGOPTIONS='-c gp_retrieve_conn=true' psql -p 7002
Password for user pguo: <输入:auth_token>
postgres=# RETRIEVE 2 FROM ENDPOINT c100000e02; <- 新的语法获取数据,类似于FETCH。
a
---
2
3
(2 rows)
注意:Parallel Retrieve Cursor只允许RETRIEVE语句来获取数据,RETRIEVE语句也只可以在gp_retrieve_conn=true的连接上使用,这种连接也只允许RETRIEVE语句,暗含了utility模式。
那么我们为什么不在RETRIEVE语句中使用cursor名字呢?这是因为要防止不同sesson的parallel retrieve cursor名字重名冲突。那么我们为什么需要指定endpoint呢?这是因为一个session用户可以declare多个cursor。
在使用Parrallel Retrieve Cursor的过程中要注重安全性的问题,我们没有提及太多细节,但是内部代码做了不少考虑。
我们也提供一些别的相关的UDF函数:
gp_check_parallel_retrieve_cursor(cursorname) - 返回是否所有数据获取完毕
gp_wait_parallel_retrieve_cursor(cursorname - 等候直到所有数据获取完毕
gp_segment_endpoints() - 某个segment上endpoint信息
Parallel Retrieve Cursor的技术实现类似于普通Cursor,也是类似Greenplum生成Plan,但是区别是不需要一个最顶层的gather motion节点,gang的管理也类似,QD到QE链接,cursor语句单独的reader gang。
Retrieve链接后端和QE的cursor后端进程通过PG的shm_mq (shared memory queue)来通信数据,shm_mq也用于PG的Parallel Scanning处理。具体来说,是通过shm_mq创建的Dest Receiver,使用Endpoint内部数据结构关联。这里的一些代码逻辑处理需要谨慎。
那我们是如何做到跨Greenplum集群查询呢?
- 本地GP集群QD节点:Foreign Scan执行器节点预先创建相应的Parallel Retrieve Cursor并记录相应信息到plan中(比如登录token等),这些信息通过gp_endpoints() UDF来获取,实现是调用BeginForeignScan()接口
- 本地GP集群的QE节点,在远端segment节点分别建立retrieve连接,实现也是调用BeginForeignScan()接口
- QE节点读取数据(通过RETRIEVE语句批次读取数据)
调用IterateForeignScan()接口返回上层执行器节点tuple。
今天介绍的相关功能计划于Greenplum 6和以上版本进行实现。Parallel Retrieve Cursor部分基本完成,主要是稳定性等余下工作。Fdw方面工作(local cluster)相对简单,已近完成,主要是联调和测试。目前的开发是现在基于master分支代码,会Backport到Greenplum 6,敬请期待。
文章转自公众号:Greenplum中文社区