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

Posted

技术标签:

【中文标题】升级 Knex 后出现“获取连接超时”【英文标题】:Having "Timeout acquiring a connection" after upgrading Knex 【发布时间】:2019-12-07 08:25:15 【问题描述】:

在我的公司,我们的应用程序通过多个 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()

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

select `contacts`.* from `contacts`

我们认为这就是它填满池的原因(因为请求每个用户的联系人是一项艰巨的工作),但同时,我们看不出 knex 为何运行这样的查询,因为:

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

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

【问题讨论】:

您是否在 Knex/MySQL 中创建了问题?似乎它可能是一个错误。 书架也可能发生了某种变化。您应该尝试找出导致此问题的确切依赖关系,否则很难猜测。 是的,我会将所有模块回滚并一次独立升级一个,以确定是哪个模块导致了问题。 谢谢各位。我们将按模块进行,并获取可能的错误报告所需的信息。会及时通知大家。 您需要隔离导致该行为的原因,并从代码的这些部分创建单个文件测试用例。这样一来,代码的问题就很明显了。如果没有隔离,我确信这至少不是由 knex 中的错误引起的。 【参考方案1】:

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

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

我有问题,上次拉了我的头发安静了一次! 在尝试跟踪我所做的不同之后,我以某种方式使用了 nvm!因为它以前工作过!我想到了它并使用 v13 对其进行了测试!它又成功了!

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

问题

Nodejs v14 做出了重大改变!并且pg 模块受到影响! pg 开始在connect() call 处退出进程!

修复节点 v14+

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

npm install pg@latest --save

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

v14 发生了什么

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

使用节点 V14! api上发生了一些重大变化!也改变了很多东西!包括Openssl版本!

对于postgres!还有pg 模块!这个问题在这个comment 中描述过这个thread:

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

pg uses) 的 net.Socket 似乎已从“关闭”变为“打开” 在节点 14 中。

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

按照这个PR!

你可以在this diffing看到变化

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

按照这个

Connection 现在总是在其流上调用 connect 时调用它。

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

查看this line

你可以理解!

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

Nodejs v14 相关改动

因为我想看看变化发生在哪里!给你

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

也可以查看更改日志:

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

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

还要提一下重大变化!使pg 使进程退出connect() call。这就是让它退出的原因!并且要看到日志记录! 对此更详细!这是怎么回事! Sequelize 有 postgres 方言实现!哪个用pg!还有pg客户端!创建连接!该连接有一个connect 事件!当它连接时它会发出它!并且因为 node v14 将流的行为更改为以 open!流连接被跳过!因为readyState 检查(预计关闭,但改为打开!)!并且流被视为已连接(其他块)!哪里不是!并且直接发出connect 事件!什么时候发生!客户端将调用连接对象的requestSsl()startup() 方法!两者都会打电话给this._stream.write。因为流没有连接!发生错误!此错误未捕获!然后是sequelize driver中的promise!将一直悬而未决!然后事件循环变空! Nodejs 默认行为只是退出!

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

Sequelize pg adapter will call pg client to create a connection and the promise pg client call connect on a connection object pg connection connect() call and emit connect! Thinking the stream is connected because of V14 change pg client connect event catched and callback run! requestSsl() or startup() will be run get run 和stream.write 方法之一将被调用(requestSsl(), startup()) 流错误(未捕获) Promise 在 sequelize postgres 适配器中!仍未解决! 事件循环为空 => Nodejs => 退出

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

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

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

what happens when a Promise never resolves?

【讨论】:

【参考方案2】:

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

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

【讨论】:

不,你根本没有

以上是关于升级 Knex 后出现“获取连接超时”的主要内容,如果未能解决你的问题,请参考以下文章

AWS RDS / EC2:TimeoutError:Knex:获取连接超时。游泳池可能已满

空手道 DSL:获取连接超时错误

Druid配置参数详解-maxWait

Knex 与 Heroku Postgres 连接出现错误?

破案了,同事升级了MySQL驱动8.0,导致应用大量超时

插入后 Knex 解析空集