为什么Mongoose v6和v7相对于v5会导致垃圾回收和事件循环延迟显著增加?

huangapple go评论48阅读模式
英文:

Why do Mongoose v6 and v7 cause a significant increase in garbage collections and event loop latency compared to v5?

问题

背景:
我们在过去一年中一直在尝试将我们的SaaS平台从Mongoose v5迁移到v6,但没有成功。在运行Mongoose 7时也发生了在这里描述的问题。
我们的软件有相当多的Web流量(约每秒25个请求),并且使用单个MongoDB Atlas集群,每个SaaS客户都使用自己的数据库。
该软件托管在Heroku上。

问题:
当我们部署使用Mongoose 6或7的软件版本时,它可以正常运行长达一小时,然后我们开始看到垃圾收集次数增加、垃圾收集所花时间增加以及事件循环延迟增加。这将持续下去,直到服务器的事件循环完全被占用,无法处理更多的传入请求。
内存使用量没有受到影响,所以似乎不是内存泄漏。

代码:

在启动时,我们创建了一个带有300个连接池连接的数据库连接:

global.databaseConnection = await mongoose.createConnection(global.config.dbConnectionString, {
    maxPoolSize: 300,
    minPoolSize: 300
});

当接收到特定客户端的请求时,使用useDb函数从连接池中获取到该特定客户端的数据库连接:

clientDatabaseConnection = global.databaseConnection.useDb(`client-${client.databaseName}`, {
    noListener: true,
    useCache: true
});

然后使用客户端数据库连接来执行请求所需的各种查询,例如:

let clientUserCollection = connection.model("ClientUser", ClientUserSchema, 'users');
let user = await clientUserCollection.findById(userId).exec();

这基本上是应用程序的结构。在v6和v7中与v5相比,可能导致这些性能问题的区别是什么?

我已尝试调整连接池大小,但没有帮助。

英文:

Background:
We have been trying to migrate from Mongoose v5 to v6 in our SaaS platform for the past year with no luck. The issues described here also occurs when running Mongoose 7.
Our software have a decent amount of web traffic(about 25 requests per second) and is using a single MongoDB Atlas cluster where each SaaS client is using its own database.
The software is hosted on Heroku.

The issue:
When we deploy a version of our software that is using Mongoose 6 or 7 it runs fine for up to an hour, then we start to see an increase in number of garbage collections, time spent in garbage collection, and event loop latency. This continues until the servers event loop is completely hogged and cannot handle any more incoming requests.
The memory usage is unaffected, so it doesn't seem to be a memory leak.

The code:

Upon start, we create a database connection with a pool of 300 connections:

global.databaseConnection = await mongoose.createConnection(global.config.dbConnectionString, {
	maxPoolSize: 300,
	minPoolSize: 300
});

When a request for a specific client is received, a connection to that specific clients database is drawn from the connection pool using the useDb-function:

clientDatabaseConnection = global.databaseConnection.useDb(`client-${client.databaseName}`, {
	noListener: true,
	useCache: true
});

The client database connection is then used to do various quesries required by the request, for example:

let clientUserCollection = connection.model<IClientUserModel>("ClientUser", ClientUserSchema, 'users');
let user = await clientUserCollection.findById(userId).exec();

This is basically the structure of the application. What is it that is different in v6 and v7 compared to v5 that could cause these performance implications?

I have tried to adjust the connection pool size, but this doesn't help.

答案1

得分: 1

我们可能遇到了类似的问题。我们也在使用Heroku、MongoDB Atlas和Mongoose。我们为每个客户端都有一个数据库。

过去,我们每个客户端都有一个应用程序。但一年前,我们切换到了多租户架构,一个Heroku应用程序上有多个客户端,每个客户端都有自己的数据库(我们有多个这样的应用程序,每个应用程序上有大约20个客户端)。与架构实施同时,我们还从Mongoose 5切换到了Mongoose 6(现在使用的是7)。

目前,我们使用createConnection并将其存储在地图中,而不是使用useDb,但我们也尝试过使用useDb,并且遇到了相同的问题。

今天,我们通过定期重启我们的Dynos来使其正常工作。这显然是一个临时解决方案。

在我们的情况下,很明显崩溃发生在许多客户端连接到同一个应用程序时。内存似乎增加了,所以我们怀疑是由于我们如何管理Mongoose连接和模型存储引起的问题,但我们尝试切换到内存更多的Dynos时,它在内存限制之前就崩溃了。所以,和你的情况一样,似乎这不是内存泄漏。

编辑

1. populate中的无限循环

所以在我们的情况下,我们发现在某些情况下由嵌套的populate生成的无限任务(循环)。它不会一直中断,而是在我们同时连接到多个数据库时中断。我们不知道它是否与连接在populate进行中断期间或其他情况有关。

populate的代码如下:

ModelA.find(...)
  .populate([
    {
      path: 'propertytOfA',
      populate: {
        path: 'propertyB', // 有鉴别器
        populate: { path: 'propertyC' } // 有鉴别器
      }
    }
  ])

这里重要的事情可能是最后两个集合都有鉴别器。我想知道这是否是Mongoose迁移指南中所谓的递归嵌套鉴别器2

2. maxPoolSize

你说你尝试更改了这个参数,但没有帮助。

嗯,在我们的情况下,解决了无限任务后,我们开始遇到其他问题,比如Client network socket disconnected before secure TLS connection was established,所以我们决定尝试调整maxPoolSize

我们将其设置为5(在Mongoose 5中是默认值,而在Mongoose 6中增加到100),似乎解决了我们的问题。

英文:

We might have a similar issue. We are also using Heroku, MongoDB Atlas and Mongoose. We also have one DB per client.

We used to have one application per client. But one year ago, we switched to a multitenant architecture with several clients on a single heroku application, each with their own databases (we have several of those applications and around 20 clients on each). At the same time we implemented the architecture, we also moved from Mongoose 5 to Mongoose 6 (we are now on 7).

We are currently using createConnection and storing it ourselves in a map instead of useDb, but we also tried with useDb and had the same issue.

Today, we make it work by rebooting our dynos regularly. This is clearly a temporary solution.

In our case, it is pretty clear the crash is happening when many clients have connected to the same application. Memory seemed to increase, so we suspected issues due to how we managed mongoose connections and models storage, but we tried to move to dynos with more memory, and it crashes way before the memory limit. So, as in your case, it seems this is not a memory leak.

EDIT

1. Infinite loop in populate

So in our case, we found an infinite task (loop) in some cases generated by a nested populate. It would not break all the time, but rather when we connect to many databases at the same time. We don't know if it is linked to connections being dropped while the populate is on going or else.

The populate was like this :

ModelA.find(...)
  .populate([
    {
      path: 'propertytOfA',
      populate: {
        path: 'propertyB', // has discriminator
        populate: { path: 'propertyC' } // has discriminator
      }
    }
  ])

The important thing here could be that both of the last collections have discriminators. I wonder if this is what is called recursive embedded discriminators in the mongoose migration guide here.

2. maxPoolSize

You said you try to change this parameter, and it didn't help.

Well in our case, after solving the infinite task, we started seing other issues like for example Client network socket disconnected before secure TLS connection was established' so we decided to try tweaking the maxPoolSize.

We set it to 5 (default on Mongoose 5 that was increased to 100 in Mongoose 6) and it seemed to have solved our issues.

huangapple
  • 本文由 发表于 2023年6月2日 03:51:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76385258.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定