背景
自 RDS for PostgreSQL 上线以来,我们发现不少用户反馈 PostgreSQL 性能很差,尤其是业务高峰期。
这类问题并不是个例,我们一个共享单车客户的实例和一个考试系统实例, 发现了该问题,用户反馈在业务高峰期,数据库吞吐量比平时还低。同时,我们发现,客户碰到该问题后的解决方案都是,增加连接池大小,但几乎没效果。
问题分析
通过分析,我们发现,在大并发场景下
数据库实例的每一个连接执行 SQL 时都会开启一个事务(只读事务和读写事务),同时开启事务较多时,数据的可见性判断复杂程度会急剧增加, 通过 perf top 可以看到,函数 XidInMVCCSnapshot 的占用超过 90%。这是性能问题的主要原因。
数据库内部锁争用明显增加。各类型的锁都纯在不同程度的争用。
进程间的上下问切换代价增加。由于 PostgreSQL 是多进程架构,即使是在 64 核的主机上,同时开启几千个连接跑任务,势必导致这些进程频繁的进行切换,这些损耗是不可忽视的。
另外,之前提到的,这样的场景下,大多数 DBA 会选择增加客户端程序的连接池大小,希望通过加大并发提高系统吞吐量。根据分析,这只会徒劳加大同时开启的事务数,对提高系统吞吐量帮助不大。
当该问题发生时,虽然开启了数千个连接,也是业务的高峰期,但整个实例吞吐量却十分低下,业务很难走出高峰期。
解决方案
针对该问题,RDS PostgreSQL 在内核层面提出解决方案
- 在 PostgreSQL 内部创建4个独立的队列
- a dml 队列
- b autocommit 语句队列
- c 事务块队列
- d 长 SQL 队列
数据库执行 SQL 时,会先把他们分类,然后按照 SQL 的类型进行排队执行。
通常,这4类 SQL 和业务有比较紧密的关系。例如,分析性业务通常都是长 SQL。让它们排队执行,不会消耗过多的硬件资源,从而做到分析型业务和交易型业务在一定程度上共存。
对于交易型业务,控制他们的并发程度,可以让它们很快的执行完成,下一章有性能数据。
交易型业务的并发程度可以支持设定,建议并发度和实例规格的的 CPU 数相当。
性能
下面,让我们来看看 RDS for PostgreSQL 应用该特性后和社区 PostgreSQL 的性能测试数据
测试场景 pgbench tpc-b
连接数 | 社区版本 PostgreSQL TPS | RDS for PostgreSQL TPS | 社区版本 PostgreSQL RT | RDS for PostgreSQL RT |
---|---|---|---|---|
64 | 99086 | 95932 | 0.475 ms | 0.501 ms |
128 | 86807 | 86719 | 0.934 ms | 0.854 ms |
256 | 69805 | 74371 | 2.109 ms | 1.842 ms |
512 | 49147 | 59423 | 4.656 ms | 4.587 ms |
1024 | 42295 | 45883 | 9.837 ms | 8.69 ms |
2048 | 32147 | 38698 | 36.882 ms | 7.928 ms |
4096 | 23556 | 27604 | 67.513 ms | 7.522 ms |
8192 | 17037 | 24524 | 201.208 ms | 6.536 ms |
从性能数据得到下列结论:
- 社区版本的 PostgreSQL 随着连接数增加,TPS 会急剧降低,RT 会显著增加。
- RDS PostgreSQL 随着连接数的增加 SQL RT 表现比较稳定,TPS 也在降低,但相对社区版 PostgreSQL 高不少。能保证数据库有一个较高的吞吐量。
总结
本文从原理角度出发,解析了大并发条件下 PostgreSQL 系统吞吐量的原因,也给出了RDS PostgreSQL 解决该问题的思路,欢迎大家使用该特性!