PostgreSQL学徒

Online Upgrade of replication clusters without downtime

前言

最近抽空在看 PGConf.dev 相关议题,今天听了一下 Online Upgrade of replication clusters without downtime (PGConf.dev 2024) (各位在油管上也能搜到),17 虽未正式谋面,却被吐槽许久,又在挤牙膏,话虽如此,其中仍不乏一些亮眼特性,比如该 Topic 中的 pg_createsubscriber,在 17 以后,要升级集群就要方便多了,大幅减少升级期间的停机时间。

介绍

首先,为何需要升级?升级的理由千千万,无外乎新特性、新性能、新工具、Security fixes、Bug fixes等等,其次每个版本都有其生命周期(5 年),关于 EOL 各位也可以查询 https://endoflife.date/ 这个网站。

Image

小版本的升级很方便,直接替换二进制文件然后重启一下就成,但是大版本就行不通了,文中也提到了原因,比如

  1. 系统目录可能发生变化,比如 pg_xlog → pg_wal

  2. WAL 的格式、条目也可能发生变化

当启动时,如果 PostgreSQL 发现版本不一致,还会拒绝启动。关于大版本升级,官方提供了 pg_upgrade 工具,其以速度著称,可以复用老版本的数据文件。

Image

元信息通过 pg_dump/pg_restore 进行复制,pg_xact/pg_multixact 则是直接 copy,而用户数据,根据可选项,分为 Copy/clone/relink,分别对应 read()/write()、ioctl_ficlone 和 link(),关于 pg_upgrade 的更多内核原理,找一期再好好研究分享一下。

Image

其次作者还提到了一句

The application can migrate data rapidly because it skips transactional operations at that time.

应用程序可以快速迁移数据,因为它当时跳过了事务操作。

但是 pg_upgrade 也有缺点

Image

首先要求实例必须处于停止状态,主要是为了防止一些异常活动

because it prevents unexpected behaviors during user activity or autovacuum or somethine.

其次复制槽也无法进行复制 (17 版本以前),流复制集群 (主库升级了之后,备库也依葫芦画瓢进行升级是不行的,主备关系会断,提示 system identifier 不一致)也会被破坏,因为流复制要求主版本必须一致 (小版本可以不同,但是并不推荐这么搞)。

那么还有什么大版本升级的替代方案呢?没错,自然是 logical replication  了。

Image

关于流复制和逻辑复制的区别就不多描述了。简而言之,流复制发送的可以理解成二进制变更信息,直接对应到底层数据块的变化,而逻辑复制相当于重演一遍。

Image

Image

然后作者提到了一些逻辑复制的原理,关于 reorder buffer 之前文章也有讲过,逻辑复制使用的专门的 output plugin。

升级复制集群

前面部分看 PPT 即可。

Image

Image

使用逻辑复制进行升级的好处是 WAL 格式的改变不影响逻辑复制,其次是不同版本之间也可以使用。

然后作者举了一个实际案例——将一个 12 的流复制集群升级到 16 版本,让我们顺着作者的思路理一理,当然,前提是尽量不停机,也就是在升级期间是可写的

Any concurrent activities are allowed, as much as possible

Image

  1. CREATE replication slots for all databases,对所有的库创建复制槽
  2. Create publications for all tables,在所有库下面创建发布
  3. Promote to primary,提升原来的备节点为主节点
  4. Advance replication slots,推进复制槽的位点信息,基于第三步时刻的 LSN,因为从这个 LSN 开始,两个节点间的数据就开始不一致了
  5. Create subscriptions for all databases,变成主节点之后,创建订阅,从源端 (12) 开始进行复制
  6. 待数据同步完成之后,就可以进行升级了,升级到 16

Image

  1. 现在复制链路就变成了上方所示,从 12 到 16,注意,由于源端 (12) 并没有停机,还在不断地写,并且订阅端升级完之后,也可能进行写入,两边数据就不一致了,所以为了一致性,这边做了 truncate all tables (我猜的原因,可能不准确,这个方案的准确性存疑,以发邮件咨询 speaker)
  2. Re-create subscriptions,重新拉取一遍全量数据
  3. Stop node1,等待数据同步完成之后,就可以停止源端了
  4. Run pg_basebackup,搭建新的备节点 (PG16 standby)
  5. Drop subscriptions,删除订阅

至此,一个 12 的集群顺利升级到了 16。

Image

各位也发现了,这样升级步骤太过于繁琐且麻烦

Image

不过作者这么搞确实繁琐了,如果是我的话,我会选择这么做

  1. 提升 12 的备节点
  2. 升级 12 备节点至 16
  3. dump schema
  4. 创建逻辑复制,从 12 拖到 16
  5. pg_basebackup 创建备节点

暂时没有想明白作者这么做的意图。

pg_createsubscriber

在 17 以后,上面的步骤就方便了

Image

直接运行 pg_createsubscriber,便可以将 12 的备节点转化为订阅节点,然后停止再升级,升级完成之后就可以搭建复制链路,至此便成功升级到了 17,大大减少了升级的复杂度 (相较于作者的方式)。

这个工具的目的也很简单,以往逻辑复制在初始 copy_data 期间可能会巨慢无比 (表越多越慢),并且在老版本中,一旦遇到报错,还会重头再来一遍,慢的抠脚,更不用说潜在的危害了,比如复制槽保留的 WAL。因此,pg_createsubscriber 直接将一个备节点转化为订阅者,就无需再慢吞吞地等了。

Image

以下是一个性能对比

Image

其原理其实也不复杂

ImageImage

逻辑复制版本升级

另外,作者还提到了一个很方便的特性,此处的 truncate all tables 可能是因为订阅端在写入,不过本身订阅端就可以接受数据不一致

Image

Image

如果现在要将 12 的逻辑复制升级到 16,那么你需要按照上面的步骤哐哐哐一阵操作。而 17 以后,就要方便多了

Image

Image

因为在 17 以后,pg_upgrade 还会复制复制槽的信息!其次 pg_subscription_rel 也会保留 (里面记录着哪些表在进行发布订阅),复制源也会保留,所以,在 17 以后,你只需要 enable 一下订阅即可,niubility。

小结

让我们小结一下,在 17 版本中

  1. pg_upgrade 会保留复制槽的信息,还会保留额外的订阅信息
  2. 大幅减少升级流复制集群停机时间
  3. 升级逻辑复制集群,而无需再次复制表数据

不过文中还有些许疑惑的点,比如为啥要 truncate all tables?逻辑复制本身就可以增量,也可以选择不拉全量 (copy_data=false),其次为啥要 create subscription 两次?关于这些,我已经发邮件咨询了作者,等回复之后我会在留言区评论。

另外,知道的读者也可以不吝赐教~ 总之,17 版本还是可圈可点。