PostgreSQL-14版本snapshot的几点优化

2022/12/01 18:00
阅读数 31


最近在分析PostgreSQL-14版本性能提升的时候,关注到了Snapshots的这一部分。发现在PostgreSQL-14版本,连续合入了好几个和Snapshots相关的patch。

并且,Andres Freund也通过这些改进显著减少了已确定的快照可扩展性瓶颈,从而改进了Postgres处理大量连接的问题,因为在连接数很高时计算一个快照的代价是很昂贵的。

(对于分析连接可扩展性的局限性可以参考 Andres Freund 的这篇文章 https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/analyzing-the-limits-of-connection-scalability-in-postgres/ba-p/1757266)

本篇文章,我会结合 Andre Freund 的几个patch进行一些分析,看看在PostgreSQL-14版本里对于Snapshots做的这部分优化。

在PostgreSQL里是使用SnapshotData结构体的xip来记录哪些事务正在运行中,正常情况下,对于普通的MVCC快照,xip记录了所有运行中的事务ID。(除了快照是处于recovery状态产生的,这个时候它为空

typedef struct SnapshotData
    {
        TransactionId xmin;            /* all XID < xmin are visible to me */
        TransactionId xmax;            /* all XID >= xmax are invisible to me */
        TransactionId *xip;
        uint32        xcnt;            /* # of xact ids in xip[] */
        TransactionId *subxip;
            int32        subxcnt;        /* # of xact ids in subxip[] */
    ...

在PostgreSQL-14版本之前,每个PG进程都对应一个PGPROC结构、一个PGXACT结构,而从PostgreSQL-14版本开始,把PGXACT合到PGPROC里面了。


优化一:在构建快照时不计算全局 horizons

这个 patch 的地址:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=dc7420c2c9274a283779ec19718d2d16323640c0

为了使 GetSnapshotData() 具有更强的可伸缩性,它不能不查看每个进程的xmin:尽管快照内容不需要在提交只读事务或释放快照时更改,但在这些情况下会修改进程的xmin。xmin修改的频率很快,它会导致 GetSnapshotData() 中的许多缓存丢失,尽管快照底层的数据没有改变,尤其是在核心计数较高的系统中。这是GetSnapshotData() 在大型系统上扩展性差的最重要原因。

这个提交其实只是删除对 PGXACT->xmin 的访问,让 GetSnapshotData() 不再需要访问xmins,这本身不会带来显著的改善,但是为后续的几个优化都是针对GetSnapshotData()不需要访问xmins去做的。


优化二:将PGXACT->xmin移回PGPROC

这个 patch 的地址:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=1f51c17c68d05c28d5b9294d8013cb9e7e653160

早在2011年其实就已经发现了 GetSnapshotData 存在瓶颈,当时做的优化是把PGPROC 里面把快照需要的变量拆出来,放到 PGXACT 中,这样数据结构小很多,可以装到一个 cpu cacheline 中。

这个 patch 它把第一部分 GetSnapshotData() 不再需要的xmin移回了 PGPROC。原本在 PGXACT 里使用xmin的时候,它比 PGXACT 里的其他成员更新更频繁,进而导致 cacheline 有很多不必要的 ping-pong ,所以又将 PGXACT->xmin 移回了PGPROC 结构体。

这几句话大一看好像有点一头雾水,什么是cacheline,什么是ping-pong。
这里给大家大致解释这几个名词,方便我们更好去理解,文章底部也有我参考的链接。

1、cacheline

内存是DRAM,速度慢,容量大。属于CPU外部设备,通过总线与CPU进行通信。而cache 在 CPU内部,属于片上硬件。所以内存的速度会远远慢于cache。

cache的单位是cacheline,是一块连续的内存。以cacheline 64字节为例。所有的cacheline 又划到各个way下。row: 叫做set。columne: 叫做way


当CPU访问一个内存的时候,通过内存中间的6bits定位在哪个set,再通过24bits定位响应的cacheline。

对同一块cache不停的进行写操作,会增加cache之间更新缓存的花销。包括不停的去刷新主存的内容。缓存的cacheline的state machine 频繁的从 S -> I 状态

2、ping-pong

Ping-pong是一种数据缓冲的手段(是一种数据传输技术),能够同时利用两个相同数据缓冲区达到数据连续传输的目的(对象类型可以是任意的),两者交替地被读和被写,从而提高数据传输速率。


然后再去看开头的那句话,是不是大致理解了,这个patch主要减少了在这个函数里使用xmin所导致的cache之间频繁更新缓存的花销,对于高度并发、快照获取量大、工作负载大的情况,仅此更改就可以显著提高可伸缩性。


优化三:将PGXACT->vacuumFlags
移动到ProcGlobal->vacuumFlags

这个 patch 的链接为:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=5788e258bb26495fab65ff3aa486268d1c50b123

这个 patch 把 PGXACT->vacuumFlags 移至 ProcGlobal,增加了GetSnapshotData() 经常需要的数据保存在 L2 缓存中的机会,L2 缓存位于 CPU 与内存之间的临时存储器,容量比内存小但交换速度快,二级缓存容量大小决定了 cpu的性能。

L1 L2 L3缓存和内存的快慢关系,可以参考如下:

  • L1的存取速度:4个cpu时钟周期

  • L2的存取速度:11个cpu时钟周期

  • L3的存取速度:39个cpu时钟周期

  • RAM的存取速度:107个cpu时钟周期

可以看到,L2缓存的速度比在内存里快,因此这样的改动,相当于加快了GetSnapshotData()执行的速度。


优化四:移动subxact信息到ProcGlobal,删除PGXACT

这个 patch 的链接为:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=73487a60fc1063ba4b5178b69aee4ee210c182c4

与第三个优化的更改类似,这个patch增加了 GetSnapshotData() 经常需要的数据保存在L2缓存中的机会。在许多 workload 中,子事务是非常罕见的,这使得检查的代价很小。

把 PGXACT 的所有成员都转移了,所以不需要再保留它了,因此删除了 PGXACT 结构体。


优化五: 引入正在进行的xid的密集数组

这个 patch 的链接为:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=941697c3c1ae5d6ee153065adb96e1e63ee11224

之前构建快照时,GetSnapshotData 会遍历 procArray->pgprocnos 找到所有存在的 PGPROC、PGXACT 结构,收集所有的 xid,即通过 pgprocnos 拿到索引,通过索引在离散的 allPgXact 数组中拿到 xid。而现在的版本,其实改成了将 xids 单独拿出来放到连续存储的密集数组中,来显著提高其命中率。


优化六:使用xact完成计数器缓存快照

这个 patch 的链接为:https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=623a9ba79bbdd11c5eccb30b8bd5c446130e521c

这个提交在VariableCacheData的结构体里引入了xactCompletionCount(VariableCacheData是共享内存中的一个数据结构,用于跟踪OID和XID赋值状态),它跟踪自服务器启动以来以某种形式完成的带有xid(即可能修改了数据库)的Top事务的数量。PostgreSQL-14版本仅用于检查GetSnapshotData(是否需要重新计算快照的内容。


只要当前的xactCompletionCount与最初构建快照时相同,就可以避免重新构建快照的内容。

这个改动的意图很明显了,前几个优化都是让计算快照变得更快/更可伸缩,而这个改动是:避免重新构建快照的内容,减少资源占用。(也就是能不重新做快照就不做快照)。

参考列表

1.Improving Postgres Connection Scalability: Snapshots(Andres Freund)

https://www.citusdata.com/blog/2020/10/25/improving-postgres-connection-scalability-snapshots/#unfortunately-a-minimal-transaction-visibility-primer

2.cacheline

https://www.jianshu.com/p/f8d3d17e9e8b

3.ping-pong机制

https://blog.csdn.net/chenyu201003/article/details/81449762

4.德哥的PostgreSQL 20200819当天代码 - 14 对比 13 高并发性能优化 数据对比 -

get snapshot improve
https://github.com/digoal/blog/blob/master/202008/20200817_01.md

点击此处阅读原文

↓↓↓

本文分享自微信公众号 - 开源软件联盟PostgreSQL分会(kaiyuanlianmeng)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部