MongoDB Atlas 和 AWS Lambda 之间的间歇性超时问题



我在这个问题上有点绝望:我们正在为我们的API运行AWS Lambda,该API与MongoDB Atlas(M20(上的MongoDB集群进行通信。为了防止在每次 Lambda 调用时创建新连接,我们遵循以下模式:https://docs.atlas.mongodb.com/best-practices-connecting-to-aws-lambda/在 Lambda 容器的生命周期内缓存连接。我们略有不同:

async function getProdDB() {
const url = `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASSWORD}@xxxxx-yyyy.zzzzz.net?retryWrites=true`
if (!cachedDb || !cachedDb.serverConfig.isConnected()) {
cachedClient = await MongoClient.connect(
url,
{ useNewUrlParser: true, useUnifiedTopology: true }
)
cachedDb = cachedClient.db(process.env.DB_NAME)
}
return cachedDb
}

这也检查我们是否已连接。现在这在 98% 的时间内都有效,但我们的 Lambda 调用时不时地超时。我们试图诊断一下:

我们将 Lambda
  1. 的超时限制从 6 秒更改为 30 秒/60 秒,并且 Lambda 函数仍会时不时地超时。Mongo从未抛出错误,由于超时错误,总是Lambda完成调用
  2. 在调用成功和不成功的情况下,cachedDB.serverConfig.isConnected()返回 rue'
  3. 导致超时的业务逻辑部分是对MongoDB本身的查询,常见的MongoDB操作,如对非常小的集合(顶部100个文档(的findOneupdateOne
  4. 我们尝试将 NodeJS
  5. 上的 MongoDB 驱动程序从3.3.1升级到3.3.5以下 https://github.com/Automattic/mongoose/issues/8180(虽然我们没有使用 mongoose,只是官方的 mongodb NodeJS 驱动程序(,问题仍然存在
  6. 我们尝试直接通过使用相同版本的驱动程序的 NodeJS 脚本查询我们的 MongoDB 集群,在数千个查询中,没有发生一个超时问题。所以我们得出结论,问题不在于我们的集群本身,而在于连接。
  7. 经常调用的函数不会比定期调用但频率较低的函数更频繁地超时。看起来,我们与MongoDB的缓存连接以某种方式变得过时,即使在调用isConnected()时返回true并且在Lambda容器在没有调用的情况下保持打开状态一段时间后无法重用。我们使用默认超时:https://scalegrid.io/blog/understanding-mongodb-client-timeout-options/
  8. 检查了Atlas上的MongoDB日志条目 - 那里没有任何可疑之处
  9. 停止跟踪数据库连接解决了问题,但使大多数 API 调用慢了 2-3 倍,我们仍然想了解问题的根源

是否有人遇到过类似的问题,或者可以建议我们如何有效地调试此问题?

我们公司目前使用相同的架构(Lambda -> MongoDB Atlas Cluster(,拥有超过500个lambda,我们没有看到连接超时的任何问题。我们使用的是 Node.js 驱动程序的 3.6 版。

以下是我们使用的代码:

public static async connect(url?: string): Promise<Db> {
/** If MongoDB is already connected, return that */
if (this.cachedDb && this.client && this.client.isConnected())
return Promise.resolve(this.cachedDb);
/** Check for MongoURL in env if not provided */
const mongoUrl: string = url || process.env.MONGO_CONNECTION_URL || '';
this.client = await MongoClient.connect(mongoUrl, {
useUnifiedTopology: true,
useNewUrlParser: true,
ignoreUndefined: true,
});
this.cachedDb = this.client.db();
return Promise.resolve(this.cachedDb);
}

然后,我们稍后在同一MongoClient上使用缓存的数据库和客户端:

/**
* MongoDB Client Class
*/
class MongoDbClient {
private static cachedDb: Db | null = null;
private static client: MongoClient | null = null;
/**
* Connects to a MongoDB database instance
*
* @param url The MongoDB connection string
*
* @returns An instance of a MongoDB database
*/
public static async connect(url?: string): Promise<Db> {
/** If MongoDB is already connected, return that */
if (this.cachedDb && this.client && this.client.isConnected())
return Promise.resolve(this.cachedDb);
/** Check for MongoURL in env if not provided */
const mongoUrl: string = url || process.env.MONGO_CONNECTION_URL || '';
this.client = await MongoClient.connect(mongoUrl, {
useUnifiedTopology: true,
useNewUrlParser: true,
ignoreUndefined: true,
});
this.cachedDb = this.client.db();
return Promise.resolve(this.cachedDb);
}
/**
* Retrieves the first document that matches a filter condition
*
* @param collectionName The name of the collection to search
* @param filter The filter to search for
* @param options MongoDB find one option to include
*
* @returns The first document that matches the filter condition
*/
public static async findOne<T>(
collectionName: string,
filter: FilterQuery<T> = {},
options: FindOneOptions<T extends T ? T : T> = defaultDocumentOptions
): Promise<T | null> {
const db = await this.connect();
return db.collection<T>(collectionName).findOne<T>(filter, options);
}
}

我注意到的不同之处在于我们正在检查MongoClient是否连接与Db。此后,版本 4+ 中的驱动程序使用以下代码弃用并进行了内部处理:

// If a connection already been established, we can terminate early
if (mongoClient.topology && mongoClient.topology.isConnected()) {
return callback(undefined, mongoClient);
}

可能发生的另一件事是 Lambda 偶尔会被破坏/损坏。对于 Lambda 来说,这是无法预防的正常行为,这就是 AWS 建议在您的程序中内置重试策略的原因。

请注意,即使是 AWS 开发工具包也有根据其文档的重试策略:

AWS

CLI 和 AWS 开发工具包等客户端会在客户端超时、限制错误 (429( 以及非错误请求导致的其他错误时重试。

最新更新