如何在并发AWS Lambda函数中管理Postgres连接?

时间:2019-04-02 16:32:06

标签: node.js postgresql amazon-web-services lambda

有经验与Postgres构建并发AWS Lambda函数的人吗?

我必须构建一个lambda cron,它将成千上万张发票提取到Postgres数据库中。我必须为每个发票同时调用提取Lambda函数。问题在于,因为它是并发的,所以摄取函数的每个实例都将创建与数据库的连接。这意味着,如果我要提取1000张发票,则每张发票都将调用一个lambda函数,该函数将创建1000个数据库连接。这将耗尽Postgres可以处理的最大连接数。调用的lambda函数的某些实例将返回错误,表明没有更多可用连接。

有什么技巧可以解决这个问题?

以下是我的代码片段:

ingestInvoiceList.js

var AWS = require('aws-sdk');
var sftp = require('ssh2-sftp-client');

var lambda = AWS.Lambda();

exports.handler = async (evenrt) => {
   ...

        let folder_contents;
        try {
            // fetch list of Zip format invoices
            folder_contents = await sftp.list(client_folder);
        } catch (err) {
            console.log(`[${client}]: ${err.toString()}`);
            throw new Error(`[${client}]: ${err.toString()}`);
        }

        let invoiceCount = 0;

        let funcName = 'ingestInvoice';


        for (let item of folder_contents) {
            if (item.type === '-') {
                let payload = JSON.stringify({
                    invoice: item.name
                });
                let params = {
                    FunctionName: funcName,
                    Payload: payload,
                   InvocationType: 'Event'
                };


                //invo9ke ingest invoice concurrently
                let result = await new Promise((resolve) => {
                    lambda.invoke(params, (err, data) => {
                        if (err) resolve(err);
                        else resolve(data);
                    });
                });

                console.log('result: ', result);

                invoiceCount++;
            }
        }
   ...
}

ingestInvoice.js

var AWS = require('aws-sdk');
var sftp = require('ssh2-sftp-client');
var DBClient = require('db.js')l

var lambda = AWS.Lambda();

exports.handler = async (evenrt) => {
   ...

   let invoice = event.invoice;
   let client = 'client name';

   let db = new DBClient();

   try {
        console.log(`[${client}]: Extracting documents from ${invoice}`);

        try {
            // get zip file from sftp server
            await sftp.fastGet(invoice, '/tmp/tmp.zip', {});
        } catch (err) {
            throw err;
        }


        let zip;
        try {
            // extract the zip file...
            zip = await new Promise((resolve, reject) => {
                fs.readFile("/tmp/tmp.zip", async function (err, data) {
                    if (err) return reject(err);

                    let unzippedData;
                    try {
                        unzippedData = await JSZip.loadAsync(data);
                    } catch (err) {
                        return reject(err);
                    }

                    return resolve(unzippedData);
                });
            });

        } catch (err) {
            throw err;
        }

        let unibillRegEx = /unibill.+\.txt/g;

        let files = [];
        zip.forEach(async (path, entry) => {
            if (unibillRegEx.exec(entry.name)) {
                files['unibillObj'] = entry;
            } else {
                files['pdfObj'] = entry;
            }
        });


        // await db.getClient().connect();
        await db.setSchema(client);
        console.log('Schema has been set.');

        let unibillStr = await files.unibillObj.async('string');

        console.log('ingesting ', files.unibillObj.name);

        //Do ingestion queries here...
        ...

        await uploadInvoiceDocsToS3(client, files);

    } catch (err) {
        console.error(err.stack);
        throw err;
    } finally {
        try {
            // console.log('Disconnecting from database...');
            // await db.endClient();
            console.log('Disconnecting from SFTP...');
            await sftp.end();
        } catch (err) {
            console.log('ERROR: ' + err.toString());
            throw err;
        }
    }
   ...
}

db.js

var { Pool } = require('pg');

module.exports = class DBClient {
    constructor() {
    this.pool = new Pool();
   }

   async setSchema(schema) {
      await this.execQuery(`SET search_path TO ${schema}`);
   }

   async execQuery(sql) {
      return await this.pool.query(sql);
   }
}

任何答案将不胜感激,谢谢!

3 个答案:

答案 0 :(得分:0)

我看到了两种处理方法。最终,这取决于您要处理该数据的速度。

  1. 将Lambda的并发设置更改为“保留并发: Reserve Concurrency

这将允许您限制并发Lambda的运行数量(有关更多详细信息,请参见this link)。

  1. 更改代码以将要完成的工作排队在SQS队列中。从那里,您将必须创建另一个 Lambda以由队列触发并根据需要对其进行处理。该Lambda可以决定一次拉出队列的数量,并且可能也需要限制并发性。但是您可以将其调整为,例如,最长运行15分钟,这可能足以清空队列并且不会杀死数据库。或者,如果您的最大并发值为100,那么您将快速处理而不会杀死数据库。

答案 1 :(得分:0)

首先,您必须在处理程序外部初始化连接,因此,每次执行温暖的lambda时,它都不会打开新的连接:

AppbarLayout

如果是node-pg,则有一个跟踪所有空闲连接的软件包,如有必要,请终止它们,并在出现错误或const db = new DBClient(); exports.handler = async (event) => { ... await db.query(...) ... } 的情况下重试: https://github.com/MatteoGioioso/serverless-pg 具有退避功能的任何其他自定义实现的重试机制也将起作用。

MySQL也有一个:https://github.com/jeremydaly/serverless-mysql

答案 2 :(得分:0)

如今在 AWS 上可以考虑解决此问题的一个很好的解决方案是 RDS Proxy,它充当 lambda 和数据库之间的透明代理:

<块引用>

Amazon RDS 代理允许应用程序汇集和共享与数据库建立的连接,从而提高数据库效率、应用程序可扩展性和安全性。