concurrency - Lambda SQL Server RDS 连接泄漏
问题描述
问题
我在mssql
频繁调用的 Lambda 中使用 v6.2.0(在标准负载下始终有 ~25 次并发调用)。
我似乎在连接池或其他方面遇到问题,因为我一直有大量打开的数据库连接,这压倒了我的数据库(RDS 上的 SQL Server),导致 Lambda 只是超时等待查询结果。
我已经阅读了文档、各种类似的问题、Github 问题等,但对于这个特定问题没有任何效果。
我已经学到的东西
- 我确实了解到可以跨调用进行池化,因为处理函数之外的变量在同一容器中的调用之间共享。这让我觉得我应该只看到运行我的 Lambda 的每个容器的几个连接,但我不知道有多少,所以很难验证。底线是池应该让我避免大量的开放连接,所以有些东西不能正常工作。
- 有几种不同的使用方法
mssql
,我已经尝试了其中的几种。值得注意的是,我尝试使用大值和小值指定最大池大小,但得到了相同的结果。 - AWS 建议您在尝试创建新池之前检查是否已有池。我试过没有用。有点像
pool = pool || await createPool()
- 我知道 RDS Proxy 的存在是为了帮助解决此类情况,但似乎(目前)没有为 SQL Server 实例提供它。
- 我确实有能力稍微减慢我的数据速度,但这会对整个产品的性能产生轻微影响,所以我不想这样做只是为了避免解决数据库连接问题。
- 不加检查,我一次看到多达 700 个与数据库的连接,这让我认为存在某种泄漏,这可能不仅仅是高使用率的合理结果。
- 按照 re:Invent 幻灯片的建议,我没有找到缩短 SQL Server 端连接 TTL 的方法。也许这就是答案的一部分?
代码
'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);
}
问题
请帮助我了解为什么我的连接失控以及如何解决它。对于奖励积分:在 Lambda 上处理高 DB 并发的理想策略是什么?
最终,这项服务需要处理数倍于当前负载的负载——我意识到这将成为一个相当大的负载。我对只读副本或其他读取性能提升措施等选项持开放态度,只要它们与 SQL Server 兼容,而且它们不仅仅是编写正确的数据库访问代码的一种逃避。
如果我能改进这个问题,请告诉我。我知道那里有类似的,但我已经阅读/尝试了很多,但没有找到它们的帮助。提前致谢!
相关资料
解决方案
回答
经过4天的努力,我终于找到了答案。我需要做的就是扩大数据库。代码实际上是好的。
我以大约 15 美元/月的净成本从db.t2.micro
to (或 1 个 vCPU、1GB RAM 到 2 个 vCPU 和 2GB RAM)。db.t3.small
理论
就我而言,数据库可能无法同时处理我所有调用的处理(涉及多个地理计算)。我确实看到 CPU 上升了,但我认为这是高开放连接的结果。当查询速度变慢时,并发调用会随着 Lambda 开始等待结果而堆积起来,最终导致它们超时并且无法正确关闭它们的连接。
比较:
db.t2.micro:
- 200+ 数据库连接(如果你让它继续运行,它会持续上升)
- 50 多个并发调用
- 5000+ 毫秒 Lambda 持续时间,当事情变慢时,空载时约 300 毫秒
db.t3.small:
- 25-35 个数据库连接(持续)
- ~5 次并发调用
- ~33 ms Lambda 持续时间 <- 快十倍!
CloudWatch 仪表板
概括
我认为这个问题让我感到困惑,因为它闻起来不像是容量问题。过去我几乎每次处理高数据库连接时,都是代码错误。在那里尝试过选项后,我认为这是我需要理解的“一些无服务器的神奇陷阱”。最后,它就像更改数据库层一样简单。我的看法是,数据库容量问题可能会以 CPU 和内存使用率高以外的其他方式表现出来,并且高连接可能是代码错误以外的其他原因造成的。
更新(4个月)
这继续运作良好。让我印象深刻的是,将数据库资源翻倍似乎已经提供了 > 2 倍的性能。现在,由于负载(或开发期间的临时错误),数据库连接变得非常高(甚至超过 1k),数据库处理它。我根本没有看到数据库连接超时或数据库因负载而陷入困境的任何问题。自最初撰写本文以来,我添加了几个 CPU 密集型查询来支持报告工作负载,并且它继续同时处理所有这些负载。
自撰写本文以来,我们还为一位客户将此设置部署到生产环境中,它可以毫无问题地处理该工作负载。
推荐阅读
- google-cloud-platform - Dialogflow webhook 设置参数值
- transactions - xdmp:invoke-function 中的多语句对使用的分号给出错误
- ios - swift - UITableViewCells 和 SDWebImage 的问题
- reactjs - 点击搜索按钮后导航栏消失
- git - 无法推送到 github 中的空仓库
- php - 如何判断 @error 控制操作符是否抑制了错误?
- android - 如何在向公众(playstore)发布任何应用程序之前将应用程序发布到 Alpha 组?
- ios - 将 React-native 应用程序添加到 IOS 上的“活动”面板
- r - 复制/复制同一个文件N次
- web-scraping - Scrapy 无法提取文本