背景

自 RDS for PostgreSQL 上线以来,我们发现不少用户反馈 PostgreSQL 性能很差,尤其是业务高峰期。

这类问题并不是个例,我们一个共享单车客户的实例和一个考试系统实例, 发现了该问题,用户反馈在业务高峰期,数据库吞吐量比平时还低。同时,我们发现,客户碰到该问题后的解决方案都是,增加连接池大小,但几乎没效果。

问题分析

通过分析,我们发现,在大并发场景下

  1. 数据库实例的每一个连接执行 SQL 时都会开启一个事务(只读事务和读写事务),同时开启事务较多时,数据的可见性判断复杂程度会急剧增加, 通过 perf top 可以看到,函数 XidInMVCCSnapshot 的占用超过 90%。这是性能问题的主要原因。

  2. 数据库内部锁争用明显增加。各类型的锁都纯在不同程度的争用。

  3. 进程间的上下问切换代价增加。由于 PostgreSQL 是多进程架构,即使是在 64 核的主机上,同时开启几千个连接跑任务,势必导致这些进程频繁的进行切换,这些损耗是不可忽视的。

另外,之前提到的,这样的场景下,大多数 DBA 会选择增加客户端程序的连接池大小,希望通过加大并发提高系统吞吐量。根据分析,这只会徒劳加大同时开启的事务数,对提高系统吞吐量帮助不大。

当该问题发生时,虽然开启了数千个连接,也是业务的高峰期,但整个实例吞吐量却十分低下,业务很难走出高峰期。

解决方案

针对该问题,RDS PostgreSQL 在内核层面提出解决方案

  1. 在 PostgreSQL 内部创建4个独立的队列
    • a dml 队列
    • b autocommit 语句队列
    • c 事务块队列
    • d 长 SQL 队列
  2. 数据库执行 SQL 时,会先把他们分类,然后按照 SQL 的类型进行排队执行。

  3. 通常,这4类 SQL 和业务有比较紧密的关系。例如,分析性业务通常都是长 SQL。让它们排队执行,不会消耗过多的硬件资源,从而做到分析型业务和交易型业务在一定程度上共存。

  4. 对于交易型业务,控制他们的并发程度,可以让它们很快的执行完成,下一章有性能数据。

  5. 交易型业务的并发程度可以支持设定,建议并发度和实例规格的的 CPU 数相当。

性能

下面,让我们来看看 RDS for PostgreSQL 应用该特性后和社区 PostgreSQL 的性能测试数据

测试场景 pgbench tpc-b

连接数社区版本 PostgreSQL TPSRDS for PostgreSQL TPS社区版本 PostgreSQL RTRDS for PostgreSQL RT
6499086959320.475 ms0.501 ms
12886807867190.934 ms0.854 ms
25669805743712.109 ms1.842 ms
51249147594234.656 ms4.587 ms
102442295458839.837 ms8.69 ms
2048321473869836.882 ms7.928 ms
4096235562760467.513 ms7.522 ms
81921703724524201.208 ms6.536 ms

从性能数据得到下列结论:

  1. 社区版本的 PostgreSQL 随着连接数增加,TPS 会急剧降低,RT 会显著增加。
  2. RDS PostgreSQL 随着连接数的增加 SQL RT 表现比较稳定,TPS 也在降低,但相对社区版 PostgreSQL 高不少。能保证数据库有一个较高的吞吐量。

总结

本文从原理角度出发,解析了大并发条件下 PostgreSQL 系统吞吐量的原因,也给出了RDS PostgreSQL 解决该问题的思路,欢迎大家使用该特性!