升级 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 connectionconnect()
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:获取连接超时。游泳池可能已满