基于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上跨节点数据库查询主要可以通过两个组件来完成。

  1. foreign data wrapper(fdw)
  2. 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 7002Password for user pguo: <输入:auth_token>postgres=# RETRIEVE 2 FROM ENDPOINT c100000e02; <- 新的语法获取数据,类似于FETCH。a---23(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,敬请期待。

关注微信公众号

VMware 中国研发中心

Greenplum官方技术交流群

扫码添加小助手即可入群,添加时请备注 “GP网站”