队列理论在我们生活中的应用随处可见,例如机场、海关、银行甚至排队买菜。可以说,只要有排队的地方,就可以应用队列理论进行服务优化。
在计算机领域的架构设计,性能诊断等地方使用队列理论的案例也多不胜举。曾经培训的时候听到过,有牛人可以在不了解具体技术的情况下仅凭队列理论的知识就可以快速定位系统的瓶颈所在。拿一台服务器来讲,分为动态设备和静态设备。CPU和IO子系统属于动态设备,RAM属于静态设备,队列理论只对动态设备适用。今天不打算介绍队列理论的基础知识,而想讨论一下队列理论在PG数据库对于写WAL上的应用。
一个系统的响应时间由两部分决定,等待时间和服务时间。而这两部分时间又受到到达速率和服务速度的影响。这是队列最基本的因素。
在PostgreSQL中,有一个参数叫做commit_delay,可以通过调整它在某些情况下来提升系统的吞吐量。按照官方文档,commit_delay定义了一个组提交领导进程(group commit leader process)在调用XLogFlush中获得锁后,需要睡眠多少微秒来让组提交跟随者进程(group commit followers)进行排队,这样其他事务的WAL也可以写入到WAL buffer中,在下一次被唤醒时组提交领导者进程便可以一次刷新多个事务的WAL到持久化的IO设备中,从而可以提升系统的总吞吐量。
那么,这个参数的调整为什么可以在某些情况下提升系统的吞吐量?对于WAL写入来讲,等待时间是指一个提交请求到达但还没有被持久化到磁盘上的WAL段的时间,服务时间则是组提交领导者进程执行SYNC操作将WAL buffer持久到磁盘上WAL的时间。磁盘的响应时间在这里是服务速度,而系统的并发度,单位时间内的commit请求数则为到达速率。这里面有几种情况:
1、当到达速率很低,也就是并发度很低的情况下,IO设备的响应时间也就是服务速度虽然慢但能够应付得过来,并不需要做任何调整。整体的响应时间基本上就是IO设备的响应时间;
2、到达速率很低,也就是并发度很低的情况下,IO设备的响应时间很快,更加不需要做任何调整。整体的响应时间基本上就是IO设备的响应时间;
3、到达速率很高,系统并发度很高,IO子系统存在高延迟,手机游戏账号转让平台响应时间很长,服务速度很慢,很快就会造成需求积压。如果一个需求一个需求处理,则需求积压会越来越严重,事务延迟(等待时间)越来越长,导致系统吞吐量急剧下降。这种情况下,我们可以合并需求,将多个需求打包一起处理,减少和慢速IO子系统的交互次数,系统吞吐量得以上升。如果难以理解,大家不妨想一下乘坐机场摆渡车的情况。但是也要考虑不能合并过多需求,否则对于每个单一需求来讲,延迟可能会高得难以忍受;
4、到达速率很高,系统并发度很高,IO子系统延迟非常低,响应时间很快,例如使用的是SSD设备。这种情况下,需求不容易出现积压,不希望合并提交请求的情况经常发生,而更愿意获得对单一事务来讲更低的延迟;
看来,commit_delay是针对后面两种情况来调整的。对于高延迟的IO子系统,希望能够尽量合并系统的提交请求来提升整体的吞吐量。一般来讲IO子系统的延迟越高,commit_delay可以设置得越长。但前面也提到过,要防止设得过高,否则会矫枉过正,导致系统吞吐量下降。对于低延迟的IO子系统,这个值可以设得低一些或者保留缺省值。
此外,PostgreSQL还有另外一个参数配合,称为commit_siblings。这个参数用于控制commit_delay是否需要休眠。如果当前活动的事务少于commit_siblings,则commit_delay即算是非零值也不会进入休眠而且直接进行SYNC操作;反之则进入休眠状态,等待其他事务的提交请求进来进行合并。这也是一个侦测到达速率的机制,如果到达速率不高,属于前面的第1、2种情况,也就没有必要休眠等待了。对于慢速IO设备,我们希望合并的门槛低一些,尽可能地通过合并请求来提升吞吐量,所以commit_siblings可以设置得低一些;而对于低延迟高速IO设备,这个参数值可以设得高一些,使得合并没有这么容易发生。
附官方文档的一些建议:
1、在一些平台上,休眠最高的精度是10ms,所以在这些平台上,commit_delay设置1到10000的效果都是一样的;
2、在某些平台也可能导致休眠的时间稍长于commit_delay设置值;
3、如果需要设置commit_delay,建议以写入一个8k数据块平均时间的一半做为commit_delay的开始点;