Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

发布于:2020 年 5 月 10 日 10:00

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

本文经授权转载自公众号 PostgreSQL 中文社区,主要介绍了 Greenplum 集群概述、分布式数据存储和分布式查询优化。

1. 分布式执行器

现在有了分布式数据存储机制,也生成了分布式查询计划,下一步是如何在集群里执行分布式计划,最终返回结果给用户。

Greenplum 执行器相关概念

先看一个 SQL 例子及其计划:

复制代码
test=# CREATE TABLE students (id int, name text) DISTRIBUTED BY (id);
test=# CREATE TABLE classes(id int, classname text, student_id int) DISTRIBUTED BY (id);
test=# INSERT INTO students VALUES (1, 'steven'), (2, 'changchang'), (3, 'guoguo');
test=# INSERT INTO classes VALUES (1, 'math', 1), (2, 'math', 2), (3, 'physics', 3);
test=# explain SELECT s.name student_name, c.classname
test-# FROM students s, classes c
test-# WHERE s.id=c.student_id;
QUERY PLAN
----------------------------------------------------------------------------------------------
-
Gather Motion 2:1 (slice2; segments: 2) (cost=2.07..4.21 rows=4 width=14)
-> Hash Join (cost=2.07..4.21 rows=2 width=14)
Hash Cond: c.student_id = s.id
-> Redistribute Motion 2:2 (slice1; segments: 2) (cost=0.00..2.09 rows=2 width=10)
Hash Key: c.student_id
-> Seq Scan on classes c (cost=0.00..2.03 rows=2 width=10)
-> Hash (cost=2.03..2.03 rows=2 width=12)
-> Seq Scan on students s (cost=0.00..2.03 rows=2 width=12)
Optimizer status: legacy query optimizer

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

这个图展示了上面例子中的 SQL 在 2 个 segment 的 Greenplum 集群中执行时的示意图。

QD(Query Dispatcher、查询调度器):Master 节点上负责处理用户查询请求的进程称为 QD(PostgreSQL 中称之为 Backend 进程)。 QD 收到用户发来的 SQL 请求后,进行解析、重写和优化,将优化后的并行计划分发给每个 segment 上执行,并将最终结果返回给用户。此外还负责整个 SQL 语句涉及到的所有的 QE 进程间的通讯控制和协调,譬如某个 QE 执行时出现错误时,QD 负责收集错误详细信息,并取消所有其他 QEs;如果 LIMIT n 语句已经满足,则中止所有 QE 的执行等。QD 的入口是 exec_simple_query()。

QE(Query Executor、查询执行器):Segment 上负责执行 QD 分发来的查询任务的进程称为 QE。Segment 实例运行的也是一个 PostgreSQL,所以对于 QE 而言,QD 是一个 PostgreSQL 的客户端,它们之间通过 PostgreSQL 标准的 libpq 协议进行通讯。对于 QD 而言,QE 是负责执行其查询请求的 PostgreSQL Backend 进程。通常 QE 执行整个查询的一部分(称为 Slice)。QE 的入口是 exec_mpp_query()。

Slice:为了提高查询执行并行度和效率,Greenplum 把一个完整的分布式查询计划从下到上分成多个 Slice,每个 Slice 负责计划的一部分。划分 slice 的边界为 Motion,每遇到 Motion 则一刀将 Motion 切成发送方和接收方,得到两颗子树。每个 slice 由一个 QE 进程处理。上面例子中一共有三个 slice。

Gang:在不同 segments 上执行同一个 slice 的所有 QEs 进程称为 Gang。上例中有两组 Gang,第一组 Gang 负责在 2 个 segments 上分别对表 classes 顺序扫描,并把结果数据重分布发送给第二组 Gang;第二组 Gang 在 2 个 segments 上分别对表 students 顺序扫描,与第一组 Gang 发送到本 segment 的 classes 数据进行哈希关联,并将最终结果发送给 Master。

并行执行流程

下图展示了查询在 Greenplum 集群中并行执行的流程。该图假设有 2 个 segments,查询计划有两个 slices,一共有 4 个 QEs,它们之间通过网络进行通讯。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

