【发布时间】: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 中的错误引起的。
标签: mysql node.js amazon-rds knex.js bookshelf.js