在我的公司,我们的应用程序在 NodeJS 上运行,并通过多个 EC2 实例和一个 RDS 数据库运行。
我们的应用程序需要一些升级,因为一些依赖项已经很旧了,我们所做的引起我们注意的升级之一是更新我们的数据库库:mysql(从 2.16.0 到 2.17.0)、knex(从 0.12.2 到 0.19 .1) 和书架(0.10.2 至 0.15.1)。
检查更改日志后,不需要更改代码,因此我们很快设法将其上传到我们的临时服务器。
突然,我们的应用程序变得太慢了。加载所有数据需要几秒钟,而我们的主要用户的仪表板在几毫秒内加载到同一台服务器上,需要大约 30 秒。几分钟后,整个应用程序完全没有响应。
为了检查问题是否仅与依赖项升级有关,我们设法将这些降级到工作版本,并且应用程序恢复到正常速度。又升级了,又慢了。
我们已经开始通过 New Relic 分析 RDS 方面是否有问题。什么都没有。没有高峰,没有高 CPU 使用率,没有慢查询或其他任何事情。然后我们来检查连接池,发现适合我们的knex版本使用“generic-pool”,而新版本使用“tarn”。
所以我们开始调试池,发现它被一个指定的查询填满,完全冻结了一段时间,然后开始抛出“TimeoutError: Knex: 获取连接超时。池可能已满”错误。
但是关于填充所有池并冻结的查询最有趣的是它根本不应该生成(并且在使用不存在此问题的过时版本时不会生成)。
在我们的应用程序中,我们只在两种情况下对联系人表执行 SELECT 请求:
首先,很明显,当用户想要列出他们的联系人时:
let contacts = await Contacts.forge({ 'list_owner': udata.id }).fetchAll()
Run Code Online (Sandbox Code Playgroud)
其次,在检查联系人匹配以判断某些信息是否应该对指定用户可见时,取决于信息所有者的隐私设置:
let checkContact = await Contacts.where({
list_owner: target_user,
contact: udata.id
}).fetch()
Run Code Online (Sandbox Code Playgroud)
经过几次 grepping,我可以保证我们的代码库中没有其他地方可以从联系人表中进行 SELECTS。在我们的调试中,我们没有发现未定义的值,并且我们的调查显示查询在之前的代码运行时运行。但是正如您在屏幕截图中看到的,查询 knex 运行没有条件:
select `contacts`.* from `contacts`
Run Code Online (Sandbox Code Playgroud)
我们相信这就是它填满池的原因(因为请求每个用户的联系人是一项相当大的工作),但与此同时,我们看不出为什么 knex 运行这样的查询,如: