使用pg-promise在AWS Lambda中

4

我在使用pg-promise和AWS Lambda时遇到了很多问题。我希望了解如何解决这些问题。

该库建议您创建一个Database对象的实例,然后从模块中导出它。应该只创建一个对象的实例。 例如:

const db = pgp({
  host: process.env.DATABASE_HOST,
  port: process.env.DATABASE_PORT,
  database: process.env.DATABASE_NAME,
  user: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD,
  poolSize: 0,
  poolIdleTimeout: 10,
});
module.exports = db;

我了解这只是一个对象,并没有在此创建连接。只有当您在此db对象上运行任何内容,例如db.query()时,才会延迟创建连接。
由于我们将池大小设置为0,因此只会创建一个连接。这正是我们所需要的,因为在每个lambda函数开始时,我们都需要创建一个连接,然后在lambda完成时关闭连接。
我们面临的问题是:
1. 如何释放连接? AWS Lambda重复使用容器。这意味着它将调用已初始化并重新运行相同功能的相同节点代码,如果在先前运行后不久调用Lambda,则会再次运行相同的函数。 这意味着下一次调用lambda时,db对象将是相同的。在我们的第一个lambda完成后,如果我们调用pgp.end(),文档说连接池将关闭。它还说我们之后不能在同一进程中使用pgp库。但是该库将被使用,因为db对象仍然存在并将在随后的运行中使用。
2. 如何重试获取新连接? AWS Lambda带来的另一个问题是,当您在VPC内运行lambda且您的Postgres实例也在VPC内运行时,需要时间才能解析postgres数据库的DNS。因此,如果尝试连接,可能会出现ENOTFOUND错误。 AWS的建议是重试获取连接。使用pg-promise,我们如何重试获取连接?
我想要实现的方式是:
module.exports.handler = (event, context, callback) => {
 let connection;
 try {
  connection = /*gets connection and retries if it failed the first time*/
  // run db queries and transactions.. etc.
  callback(null, result);
 } finally {
  connection.close();
 }

}

@setu,你能分享一下你最终采用的解决方案吗?谢谢。 - mythicalcoder
1
@mythicalcoder - 抱歉回复晚了。下面是我们如何做到这一点的答案。 - sethu
请查看“pg-promise”的最新聊天记录(聊天链接):https://gitter.im/vitaly-t/pg-promise?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - vitaly-t
2个回答

4

这是我们最终做的事情。

重点是在每个 lambda 开始之前创建一个新连接,然后在从 lambda 返回之前关闭它。

// your lambda entry point
module.exports.handler = (event, context, callback) =>  
getConnection(async (connection) => {
    let result;
    try {
        // work with your connection
    } catch (error) {
    }
    callback(null, result);
})


// db connection 

const getConnection = async (callback) => {
const dbConnection = new DBConnection();
try {
    const connection = await dbConnection.create();
    await callback(connection);
} finally {
    dbConnection.close();
}
};

const MAX_RETRY = 3;

const options = {
// global event notification;
error: (error, e) => {
    if (e.cn) {
    // A connection-related error;
    //
    // Connections are reported back with the password hashed,
    // for safe errors logging, without exposing passwords.
    logger.error('CN:', e.cn);
    logger.error('EVENT:', error.message || error);
    }
},
};

const pgp = require('pg-promise')(options);

const connectionParams = {
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
poolSize: 0,
poolIdleTimeout: 10,
};

const db = pgp(connectionParams);

class DBConnection {

async create() {
    let retry = 0;
    while (retry < MAX_RETRY) {
    try {
        logger.debug(`Acquiring a new DB connection Attempt: ${retry}/${MAX_RETRY}`);
        this.connection = await db.connect({ direct: true });
        break;
    } catch (error) {
        logger.error(`Error occurred while getting DB connection ${error}. Retrying ${retry}/${MAX_RETRY}`);
        retry += 1;
    }
    }

    if (!this.connection) {
    throw Error(`Unable to obtain DB connection after ${MAX_RETRY} retries`);
    }

    return this.connection;
}

close() {
    if (this.connection) {
    logger.debug('Closing DB Connection');
    this.connection.done();
    }
}
}

3
我们如何释放连接?
您不需要手动释放连接,连接池会自动管理。执行查询结束后,连接将返回到池中,并可供下一个请求它的查询使用。
在同时执行多个查询时,请使用任务。请参阅链接: 链接查询
我们如何重试获取新连接?
根据连接需求和最大池大小,连接池会根据需要自动创建新的物理连接。当连接断开时,它会自动重新创建。
如果在AWS lambda中只有单个连接,则可能最好的模式是创建和维护单个全局连接?
如果是这种情况,则Robust Listeners示例可能对您有用。它展示了如何在连接池之外维护单个全局连接以及如何保持其一直存在。
但这更像是最后的选择。我认为仅使用自动连接池应该足够。

谢谢你的答复。在lambda调用结束时,我应该调用pgp.end()吗?pgp.end()会发生什么事情?在不同lambda调用之间共享连接存在问题。这是在lambda中使用连接池时的问题。连接池可能仍然有一个连接。但是,在lambda完成后,节点进程处于“冻结”状态。这可能意味着lambda与db之间的套接字也已关闭。 - sethu
你的问题是否跨越多个Node.js进程?调用pgp.end()会发生什么-文档已经很好地记录了;) - vitaly-t
如果我尝试使用单个全局连接方法,我不确定如何在该单个连接对象上使用事务。 - sethu
我在考虑使用原始的pg库,而不是池,使用客户端。我可以使用client.connect()重试连接。我会失去您构建的事务和查询抽象,这也是我想使用pg-promise的原因。不确定还能做什么。 - sethu
使用 db.connect({direct: true}) 将是相同的,除了您可以访问 pg-promise 的所有高级 API。请参阅 API。在池外创建 Client 正是在这种情况下该方法所做的事情。 - vitaly-t
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接