自省 自行 自醒

从 Greenplum 扩容谈起

Word count: 2.9kReading time: 10 min
2024/03/14
loading

前言

今天①群里一位铁子说到:”已经十天没发文了”,我自嘲式地回复到,可能是阅读量太低了,动力不足。

由于最近手上的事情比较多,忙着培训和翻译书籍,工作也忙到处出差,没有太多闲暇精力来写文章,这篇文章还是我在飞机上写完的😵‍💫。戏谑归戏谑,言归正传,最近几天一直在忙着做 Greenplum 扩容相关的事情,也增长了不少经验,简单聊一聊。

再次感谢三水编著的《Greenplum Database管理员指南》,翻看了三四遍,十分受益。

何如

随着数据量的持续增长以及新业务的不断接入,数据库需要更多的数据容量和计算能力,因此,就需要给数据库进行扩容。如果是单机 PostgreSQL,扩容还算相对简单,加点 CPU,加点内存。

但是当你面对的是加大号的 Greenplum 时,面临的境况就要复杂得多,首先,分布式数据库涉及多台机器的协同,扩容期间如何保证数据的一致性?如何保证扩容时对业务的影响降到最小?如何保证扩容之后不会引入新的短板 (比如一台算力相差很多的机器)?如何保证多台机器的操作系统参数,数据库参数等一致?以上种种都需要考虑。

因此,扩容前的准备工作就显得至关重要:

  1. 新增节点的性能测试,以排除扩容无法达到预期效果的隐患,如果新扩容的机器由于各种原因,只能发挥两三成功力,无疑,扩容之后,这个节点会成为限制集群能力的最大隐患,造成”一人有难,他人围观”的尴尬局面。性能测试可以借助 Greenplum 自带的 gpcheckperf 工具,会测量写入和读取能力,内存和网路速度,当然也可以使用 fio、iperf 等第三方工具
  2. 操作系统的配置,Greenplum 对操作系统和网络的强依赖性相较单机 PostgreSQL 更甚,处理 Greenplum,你需要对运维网络有一个基本的认知,什么丢包,乱序,重传之类的,这些都需要学会排查;还有就是 OOM 相关的配置,也是一门很大的学问,如何保证各个节点的配置都一样?最土最直接的办法就是人肉 + 脚本的形式,更加效率的方式是通过文件下发的形式,从某个节点下发对应的配置文件至所有机器,然后通过校验 md5 的形式
  3. 免密互信,由于 Greenplum 大多数操作都是借助 ssh 来实现的,在实例多的集群中,可能会同时向一个机器发起多个 ssh 访问,缺省的 ssh 设置,还需要调整 MaxStartups,其次像 gpstart、gpstop 等命令都是基于 python + ssh 实现的,因此 gpadmin、root 用户都建议配置互信

关于扩容前的准备工作,可以参照官网,也可以参照三水的管理员指南

一切准备就绪之后,便可以进行扩容了。Greenplum 针对此场景提供了 gpexpand 工具,从 6 之后,GPexpand 的能力增强了许多:

  1. 在线不停机,增加新节点只要在 gp_segment_configuration 里添加新节点信息即可,更新元数据,极短的锁定时间,因此可能你的 DDL 会被短暂阻塞。
  2. 数据重分布优化,在 Greenplum 5 和之前的版本里会将所有的表改成随机分布,然后再 ALTER 成按列分布,无疑慢如蜗牛。
  3. 并行的优化,借助全局死锁检测可以对 heap 表做并行更新,实现并行扩容,在 6 版本之前,所有的表的 UPDATE 和 DELETE 操作都是 EXCLUSIVE 锁,所以,在进行并发数据重分布时,很多小表的数据重分布时间很短,而更多的时间消耗在更新表的重分布状态上,因为 UPDATE 操作需要 EXCLUSIVE锁,只能串行执行。

更多细节各位可以阅读”Greenplum 6新特性:在线扩容工具 GPexpand 剖析”这篇文章。gpexpand 总体上分为三步:

  1. 生成扩容计划
  2. 新节点的初始化
  3. 数据重分布

扩容计划,即新加入节点如何规划数据目录?如何规划镜像策略?是 group,还是 spread,还是需要自定义的形式,这一步需要综合衡量,需要考虑当某个节点异常之后,计算倾斜的场景。

其次麻烦一点的是数据重分布,顾名思义,扩容之前的存量数据还分布在原来的数据节点上 (新进来的数据会分布在所有节点上),因此如果查询旧表,那么就无法利用新增节点的计算能力,所以需要将表重新组织,将数据分配至所有节点上,不难理解,这一操作需要获取最重的 AccessExclusiveLock,阻塞一切操作,因此重分布的窗口就显得尤为重要。

gpexpand 为我们提供了几个相对应的视图,gpexpand.status_detail 表中记录了所有需要重分布的业务表的信息,比如预估表大小,迁移进度等,expansion_progress 会展示整体的重分布情况,包括未重分布的表数量,已经重分布的表的数量等信息。

其次各位可能会担心扩容到一半,假如失败了怎么办?有机器挂了怎么办?迟迟不结束超过了窗口期怎么办?最开始我也担心这个,后面发现,gpexpand 命令对每张表执行数据重分布操作时,都会显式的 BEGIN 事务和 COMMIT 事务,因此重分布这一操作是原子性的,失败之后,再次执行 gpexpand 即可,有了原子性的加持,至于窗口期,就显得 so easy 了,gpexpand 可以指定 endtime,即运行到某时某刻就结束。