QD 和 QE 都是 PostgreSQL backend 进程,其执行逻辑非常相似。对于数据操作(DML)语句(数据定义语句的执行逻辑更简单),其核心执行逻辑由 ExecutorStart, ExecutorRun, ExecutorEnd 实现。

QD:

  • ExecutorStart 负责执行器的初始化和启动。Greenplum 通过 CdbDispatchPlan 把完整的查询计划发送给每个 Gang 中的每个 QE 进程。Greenplum 有两种发送计划给 QE 的方式:1)异步方式,使用 libpq 的异步 API 以非阻塞方式发送查询计划给 QE;2)同步多线程方式:使用 libpq 的同步 API,使用多个线程同时发送查询计划给 QE。GUC gp_connections_per_thread 控制使用线程数量,缺省值为 0,表示采用异步方式。 Greenplum 从 6.0 开始去掉了异步方式。
  • ExecutorRun 启动执行器,执行查询树中每个算子的代码,并以火山模型(volcano)风格返回结果元组给客户端。在 QD 上,ExecutorRun 调用 ExecutePlan 处理查询树,该查询树的最下端的节点是一个 Motion 算子。其对应的函数为 ExecMotion,该函数等待来自于各个 QE 的结果。QD 获得来自于 QE 的元组后,执行某些必要操作(譬如排序)然后返回给最终用户。
  • ExecutorEnd 负责执行器的清理工作,包括检查结果,关闭 interconnect 连接等。

QE 上的 ExecutorStart/ExecutorRun/ExecutorEnd 函数和单节点的 PostgreSQL 代码逻辑类似。主要的区别在 QE 执行的是 Greenplum 分布式计划中的一个 slice,因而其查询树的根节点一定是个 Motion 节点。其对应的执行函数为 ExecMotion,该算子从查询树下部获得元组,并根据 Motion 的类型发送给不同的接收方。低一级的 Gang 的 QE 把 Motion 节点的结果元组发送给上一级 Gang 的 QE,最顶层 Gang 的 QE 的 Motion 会把结果元组发送给 QD。Motion 的 Flow 类型确定了数据传输的方式,有两种:广播和重分布。广播方式将数据发送给上一级 Gang 的每一个 QE;重分布方式将数据根据重分布键计算其对应的 QE 处理节点,并发送给该 QE。

QD 和 QE 之间有两种类型的网络连接:

  • libpq:QD 通过 libpq 与各个 QE 间传输控制信息,包括发送查询计划、收集错误信息、处理取消操作等。libpq 是 PostgreSQL 的标准协议,Greenplum 对该协议进行了增强,譬如新增了‘M’消息类型(QD 使用该消息发送查询计划给 QE)。libpq 是基于 TCP 的。
  • interconnect:QD 和 QE、QE 和 QE 之间的表元组数据传输通过 interconnect 实现。 Greenplum 有两种 interconnect 实现方式,一种基于 TCP,一种基于 UDP。缺省方式为 UDP interconnect 连接方式。

Direct Dispatch 优化

有一类特殊的 SQL,执行时只需要单个 segment 执行即可。譬如主键查询:SELECT * FROM tbl WHERE id = 1;

为了提高资源利用率和效率,Greenplum 对这类 SQL 进行了专门的优化,称为 Direct Dispatch 优化:生成查询计划阶段,优化器根据分布键和 WHERE 子句的条件,判断查询计划是否为 Direct Dispatch 类型查询;在执行阶段,如果计划是 Direct Dispatch,QD 则只会把计划发送给需要执行该计划的单个 segment 执行,而不是发送给所有的 segments 执行。

2. 分布式事务

Greenplum 使用两阶段提交(2PC)协议实现分布式事务。2PC 是数据库经典算法,此处不再赘述。本节概要介绍两个 Greenplum 分布式事务的实现细节:

  • 分布式事务快照:实现 master 和不同 segment 间一致性
  • 共享本地快照:实现 segment 内不同 QEs 间的一致性

分布式快照

