【问题标题】:Lambda + pg-promise + transaction = random timeoutLambda + pg-promise + 事务 = 随机超时
【发布时间】:2020-09-05 22:53:11
【问题描述】:

在过去的 48 小时里,我一直在为这个问题苦苦挣扎,在阅读了这里的大量答案、博客、文章、文档等之后......我仍然找不到解决方案!

基本上,我有一个 2 分钟超时的 lambda 函数。根据日志和见解,它可以很好地处理大多数请求,但在尝试执行以下事务时会随机失败并出现“超时”错误。

Lambda 代码(为了便于阅读而截断):


import pgp from 'pg-promise'
import logger from '../lib/logger'

const Database = pgp()

const db = Database({
 connectionString: process.env.DATABASE_URL,
 max: 3,
 idleTimeoutMillis: 10000,
})

db.connect()
 .then(() => logger.info('Successfully connected to the PG database'))
 .catch(err => logger.error({ err }))

export const handler = async (event, context) => {
 logger.info('transaction start...')

 await db.tx(async tx => {
   await tx.none(
     `
     INSERT INTO...`,
     [someValue1, someValue2]
   )
   const updatedRow = await tx.one(
     `
     UPDATE Something...`,
     [somethingId]
   )
   return someFunction(updatedRow)
 })

 logger.info('transaction end...')
}

const someFunction = async (data) => {
 return db.task('someTask', async ctx => {
   const value = await ctx.oneOrNone(
     `SELECT * FROM Something...`,
     [data.id]
   )

   if (!value) {
     return
   }

   const doStuff = async (points) =>
     ctx.none(
       `UPDATE Something WHERE id =.....`,
       [points]
     )

   // increment points x miles
   if (data.condition1) {
     await doStuff(10)
   }

   if (data.condition2) {
     await doStuff(20)
   }

   if (data.condition3) {
     await doStuff(30)
   }
 })
}

我看到事务开始但永远不会结束,所以函数不可避免地被超时杀死。

我阅读了pg-promise 中的整个 wiki,并了解了有关调整、性能、良好做法等的所有内容。但仍然有一些非常错误的地方。

您可以看到,我还更改了池大小和最大超时时间以进行试验,但这并没有解决问题。

有什么想法吗?

谢谢!

【问题讨论】:

    标签: node.js lambda timeout pg-promise


    【解决方案1】:

    很可能您的连接用完了。您没有正确使用它们,同时您将连接限​​制设置为非常低的 3。

    第一个问题,您正在通过调用connect 来测试连接,而没有使用done 来跟踪它,它会永久占用,从而浪费您的初始/主要连接。 请参阅the example here,我们在测试后释放连接。

    第二个问题 - 您在事务中请求新连接(通过在根 db 级别上调用 .task),这对任何环境都不利,但在可用连接很少时尤其重要。

    任务应该是重用当前事务的连接,这意味着您的someFunction 应该需要连接上下文,或者至少将其作为可选参数:

    const someFunction = async (data, ctx) => {
     return (ctx || db).task('someTask', async tx => {
       const value = await tx.oneOrNone(
    
    

    任务 pg-promise 中的事务接口可以完全嵌套,您会看到,将当前连接传播到所有级别。

    另外,我建议使用pg-monitor,以获得良好的查询+上下文可视化。

    【讨论】:

    • 谢谢,但我不明白当你说任务不重用父连接时,按照文档中的说明:“每个任务/事务自动管理连接。当在根数据库对象,连接是从池中分配的,一旦方法的回调完成,连接就会释放回池中。但是,当在另一个任务或事务中调用时,该方法会重用父连接。那么,如果我在一个事务中,为什么任务不知道呢?
    • 即使这些事情是错误的,为什么函数会超时?平均函数运行时间约为 3 秒,但超时时为 120 秒。为什么我的连接用完了?我将其更改为 3 max 只是为了进行测试,因为默认设置对我不起作用。考虑到它是一个供后续请求重用的 lambda 函数,我应该打开多少个连接?
    • if I'm inside a transaction, why does the task not know about it? 因为您以完全分离的方式执行它们。我解释的更改将任务链接到包含事务,以使其了解可用连接。 why is the function timing out? - 因为您的任务正在尝试分配自己的连接,而没有剩下任何连接,所以它卡住了。
    • 好的,所以文档很混乱。它使您认为该任务知道连接上下文,但您必须通过将其作为参数发送来显式地让它知道。它根本无法扩展。我怎么知道某个函数会在事务中被调用?而且,它仍然没有解释为什么我的连接用完了。我将最大值设置为 99999,它会工作吗?我们可以在其他更友好的地方交谈吗,这很痛苦。感谢您的帮助!
    • you have to explicitly let it know by sending it as a parameter。不,你没有,你只是从容器中创建任务/事务,这就是连接可以被继承的方式。 It does not scale at all - 这与缩放有什么关系? How should I know that a certain function will be called inside a transaction - 阅读 API,您将获得任务/事务上下文 ctx 中的所有详细信息。
    猜你喜欢
    • 1970-01-01
    • 2017-12-07
    • 2018-02-19
    • 2017-05-30
    • 1970-01-01
    • 2020-08-06
    • 1970-01-01
    • 2022-12-11
    相关资源
    最近更新 更多