再之,我们还可以调整每个表的 “rank”,让重要的表先进行重分布。另外,也可以选择手动执行 alter table xx set with (REORGANIZE= true) 进行数据重分布,当然,当我们碰到分布键规划错误,需要调整新的分布键时,也可以执行重分布。

发散

聊完了重分布,再聊聊我再扩容期间的其他思考🤔。扩容期间,我发现在 Master 节点的 pg_hba 文件中,有一个特别生疏的 samehost,比如

1
host		all			all			samehost		trust

看得我是目瞪口呆,以往在 PostgreSQL 中,从未见过这玩意,那这玩意是什么呢?官网上还真有介绍,不过仅寥寥几字:

The value samehost is used to indicate that any networked connections originating from one of the server’s own IP addresses should match.

简而言之,就是自己连自己… 行吧,貌似有点鸡肋,网口比较多的时候方便点,也难怪很少看到这玩意。比如有这样两条规则,那么使用 -h 127.0.0.1,是需要密码的。

1
2
host    all             all             samehost                md5
host all all 127.0.0.1/32 trust

其次,在扩容期间,我们可能需要拒绝客户端连接,安心扩容,那么最直接的方式当然是配置白名单了,那么按照以往单机 PostgreSQL 的思维,我加个 0.0.0.0/0 reject 不就万事大吉了?但是这个套路貌似并不适用 Greenplum,首选 Greenplum 涉及到多个节点的通信和协同,通常,QD 和 QE 之间有两种类型的网络连接:

  • libpq 是基于 TCP 的控制流协议。QD 通过 libpq 与各个 QE 间传输控制信息,包括发送查询计划、收集错误信息、处理取消操作等。libpq 是 PostgreSQL 的标准协议,Greenplum 对该协议进行了增强,譬如新增了‘M’消息类型 (QD 使用该消息发送查询计划给 QE)等,我们接触的最多 prepareStatement (Parse:类型为“P”,Bind:类型为“B”),以及简单查询协议 (类型为“Q”)。QD 此外还负责整个 SQL 语句涉及到的所有的 QE 进程间的通讯控制和协调,譬如某个 QE 执行时出现错误时,QD 负责收集错误详细信息,并取消所有其他 QEs;如果 LIMIT n 语句已经满足,则中止所有 QE 的执行等。
  • Interconnect 数据流协议:QD 和 QE、QE 和 QE 之间的表元组数据传输通过 Interconnect 实现,Greenplum 有三种 Interconnect 实现方式,一种基于 TCP 协议,一种基于 UDP 协议,还有一种是 Proxy 协议。缺省方式为 UDP Interconnect 连接方式。Interconnect 是 Greenplum 数据库中负责不同节点进行内部数据传输的组件。Greenplum 数据库有一种特有的执行算子 Motion,负责查询处理在执行器节点之间交换数据,底层网络通信协议通过 Interconnect 实现。

所以对于 QE 而言,QD 是一个 PostgreSQL 的客户端,它们之间通过 PostgreSQL 标准的 libpq 协议进行通讯。对于 QD 而言,QE 是负责执行其查询请求的 PostgreSQL Backend 进程。通常 QE 执行整个查询的一部分 (称为 Slice)。

那么我设为 0.0.0.0/0 reject,是否会影响到和 segment 的通信?并且,pg_hba.conf 的匹配规则是从上往下,匹配到第一条就不再继续,因此假如已经配置了许多规则,你把 0.0.0.0 加在末尾是不起作用的。那我偷懒放在最前面会不会有问题?用脚指头都能想到,这个操作应该不可取,但是神奇的是,我简单测了一下,将 segment 节点的 pg_hba.conf 只加了一条 0.0.0.0/0 md5(其他内部规则全部移除),也试过在 master 上添加 0.0.0.0/0 reject 在最前面,Master

1
2
host    all             all     0.0.0.0/0     reject
host replication all 0.0.0.0/0 reject

,虽然 segment 上少了一些从 master 连过来的杂七杂八的 MPPEXEC 进程,其次也可以看到 mirror 不正常了 (不难理解,当 segment 添加了 replication 0.0.0.0/0 reject 之后),但是在 master 上仍然可以正常建表,插入,查询等,这与我预期有点不符。不过,现在功力还未到家,做的实验也比较简单粗糙,关于这一点,就当做一个 TODO 项吧,还需要更多深入点的实验,等空了分析一下吧,知晓的读者也请不吝赐教,在此叩谢。

总而言之,在 Greenplum 里,还是不建议 0.0.0.0 如此粗暴的方式,操作小心为上,以下对话来自三水

segment 上不要加 reject,后续会有不能 work 的情况出现

常规的很多通信是走 ic 的,应该跟 hba 无关,所以,看起来能工作,但会有其他问题

早期版本 4 的时候,出过 bug,扩容的时候,hba 设置的不对,导致备份报错

有些操作是要直连节点的

小结

这几天被扩容折腾地够呛,好在增长了不少经验。在飞机上所写,思路也不是很清晰,就简单写两句吧,如上。

参考

PostgreSQL 技术内幕(五)Greenplum-Interconnect模块

Greenplum Database管理员指南

PgSQL · 引擎特性 · PostgreSQL通信协议

PostgreSQL技术内幕(九)libpq通信协议

CATALOG
  1. 1. 前言
  2. 2. 何如
  3. 3. 发散
  4. 4. 小结
  5. 5. 参考