Lambda SQL Server RDS 连接泄漏

tra*_*ark 5 concurrency amazon-rds aws-lambda

问题

mssql我在频繁调用的 Lambda 中使用v6.2.0(在标准负载下始终有约 25 个并发调用)。

我似乎在连接池或其他方面遇到了问题,因为我一直有大量打开的数据库连接,这些连接淹没了我的数据库(RDS 上的 SQL Server),导致 Lambda 在等待查询结果时超时。

我已经阅读了文档、各种类似的问题、Github 问题等,但对这个特定问题没有任何作用。

我已经学到的东西

  • 我确实了解到,由于处理程序函数外部的变量在同一容器中的调用之间共享,因此跨调用可以进行池化。这让我觉得我应该只看到运行 Lambda 的每个容器的几个连接,但我不知道有多少连接,因此很难验证。最重要的是,池应该阻止我拥有大量的开放连接,因此有些东西无法正常工作。
  • 有几种不同的使用方法mssql,我已经尝试了其中几种。值得注意的是,我尝试使用大值和小值指定最大池大小,但得到了相同的结果。
  • AWS 建议您在尝试创建新池之前检查是否已有池。我尝试过,但没有成功。这就像pool = pool || await createPool()
  • 我知道 RDS Proxy 的存在是为了帮助解决此类情况,但目前似乎尚未为 SQL Server 实例提供它。
  • 我确实有能力稍微减慢我的数据速度,但这对整个产品的性能有轻微影响,所以我不想这样做只是为了避免解决数据库连接问题。
  • 如果不加以检查,我会同时看到多达 700 个与数据库的连接,这让我认为存在某种泄漏,这可能不仅仅是高使用率的合理结果。
  • 我没有找到一种方法来缩短 SQL Server 端连接的 TTL,正如 re:Invent 幻灯片所建议的那样。也许这就是答案的一部分? 在此输入图像描述

代码

'use strict';

/* Dependencies */
const sql = require('mssql');
const fs = require('fs').promises;
const path = require('path');
const AWS = require('aws-sdk');
const GeoJSON = require('geojson');

AWS.config.update({ region: 'us-east-1' });
var iotdata = new AWS.IotData({ endpoint: process.env['IotEndpoint'] });

/* Export */

exports.handler = async function (event) {

    let myVal= event.Records[0].Sns.Message;

    // Gather prerequisites in parallel
    let [
        query1,
        query2,
        pool
    ] = await Promise.all([
        fs.readFile(path.join(__dirname, 'query1.sql'), 'utf8'),
        fs.readFile(path.join(__dirname, 'query2.sql'), 'utf8'),
        sql.connect(process.env['connectionString'])
    ]);

    // Query DB for updated data
    let results = await pool.request()
        .input('MyCol', sql.TYPES.VarChar, myVal)
        .query(query1);

    // Prepare IoT Core message
    let params = {
        topic: `${process.env['MyTopic']}/${results.recordset[0].TopicName}`,
        payload: convertToGeoJsonString(results.recordset),
        qos: 0
    };

    // Publish results to MQTT topic
    try {
        await iotdata.publish(params).promise();
        console.log(`Successfully published update for ${myVal}`);

        //Query 2
        await pool.request()
            .input('MyCol1', sql.TYPES.Float, results.recordset[0]['Foo'])
            .input('MyCol2', sql.TYPES.Float, results.recordset[0]['Bar'])
            .input('MyCol3', sql.TYPES.VarChar, results.recordset[0]['Baz'])
            .query(query2);
        
    } catch (err) {
        console.log(err);
    }
};

/**
 * Convert query results to GeoJSON for API response
 * @param {Array|Object} data - The query results
 */
function convertToGeoJsonString(data) {
    let result = GeoJSON.parse(data, { Point: ['Latitude', 'Longitude']});
    return JSON.stringify(result);
}
Run Code Online (Sandbox Code Playgroud)

问题

请帮助我理解为什么我的连接失控以及如何修复它。对于奖励积分:在 Lambda 上处理高数据库并发性的理想策略是什么?

最终,该服务需要处理数倍于当前负载的负载——我意识到这会成为一个相当紧张的负载。我愿意接受只读副本或其他读取性能提升措施等选项,只要它们与 SQL Server 兼容,并且它们不仅仅是编写正确的数据库访问代码的借口。

如果我可以改进这个问题,请告诉我。我知道那里有类似的东西,但我已经阅读/尝试了很多,但没有发现它们有帮助。提前致谢!

相关材料

tra*_*ark 3

回答

经过4天的努力,我终于找到了答案。我所需要做的就是扩展数据库。该代码实际上按原样就可以了。

我从 到db.t2.microdb.t3.small或 1 个 vCPU、1GB RAM 到 2 个 vCPU 和 2GB RAM),净成本约为 15 美元/月。

理论

就我而言,数据库可能无法同时处理我的所有调用的处理(这涉及多个地理计算)。我确实看到 CPU 上升,但我认为这是高开放连接的结果。当查询速度减慢时,并发调用会随着 Lambda 开始等待结果而堆积,最终导致它们超时并且无法正确关闭连接。

比较:

db.t2.micro:

  • 200+ 数据库连接(如果保持运行,则会持续增加)
  • 50+并发调用
  • 当速度减慢时,Lambda 持续时间为 5000+ 毫秒,无负载时约为 300 毫秒

db.t3.小:

  • 25-35 个数据库连接(持续)
  • ~5 个并发调用
  • ~33 毫秒 Lambda 持续时间 <-- 快十倍!

云观察仪表板

云观察仪表板

概括

我认为这个问题让我感到困惑,因为它听起来不像是容量问题。过去,几乎每次我处理高数据库连接时,都是代码错误。在尝试了那里的选项后,我认为这是我需要理解的“无服务器的一些神奇陷阱”。最后就像更改数据库层一样简单。我的结论是,数据库容量问题可以通过高 CPU 和内存使用率以外的其他方式表现出来,而高连接数可能是代码错误之外的其他原因造成的。

更新(4个月后)

这仍然非常有效。令我印象深刻的是,将数据库资源加倍似乎可以提供超过 2 倍的性能。现在,当由于负载(或开发期间的临时错误),数据库连接变得非常高(甚至超过 1k)时,数据库会处理它。我没有看到任何数据库连接超时或数据库因负载而陷入困境的问题。自最初撰写本文以来,我添加了几个 CPU 密集型查询来支持报告工作负载,并且它继续同时处理所有这些负载。

自撰写本文以来,我们还为一位客户将此设置部署到生产中,并且它可以毫无问题地处理该工作负载。