GitLab在去年6月的时候,发现GitLab.com出现不寻常的停顿,经过调查才知道,在数据库PostgreSQL中,通过SAVEPOINT SQL查询起始子交易,则正在进行中的长交易(Long Transaction),有可能会对数据库的副本产生严重性能冲击。
之所以会发现这个问题,是GitLab观察到CI/CD Runner服务偶而会出现高错误率,用来检索CI/CD构建资料的数据库查询发生超时的状况,并且未处理的构建积压迅速增长。
而这个问题难以调查的程度,官方将其称之为Nessie,意思是这个问题就像是尼斯湖水怪一样,无法预测出现的时机。官方提到,数据库查询停滞的情况随机出现,而且停顿可能持续15分钟后就消失,几天后才会再出现。
数据库停顿伴随一些模式,GitLab发现只有数据库副本会受到影响,主要数据库运行正常,在发生停顿期间,通常有一个长时间执行的交易正在进行,通常和PostgreSQL自动清理有关,只要长交易结束,停顿也会迅速停止。经过了长时间的调查,以及诸多假设与验证,GitLab终于发现并且能够重现这个问题。
官方称这个问题为Subtrans SLRU溢出,起因是忙碌的数据库,可能使子交易日志的大小,庞大到工作集无法存在内存中,这将导致大量缓存未命中,进而造成大量磁盘I/O和CPU负载,因为PostgreSQL会疯狂地尝试从磁盘中加载资料,以符合查询使用。官方提到,只要在长交易间,有一个SAVEPOINT出现,就会造成这个问题。
在PostgreSQL中,交易从BEGIN语句启动,而SAVEPOINT则是开始子交易,官方举例解释需要使用SAVEPOINT的理由,当消费者在线上商店下订单,意外地同时有两笔订单抵达数据库,要让数据库不因为重复创建账户,导致其中一笔订单失败,而是希望可以在订单直接使用该账户,开发者便能以SAVEPOINT来进行子交易,先进行查询,使用刚刚创建的账户并且完成订单。
要解决Nessie数据库停顿问题有三个选择,完全消除SAVEPOINT的调用、消除所有长时间执行的交易,或是在PostgreSQL加入应急修补程序。GitLab先选择方案一,消除SAVEPOINT的调用来解决这个问题。官方提到,消除数据库的长交易不切实际,而且许多长交易是数据库的自动清理,要从这方面下手,需要花费过多的时间精力。
而官方同时也在考虑对PostgreSQL安装修补程序,但是安装修补程序有许多需要考量的点。修补程序虽然能够借由增加缓存到100 MB,来解决SAVEPOINT的问题,但是缓存达100 MB,会让搜索成本变高。
问题不只如此,当使用了应急修补程序,便会使得GitLab所使用的PostgreSQL成为非官方版本,这会大幅增加基础设施团队的维护成本,而且自我托管的用户也必须安装修复程序,才能解决问题。
官方目前选择成本最低的方案,消除SAVEPOINT的调用,来避免Nessie问题,而GitLab在删除SAVEPOINT之后,也就没有再遭遇到数据库停顿的状况,GitLab提到,他们建议所有PostgreSQL的用户,要执行只读副本的PostgreSQL,也应该删除所有子交易,避免遇到Nessie问题。