openGauss和PostgreSQL深度差异对比

众所周知,openGauss是基于PostgreSQL 9.2 版本进行研发的,但是发展到现在,二者的差异越来越大。今天我就选取其中的比较重要的五个部分(Xid、检查点、页面断裂处理、内存表、流复制)带大家深入了解二者的差异。


Xid

Xid就是事务ID(tanssaction ID),事务ID用于在数据库中标识事务操作的唯一标识符,它对于数据库事务的管理和控制十分重要。在PostgreSQL中,它是一个32位整数,递增地分配给每个新的事务。

openGauss将Xid由 int32 改为了 int64。

int32 最大的值是4294967296,大约43亿,如果遇到一些数据量很大的数据库,那么就会存在Xid被耗尽的风险。为了防止事务回卷,事务环被分为两个半圆,当前事务号过去的21亿事务属于过去的事务号,当前事务号往前的21亿属于未来的事务号,未来的事务号对当前事务是不可见的。

Postgresql有三个特殊事务号:0代表无效事务号;1表示数据库集群初始化的事务id,也就是在执行initdb操作时的事务号;2代表冻结事务id。Txid=2的事务在参与事务id比较时总是比所有事务都旧,冻结的txid始终处理非活跃状态,并且始终对其他事务可见。

PostgreSQL通过vacuum freeze的方式来循环利用这些Xid,如果发生当新老事务id差超过21亿(即2 ^ 31)的时候,事务号会发生回卷,此时数据库会报出如下错误并且拒绝接受所有连接,必须进入单用户模式执行vacuum freeze操作。

使用 int64 的Xid则不会存在这些问题,2 ^64可以说是一个天文数字,不可能被耗尽,也就不存在事务回卷宕机的风险。

但是如果引用64位的Xid也会导致一个新的问题:需要存储的Xid占用空间太大。但是事实上在openGauss中,Tuple占用的存储风险与PostgreSQL相同。 这是因为openGauss在每个页的PageHeader新增了一个BaseXid用于记录64位基准Xid信息,而原本的TupleHeader还是记录32位的Xid偏移量,这样以来,由 BaseXid + Xid偏移量就可以很轻易地求出来任意的Xid,而因此牺牲掉的代价仅仅是BaseXid的8字节。

与此同时,vacuum freeze这个清除过期Xid的方式openGauss还是保留了。有读者可能好奇:既然已经没有事务回卷的风险了为什么还要进行这个操作呢?

这里就要引入CSN(Commit Sequence Number)的概念了, CSN是待提交事务的序列号(一个64位的无符号自增长整数),每个事务启动时会创建一个CSN快照。

一个CSN占据8字节,如果数据库已经执行了10亿个事务,则CSN LOG所需要的空间就是 1000000000 * 8 bytes = 7.45G。 这显然是十分浪费空间的,所以保留vacuum freeze的意义就是及时清理过期的CSN,最大化利用空间。


检查点

在PostgreSQL中的检查点叫全量检查点,执行时会将buffer中的所有的脏页刷到磁盘,需要在一定时间内完成刷脏页的操作,导致数据库运行性能波动较大。同时全量检查点开始时需要遍寻内存中的所有脏页,内存越大,寻找脏页的时间也越长,具体过程如下:

  1. 遍历所有BUFFER,将当前时刻的所有脏块状态改为CHECKPOINT NEEDED,来表示需要将这些脏块写出磁盘。这一步是在内存中完成的不涉及磁盘操作。
  2. 刷物理文件,从缓存中将脏块fsync到磁盘。这一步涉及磁盘操作。将标记为CHECKPOINT NEEDED的block写出到磁盘。
  3. Checkpoint本身也会被记录到XLOG中,检查点会被信息刷出到xlog中。
  4. 更新控制文件中的检查点信息到当前位置。

这样做总体看上去没什么问题,但是在Buffer容量设置的很大的时候,那么寻找脏块的时间就会很长,并且刷入磁盘的时间也会很长。

对此openGauss新增了增量检查点功能,默认每分钟执行一次。openGauss增量检查点会小批量的分阶段的进行脏页刷盘,同时更新lsn信息,回收不需要的xlog日志。在增量检查点的机制下,会维护一个按照LSN顺序递增排列的脏页面队列,无需每次遍历内存中所有的脏页。


页面断裂处理

操作系统数据块是4k,数据块一般是8k/16k/32k(openGauss是8k,MySQL是16k),这样有可能造成页面断裂问题,即一个数据库数据块刷到操作系统的过程中可能发生宕机造成数据块损坏导致数据库无法启动。

当数据库正在从内存向磁盘写一个数据页时,数据库宕机,从而导致这个页只写了部分数据,这就是部分写失效,导致数据丢失。这时即使是redo log也无法恢复,因为redo log记录的是对页的物理修改,如果页本身已经损坏,redo log也无能为力。

对于这个问题,PostgreSQL采用的方式是full page write,数据页第一次发生变更时将整个页面记录至xlog日志中,这样即使出错也具备完整的数据页和xlog日志进行数据恢复。但是这样大大增加了xlog的日志量,对性能有一定的影响。

openGauss采用的方式是double write,这类似与MySQL,写数据块的同时将脏页写到一个共享的双写空间中,如果发生问题就从双写空间中找到完整的数据页进行恢复。(备注:双写特性需要配合增量检查点一起使用)


内存表

PostgreSQL不支持内存表,但可通过其他手段实现,比如将内存挂载成硬盘,在其上建立表空间。但是这种做法内存表数据只能临时存放,重启PostgreSQL没问题,但是重启操作系统后数据就会丢失。

openGauss支持内存表,支持基于llvm的内存查询引擎,支持高吞吐、低延迟访问。内存表非只读操作会记录xlog,数据库重启时通过xlog进行恢复,数据可持久化存在。(内存表与增量检查点的特性不兼容,如果使用内存表就需要关闭增量检查点功能)


流复制

最大可用模式(most_available_sync)

它防止同步备库宕机时,主库被事务夯住。开启该参数后在主从连接正常的情况下处于同步模式,如果备机断连会立刻切为异步模式,如果备机再次启动会自动连接并且切为同步模式。

openGauss这个功能有效地避免因网络抖动、进程闪断等因素导致主机在最大保护模式和最大可用模式之间频繁来回切换导致的性能损失。

自动创建物理复制槽

openGauss中搭建主从流复制环境后会默认自动创建一个slot_name为对端ndoename的物理复制槽,为了防住备库需要的xlog被主库删除或者清理。

主库Xlog容量限制

通过max size_for xlog prune参数控制,不管xlog的删除会不会影响备机,只要超过该值就会进行删除。可以防止主备长期断连造成主库目录爆掉。