在我的公司里,我们的应用程序在多个EC2实例和一个RDS数据库上运行NodeJS。
我们的应用程序需要一些升级,因为一些依赖已经很旧了,我们做的升级之一引起了我们的注意,就是更新我们的数据库库:mysql(从2.16.0到2.17.0),knex(从0.12.2到0.19.1)和bookshelf(从0.10.2到0.15.1)。
在检查了变更日志后,不需要修改代码,所以我们很快就把它上传到了我们的暂存服务器上。
突然,我们的应用程序变得太慢了。所有的数据都需要几秒钟的时间来加载,而我们的主用户的仪表盘,在同一台服务器上只需要几毫秒的时间就可以加载,却需要30秒左右。几分钟后,整个应用程序完全没有反应。
为了检查问题是否只涉及到依赖升级,我们已经设法将这些降级到工作版本,应用程序恢复到正常速度。再次升级,又慢了。
我们已经开始通过New Relic分析是不是RDS那边出了问题。完全没有。没有出现峰值,没有出现CPU使用率高的情况,也没有出现查询慢的情况,也没有出现其他情况。然后我们来检查连接池,发现我们用的knex版本用的是 "generic-pool",而新版本用的是 "tarn"。
于是我们开始调试池子,发现池子被指定的查询填满,完全冻结一段时间,然后开始抛出 "TimeoutError: Knex: 超时获取连接。The pool is probably full "错误。
但是,最有趣的是,填满所有池子然后冻结的查询,根本就不应该生成(使用过时的版本不面临这个问题时,也不会生成)。
在我们的应用中,我们只在两种情况下对联系人表进行SELECT请求。
首先,很明显,当用户想列出他们的联系人时,我们才会对联系人表进行SELECT请求。
let contacts = await Contacts.forge({ 'list_owner': udata.id }).fetchAll()
第二,当检查联系人匹配时,根据信息所有者的隐私设置,来判断某些信息是否应该对特定用户可见。
let checkContact = await Contacts.where({
list_owner: target_user,
contact: udata.id
}).fetch()
经过多次grepping,我可以保证,在我们的代码库中,没有其他地方可以从联系人表中选择。在我们的调试中,我们没有发现未定义的值,我们的调查显示,查询运行时,前者的代码运行。但是从截图中可以看到,knex运行的查询没有条件。
select `contacts`.* from `contacts`
我们相信这就是为什么它能填满池子的原因(因为请求每个用户的联系人是一项相当大的工作),但同时,我们也不明白为什么knex会运行这样的查询,因为:
是什么原因导致了这样的问题?
对于有些人来说,可能属于这里!
如果没有意义,你最近把nodejs升级到了v14! 可能是这个原因!
我有这个问题,并拉我的头发安静的时间最后一次!和我使用的是nvm,所以不知何故后,试图跟踪我做了什么不同的! 因为它之前是工作的! 它来到我的脑海里,并用v13测试它! 然后它又工作了!
所以要注意!可能就是这个东西,这可能会节省你巨大的时间和压力!
你必须摧毁连接,一旦查询执行。
var knex = new Knex(config)
knex(table)
.where({ id: 1 })
.then((result) => {
callback(output)
})
.catch((err) => {
err.error = true
callback(err)
})
.finally(() => {
knex.destroy()
})
})