在分布式环境下,SQL 在不同节点上的执行顺序可能不同。譬如下面例子中 segment1 首先执行 SQL1,然后执行 SQL2,所以新插入的数据对 SQL1 不可见;而 segment2 上先执行 SQL2 后执行 SQL1,因而 SQL1 可以看到新插入的数据。这就造成了数据的不一致。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

Greenplum 使用分布式快照和本地映射实现跨节点的数据一致性。Greenplum QD 进程承担分布式事务管理器的角色,在 QD 开始一个新的事务(StartTransaction)时,它会创建一个新的分布式事务 id、设置时间戳及相应的状态信息;在获取快照(GetSnapshotData)时,QD 创建分布式快照并保存在当前快照中。和单节点的快照类似,分布式快照记录了 xmin/xmax/xip 等信息,结构体如下所示:

复制代码
typedef struct DistributedSnapshot
{
DistributedTransactionTimeStamp distribTransactionTimeStamp;
DistributedTransactionId xminAllDistributedSnapshots;
DistributedSnapshotId distribSnapshotId;
DistributedTransactionId xmin; /* XID < xmin 则可见 */
DistributedTransactionId xmax; /* XID >= xmax 则不可见 */
int32 count; /* inProgressXidArray 数组中分布式事务的个数 */
int32 maxCount;
/* 正在执行的分布式事务数组 */
DistributedTransactionId *inProgressXidArray;
} DistributedSnapshot;

执行查询时,QD 将分布式事务和快照等信息序列化,通过 libpq 协议发送给 QE。QE 反序列化后,获得 QD 的分布式事务和快照信息。这些信息被用于确定元组的可见性(HeapTupleSatisfiesMVCC)。所有参与查询的 QEs 都使用 QD 发送的同一份分布式事务和快照信息判断元组的可见性,因而保证了整个集群数据的一致性,避免前面例子中出现的不一致现象。

在 QE 上判断一个元组对某个快照的可见性流程如下:

  • 如果创建元组的事务:xid (即元组头中的 xmin 字段)还没有提交,则不需要使用分布式事务和快照信息;
  • 否则判断创建元组的事务 xid 对快照是否可见
  • 首先根据分布式快照信息判断。根据创建元组的 xid 从分布式事务提交日志中找到其对应的分布式事务:distribXid,然后判断 distribXid 对分布式快照是否可见:
    • 如果 distribXid < distribSnapshot->xmin,则元组可见
    • 如果 distribXid > distribSnapshot->xmax,则元组不可见
    • 如果 distribSnapshot->inProgressXidArray 包含 distribXid,则元组不可见
    • 否则元组可见
  • 如果不能根据分布式快照判断可见性,或者不需要根据分布式快照判断可见性,则使用本地快照信息判断,这个逻辑和 PostgreSQL 的判断可见性逻辑一样。

和 PostgreSQL 的提交日志 clog 类似,Greenplum 需要保存全局事务的提交日志,以判断某个事务是否已经提交。这些信息保存在共享内存中并持久化存储在 distributedlog 目录下。

为了提高判断本地 xid 可见性的效率,避免每次访问全局事务提交日志,Greenplum 引入了本地事务 - 分布式事务提交缓存,如下图所示。每个 QE 都维护了这样一个缓存,通过该缓存,可以快速查到本地 xid 对应的全局事务 distribXid 信息,进而根据全局快照判断可见性,避免频繁访问共享内存或磁盘。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

共享本地快照(Shared Local Snapshot)

Greenplum 中一个 SQL 查询计划可能含有多个 slices,每个 Slice 对应一个 QE 进程。任一 segment 上,同一会话(处理同一个用户 SQL)的不同 QE 必须有相同的可见性。然而每个 QE 进程都是独立的 PostgreSQL backend 进程,它们之间互相不知道对方的存在,因而其事务和快照信息都是不一样的。如下图所示。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

为了保证跨 slice 可见性的一致性,Greenplum 引入了“共享本地快照 (Shared Local Snapshot)”的概念。每个 segment 上的执行同一个 SQL 的不同 QEs 通过共享内存数据结构 SharedSnapshotSlot 共享会话和事务信息。这些进程称为 SegMate 进程组。

