【问题标题】:Intermittent time out issues between MongoDB Atlas and AWS LambdaMongoDB Atlas 和 AWS Lambda 之间的间歇性超时问题
【发布时间】:2020-04-08 19:29:39
【问题描述】:

我对这个问题有点绝望:我们正在为我们的 API 运行 AWS Lambda,它与 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 调用有时会超时。我们试着诊断了一下:

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

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

【问题讨论】:

  • 如果我这个语句 :: In case of successful and unsuccessful invocations 是关于 lambda 调用 - 那么你的意思是不管 lambda 超时或成功 - DB 调用正在发生吗?另外我会说将这个(!cachedDb || !cachedDb.serverConfig.isConnected()) 更改为(!cachedDb && !cachedDb.serverConfig.isConnected()),请尝试测试是否每次都创建连接或者它是否正在使用缓存连接。同样的代码或不同的代码也会发生超时 - 当 callback() 未正确调用时,可能会发生通常的 lambda 超时。
  • 续:在您的情况下,如果使用客户端或通用代码执行的相同查询确实在更少的毫秒内执行,则不会花费太多时间导致 lambda 超时!通常的 lambda 超时时间最长为 15 分钟,但理想的是 15-30 秒,具体取决于操作 - 你的情况已经足够好了..
  • 关于您的第一条评论:是的,true 是从 cachedDb.serverConfig.isConnected() 返回的情况下,数据库将查询结果返回给 lambda,也没有,导致 lmabda 调用卡在对数据库的调用中并在 lambda 设置的超时限制后超时
  • 建议更改为条件对我来说没有意义。我们想检查他们是否还没有缓存连接,或者我们有一个,但它没有连接。在这里使用 AND 是行不通的,例如当你有一个cachedDB 但它没有连接时,条件将是虚假的,不会发生创建新连接的尝试。
  • 是的,完全相同的代码有时会正确执行,有时会超时。当代码在 lambda 上执行良好时,lambda 调用平均需要 20-30 毫秒才能执行。无论我们在 Lambda 中设置多高的超时限制,有时相同的函数都会超时。

标签: node.js mongodb amazon-web-services aws-lambda


【解决方案1】:

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

这是我们正在使用的代码:

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);
  }
}

我注意到的不同之处在于,我们正在检查 MongoClientDb 是否已连接。此后,该代码已被 4+ 版本的驱动程序弃用并 handled internally 使用此代码:

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

可能发生的另一件事是 Lambda 偶尔会被破坏/损坏。这是无法避免的,normal behavior for Lambda,这就是为什么 AWS recommends having a retry strategy 内置到您的程序中。

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

AWS CLI 和 AWS 开发工具包等客户端会在客户端超时、限制错误 (429) 和其他并非由错误请求引起的错误时重试。

【讨论】:

    猜你喜欢
    • 2018-03-21
    • 1970-01-01
    • 2023-02-26
    • 2023-01-18
    • 2020-09-02
    • 2019-11-02
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多