升级 Knex 后出现“获取连接超时”

2024-03-16

在我的公司,我们的应用程序在多个 EC2 实例和一个 RDS 数据库上的 NodeJS 上运行。

我们的应用程序需要一些升级,因为一些依赖项已经相当旧了,我们所做的引起我们注意的升级之一是更新我们的数据库库: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()

其次,在检查联系人匹配以判断某些信息是否应该对指定用户可见时,具体取决于信息所有者的隐私设置:

let checkContact = await Contacts.where({
        list_owner: target_user,
        contact: udata.id
}).fetch()

经过几次 grep 后,我可以保证我们的代码库中没有其他地方从联系人表中进行选择。在我们的调试中,我们没有发现未定义的值,并且我们的调查表明,查询在前面的代码运行时运行。但正如您在屏幕截图中看到的,查询 knex 运行没有条件:

select `contacts`.* from `contacts`

我们相信这就是它填满池的原因(因为请求每个用户的联系人是一项艰巨的工作),但同时,我们无法理解为什么 knex 正在运行这样的查询,如下所示:

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

什么可能导致这样的问题?


对于一些可能会跌倒在这里的人!

如果这没有意义,并且您最近将 Nodejs 升级到了 v14!这可能是原因!

上次我就遇到了这个问题,把头发拉了一会儿! 我在尝试跟踪我所做的不同之后,以某种方式使用了 nvm!因为它之前工作过!我想到了并用v13测试了它!它又起作用了!

所以要注意!这可能会为您节省大量时间和压力!

Problem

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

修复节点 v14+

如果您正在使用postgres!使用 Nodejs v14 及以上版本!确保使用驱动模块pg在版本>=8.0.3!并更好地升级到最新

npm install pg@latest --save

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

v14 中发生了什么

如果像我一样你想知道细节以及发生了什么!?

使用节点 V14! API 发生了一些重大变化!还有很多东西都改变了!包括Openssl版本!

对于postgres!和pg模块!问题是这样描述的comment https://github.com/brianc/node-postgres/issues/2170#issuecomment-617556848根据这个thread https://github.com/brianc/node-postgres/issues/2170:

初始的readyState(一个私有/未记录的API,

net.Socket 的 pg 使用)似乎已从“关闭”更改为“开放” 在节点 14 中。

很难以完美的向后兼容性来修复,但我想我 有一个足够接近的补丁。

并且根据这个PR https://github.com/brianc/node-postgres/pull/2171!

您可以看到以下变化这个差异 https://github.com/brianc/node-postgres/pull/2171/commits/149f48232445da0fb3022044e4f1c53509040ad3

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

并且根据这个

现在,当在其上调用 connect 时,Connection 始终会在其流上调用 connect。

在旧版本中,仅当套接字打开时才调用 connectclosed state! readyState使用被消除!

Check 这条线 https://github.com/charmander/node-postgres/blob/149f48232445da0fb3022044e4f1c53509040ad3/packages/pg/lib/connection.js#L54

你能明白!

取决于实施!许多事情可能会或不会受到这些核心变化的影响!

Nodejs v14相关变更

因为我想看看变化发生在哪里!干得好

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

人们也可以检查更改日志:

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

详细原因+退出并且没有日志记录错误

还要提一下重大变化!制成pg使进程退出connect() call。这就是它退出的原因!日志记录是可见的! 对此更详细!这是怎么发生的! Sequelize 有 postgres 方言实现!哪个用pg!还有pg客户端!创建连接!该连接有一个connect事件!当它连接时它会发出它!而且因为节点 v14 将流的行为更改为以 open 开头!流连接被跳过!因为readyState检查(预计已关闭,但实际上已打开!)!并且流被视为已连接(否则为块)!哪里不是!还有connect事件直接发出!当这种情况发生时!客户端要么会调用requestSsl() or startup()连接对象的方法!两者都会调用this._stream.write。因为流没有连接!发生错误!这个错误没有被catch!那么续集驱动程序中的承诺!将一直悬而未决!然后事件循环变空! Nodejs 默认行为只是退出!

您可以通过代码行看到该步骤:

  • Sequelize pg 适配器将调用 pg 客户端来创建连接和承诺 https://github.com/sequelize/sequelize/blob/febc083adee2cd3e6f24b18a556acdc4c4f50f96/src/dialects/postgres/connection-manager.js#L123
  • pg 客户端在连接对象上调用 connect https://github.com/brianc/node-postgres/blob/aafd8ac64e588e689ed08e7957bc3c91f8fe01e3/packages/pg/lib/client.js#L108
  • PG连接connect()调用并发出connect!认为流已连接是因为 V14 更改 https://github.com/brianc/node-postgres/blob/aafd8ac64e588e689ed08e7957bc3c91f8fe01e3/packages/pg/lib/connection.js#L56
  • PG客户端connect事件被捕获并回调运行!requestSsl() or startup()将运行 https://github.com/brianc/node-postgres/blob/aafd8ac64e588e689ed08e7957bc3c91f8fe01e3/packages/pg/lib/client.js#L115
  • 其中一种方法运行并stream.write将被称为(请求SSL() https://github.com/brianc/node-postgres/blob/aafd8ac64e588e689ed08e7957bc3c91f8fe01e3/packages/pg/lib/connection.js#L140, 启动() https://github.com/brianc/node-postgres/blob/aafd8ac64e588e689ed08e7957bc3c91f8fe01e3/packages/pg/lib/connection.js#L164)
  • 流错误(未捕获)
  • Promise https://github.com/sequelize/sequelize/blob/febc083adee2cd3e6f24b18a556acdc4c4f50f96/src/dialects/postgres/connection-manager.js#L123在sequelize postgres 适配器中!还是没有解决!
  • 事件循环为空 => Nodejs => 退出

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

https://github.com/nodejs/node/issues/22088 https://github.com/nodejs/node/issues/22088

节点退出时没有错误并且不等待承诺(事件回调) https://stackoverflow.com/questions/46914025/node-exits-without-error-and-doesnt-await-promise-event-callback

当 Promise 永远无法解决时会发生什么? https://stackoverflow.com/questions/46966890/what-happens-when-a-promise-never-resolves

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

升级 Knex 后出现“获取连接超时” 的相关文章

随机推荐