Greenplum 把 SegMate 进程组中的 QE 分为 QE writer 和 QE reader。QE writer 有且只有一个,QE reader 可以没有或者多个。QE writer 可以修改数据库状态;QE reader 不能修改数据库的状态,且需要使用和 QE writer 一样的快照信息以保持与 QE writer 一致的可见性。如下图所示。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

“共享”意味着该快照在 QE writer 和 readers 间共享,“本地”意味着这个快照是 segment 的本地快照,同一用户会话在不同的 segment 上可以有不同的快照。segment 的共享内存中有一个区域存储共享快照,该区域被分成很多槽(slots)。一个 SegMate 进程组对应一个槽,通过唯一的会话 id 标志。一个 segment 可能有多个 SegMate 进程组,每个进程组对应一个用户的会话,如下图所示。

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (下篇)

QE Writer 创建本地事务后,在共享内存中获得一个 SharedLocalSnapshot 槽,并它自己的本地事务和快照信息拷贝到共享内存槽中,SegMate 进程组中的其他 QE Reader 从该共享内存中获得事务和快照信息。Reader QEs 会等待 Writer QE 直到 Writer 设置好共享本地快照信息。

只有 QE writer 参与全局事务,也只有该 QE 需要处理 commit/abort 等事务命令。

3. 数据洗牌(Shuffle)

相邻 Gang 之间的数据传输称为数据洗牌(Data Shuffling)。数据洗牌和 Slice 的层次相吻合,从下到上一层一层通过网络进行数据传输,不能跨层传输数据。根据 Motion 类型的不同有不同的实现方式,譬如广播和重分布。

Greenplum 实现数据洗牌的技术称为 interconnect,它为 QEs 提供高速并行的数据传输服务,不需要磁盘 IO 操作,是 Greenplum 实现高性能查询执行的重要技术之一。interconnect 只用来传输数据(表单的元组),调度、控制和错误处理等信息通过 QD 和 QE 之间的 libpq 连接传输。

Interconnect 有 TCP 和 UDP 两种实现方式,TCP interconnect 在大规模集群中会占用大量端口资源,因而扩展性较低。Greenplum 默认使用 UDP 方式。UDP interconnect 支持流量控制、网络包重发和确认等特性。

4. 分布式集群管理

分布式集群包含多个物理节点,少则四五台,多则数百台。管理如此多机器的复杂度远远大于单个 PostgreSQL 数据库。为了简化数据库集群的管理, Greenplum 提供了大量的工具。下面列出一些常用的工具,关于更多工具的信息可以参考 Greenplum 数据库管理员官方文档。

  • gpactivatestandby:激活 standby master,使之成为 Greenplum 数据库集群的主 master。
  • gpaddmirrors:为 Greenplum 集群添加镜像节点,以提高高可用性
  • gpcheckcat:检查 Greenplum 数据库的系统表,用以辅助故障分析。
  • gpcheckperf:检查 Greenplum 集群的系统性能,包括磁盘、网络和内存的性能。
  • gpconfig:为 Greenplum 集群中的所有节点进行参数配置。
  • gpdeletesystem:删除整个 Greenplum 集群
  • gpexpand:添加新机器到 Greenplum 集群中,用以扩容。
  • gpfdist:Greenplum 的文件分发服务器,是 Greenplum 数据加载和卸载的最主要工具。 gpfdist 充分利用并行处理,性能非常高。
  • gpload:封装 gpfdist 和外部表等信息,通过配置 YAML 文件,可以方便的加载数据到 Greenplum 数据库中。支持 INSERT、UPDATE 和 MERGE 三种模式。
  • gpinitstandby:为 Greenplum 集群初始化 standby master
  • gpinitsystem:初始化 Greenplum 集群
  • gppkg:Greenplum 提供的软件包管理工具,可以方便的在所有节点上安装 Greenplum 软件包,譬如 PostGIS、PLR 等。
  • gprecoverseg:恢复出现故障的主 segment 节点或者镜像 segment 节点
  • gpssh/gpscp:标准的 ssh/scp 只能针对一个目标机器进行远程命令执行和文件拷贝操作。gpssh 可以同时在一组机器上执行同一个命令;gpscp 同时拷贝一个文件或者目录到多个目标机器上。很多 Greenplum 命令行工具都使用这两个工具实现集群并行命令执行。
  • gpstart:启动一个 Greenplum 集群。
  • gpstop:停止一个 Greenplum 集群
  • gpstate:显示 Greenplum 集群的状态
  • gpcopy:将一个 Greenplum 数据库的数据迁移到另一个 Greenplum 数据库中。
  • gp_dump/gp_restore:Greenplum 数据备份恢复工具。从 Greenplum 5.x 开始,推荐使用新的备份恢复工具: gpbackup/gprestore。
  • packcore:packcore 可以将一个 core dump 文件及其所有的依赖打成一个包,可以在其他机器上进行调试。非常有用的一个调试用具。
  • explain.pl:把 EXPLAIN 的文本结果转换成图片。本节中用的计划树图片都是使用这个工具生成的。

