升级Knex后具有“超时获取连接”

时间:2019-07-30 01:40:28

标签: mysql node.js amazon-rds knex.js bookshelf.js

在我公司中,我们的应用程序在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:超时获取连接。该池可能已满”错误。

但是对于填充所有池并冻结的查询而言,最有趣的是根本不应该生成该查询(并且在使用不存在此问题的过时版本时也不应该生成该查询)。

enter image description here

在我们的应用程序中,我们仅在两种情况下针对联系人表执行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之后,我可以保证在我们的代码库中没有其他地方可以从contacts表中进行SELECTS选择。在我们的调试中,我们没有发现未定义的值,并且我们的调查显示查询在前一个代码运行时运行。但是,如您在屏幕快照中所见,查询knex运行没有条件:

select `contacts`.* from `contacts`

我们相信这就是它填充池的原因(因为请求每个用户的联系人是一件很艰巨的工作),但与此同时,我们看不到为什么knex运行这样的查询,例如:

  • knex升级后未进行任何代码更改
  • 使用旧版本的knex(我们的生产服务器已启动并使用过时的knex版本运行)时,该问题不存在
  • 我们使用Redis进行了很多缓存(但是无论如何,数据库不会过载,并且旧的Knex版本可以工作)
  • 如果问题确实是缺少条件,那么我们可以早先发现它,因为每个用户都会看到相同的联系人列表。

是什么原因导致这种问题?

2 个答案:

答案 0 :(得分:0)

对于一些可能落在这里的人!

如果没有意义,您最近将nodejs升级到v14!可能是原因!

我遇到了问题,上次安静地拉头发! 在尝试跟踪与众不同之处后,我以某种方式使用了nvm!因为它以前工作过!我想到了它,并使用v13进行了测试!并且再次起作用!

所以要注意!可能就是这样,它可以为您节省大量时间和压力!

问题

Nodejs v14进行了重大更改! pg模块会受到影响! pg开始在connect() call退出进程!

节点v14 +的修复程序

如果您使用的是postgres!使用nodejs v14及更高版本!确保使用版本为pg的驱动程序模块>=8.0.3!更好地升级到最新版本

npm install pg@latest --save

如果您不使用postgres!尝试更新您的数据库驱动程序!可能是一样的!也可以尝试使用nodejs V13。要确认这是同样的问题!

v14中发生了什么

如果喜欢我,您想知道详细信息以及发生了什么事!?

使用节点V14! API发生了一些重大变化!很多事情也改变了!包括Openssl版本!

对于postgres!还有pg模块!问题comment thread对此PR进行了描述:

初始readyState(一个私有/未公开的API,

net.Socket的

pg使用)似乎已从“关闭”更改为“打开” 在节点14中。

很难实现完美的向后兼容性,但我想我 补丁足够近了。

并按照此this diffing

您可以看到this line

中的更改

简而言之! onReady的api已更改为net.Socket! 实施的解决方案是根本不使用onReady!

按照这个

现在,当连接被调用时,Connection总是在其流上调用connect。

在旧版本中,仅当套接字处于closed状态时才调用连接!消除了readyState的使用!

选中https://github.com/nodejs/node/pull/32272

你能理解!

取决于实现!这些核心更改可能会或不会影响很多事情!

Nodejs v14相关更改

因为我想知道变化发生的地方!给你

https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md

也可以检查更改日志:

Sequelize pg adapter will call pg client to create a connection and the promise

详细说明为什么+退出并且没有记录错误

还要提及重大变化!设为pg,使流程退出connect() call。这就是它退出的原因!还有日志记录! 更详细的说明!这是怎么回事!续用postgres方言实现!哪用pg!和pg客户!建立连接!该连接有一个connect事件!当它连接时发出它!并且因为节点v14将流的行为更改为以open开头!流连接已跳过!由于readyState检查(预期为关闭,但改为打开!)!流被视为已连接(其他块)!哪里不行! connect事件将直接发出!当那件事发生时!客户端将调用连接对象的requestSsl()startup()方法!两者都将呼叫this._stream.write。因为流未连接!发生错误!此错误未捕获!然后承诺在驱动程序中!将保持悬而未决!然后事件循环为空!默认情况下,Nodejs的行为只是退出!

您可以查看代码行的逐步操作:

为什么nodejs退出(未解决的承诺)

Node exits without error and doesn't await promise (Event callback)

what happens when a Promise never resolves?

{{3}}

答案 1 :(得分:0)

执行查询后,您必须销毁连接。

 var knex = new Knex(config)
  knex(table)
    .where({ id: 1 })
    .then((result) => {
      callback(output)
    })
    .catch((err) => {
      err.error = true
      callback(err)
    })
    .finally(() => {
      knex.destroy()
    })
})