5. 动手实践

上面概要介绍了把单个 PostgreSQL 数据库变成分布式数据库涉及的 6 个方面的工作。若对更多细节感兴趣,最有效的方式是动手改改代码实现某些新特性。下面几个项目可以作为参考:

  • 数据存储:实现 partial table,使得一张表或者一个数据库仅仅使用集群的一个子集。譬如集群有 200 个节点,可以创建只是用 10 个节点的表或者数据库。
  • 资源管理:目前的 Gang 只能在一个会话内部共享,实现 Gang 的跨会话共享,或者 Gang 共享池。
  • 调度:目前 dispatcher 将整个 plan 发送给每个 QE,可以发送单个 slice 给负责执行该 slice 的 QE
  • 性能优化:分析 Greenplum 分布式执行的性能瓶颈,并进行进一步的优化,特别是 OLTP 型查询的性能优化,以实现更高 tps。
  • 执行器优化:目前 Greenplum 使用 zstd 压缩 AO 数据和临时数据,zstd 造成的一个问题是内存消耗较大,如何优化操作大量压缩文件时的内存消耗是一个很有挑战的课题。有关更多细节可以参考这个讨论(最后部分有简单的问题重现方法)

对这些项目有兴趣者可以联系 yyao AT pivotal DOT io 提供更多咨询或帮助。实现以上任何一个功能者,可以走快速通道加入 Greenplum 内核开发团队,共应挑战共享喜悦 :)

6. 后记

想了一会,本想写一点打鸡血的话吸引更多人加入数据库内核开发行列,然觉不合自性,作罢。

Greenplum 酒文化比较浓厚,还是分享一个与酒有关的小故事收尾吧。

13/14 年左右有幸和数据库老前辈 Dan Holle(Teradata CTO,第七号员工)有诸多交集。老爷子谈吐优雅而不失幽默,从事 MPP 数据库已有 30 余年。每次喝酒至少放两瓶酒于面前,且同时喝两瓶酒,笑谈此为并行处理;若某一瓶喝的多了,必拿起另一瓶再喝一点,以确保两瓶余量保持一致,笑谈此为避免倾斜。经常左瓶喝的多了点,拿起右瓶补一口,补多了,再拿起左瓶补一口。如此左右互补,无需山东人劝酒,自己很快进入状态。

老先生对 MPP 数据库之爱已融入生活中,令人敬佩。而正是许多这样数十年如一日的匠人成就了当今数据库领域的辉煌。期待更多人加入,幕天席地把酒言欢!

作者介绍

姚延栋, Greenplum 研发总监,Greenplum 中文社区发起人。致力于 Greenplum/PostgreSQL 开源数据库产品、社区和生态的发展。

相关阅读

Greenplum:基于 PostgreSQL 的分布式数据库内核揭秘 (上篇)

阅读数:1149 发布于:2020 年 5 月 10 日 10:00

评论

发布
暂无评论