摘要

  • 10 月 9 日的数据中心网络故障与 10 月 10 日的数据库连接池故障是两起不同事件,但都通过共享依赖中断了任务执行。
  • 一些因内部错误永久失败的运行仍停留在 Running,失败处理器和等待中的父函数没有收到通知;客户不得不取消或重放它们。

“持久执行”通常意味着任务可以等待、在中断后恢复,并保留已完成步骤。Inngest 对 10 月 9 日至 11 日事故的说明,让这项承诺背后的条件变得清楚:运行状态存在哪里?哪些队列共用处理路径?内部依赖失败时,系统记录什么状态?谁会知道某个运行已经无法自行继续?

Inngest 描述的是两起事故,而不是同一次停机延长。10 月 9 日 21:05 UTC,一家数据中心供应商失去了其站点与 Inngest 其他站点之间的连接,同时失去出站互联网连接。Inngest 表示,供应商给出的初步原因是在更改 DDoS 防护配置时出错,完整报告尚未收到。连接在 21:49 恢复。10 月 10 日,主数据库前的两个 PgBouncer 连接池先在 14:00、又在 15:30 全部失去数据库连接。大多数账户的运行调度和队列处理因此停滞。临时措施恢复了处理,却又带来间歇性延迟,主要影响 cron 函数,直到 10 月 11 日 04:17 UTC 把调度切回原连接池。次日,网关路由得到改进。两次事故的机制和恢复路径不同。

10 月 9 日的故障展示了局部边界如何扩散成平台问题。Inngest 说平台运行在三个数据中心:账户可能在一个站点处理,但运行状态在另一个站点;中央账户配置数据库及其连接池又位于另一家供应商处。站点间路径断开后,部分执行服务无法查询账户信息或保存运行状态。影响并未止于这些账户。反复失败的工作堆积在共享事件处理管线中,连那些不直接依赖故障站点的账户也遭遇处理延迟。使用专用事件管线的账户没有受到影响。共享队列会把局部依赖的故障传播给更广的工作负载。

第二天暴露的是另一种耦合。Inngest 将连接池崩溃归因于较短的查询期限、客户端取消与 PgBouncer 的交互。有些查询仍在等待连接,调用方期限就已到。驱动关闭客户端连接;之后 PgBouncer 仍执行已被放弃的查询,却关闭数据库连接而不是把它放回池中。新连接一次只能逐个建立,连接被销毁的速度超过补充速度。事故同时碰上每小时起止时刻的 cron 流量,当时查询量约为平时三倍;周末前新上线的一批查询也开始大量失败。重启连接池无法可靠恢复;提高池容量上限也解决不了新连接建立速度这一瓶颈。

临时修复又引入了一条新路径。把服务迁移到数据库供应商旁的连接池后,处理在 16:10 至 16:25 UTC 恢复。但新路径经过网络地址转换网关,其中一些连接会被静默丢弃;查询可能挂起最多 16 分钟才报错,cron 函数的延迟尤其明显。Inngest 在 10 月 11 日 04:17 UTC 把 cron 调度切回原连接池,并于 12 日改进网关路由。缓解措施本身也是拓扑变化:它能移除一个瓶颈,也可能加入另一个较难观察的超时边界。

更关键的问题在状态转换。Inngest 表示,内部故障会消耗函数配置的重试次数。当某个步骤因内部错误最终失败时,一些运行却留在 Running,而没有变为 Failed。因此 onFailure 处理器没有触发,通过 step.invoke 等待它的父函数也没收到失败结果。Inngest 说这些运行不会自行继续,客户需要取消或重放。工作进程恢复,不等于每个被中断的业务流程都到达了终态。

这与常规的工作流恢复不同。Inngest 文档说明,正常恢复时可以复用已完成步骤的结果,并分别重试失败步骤。但事故报告说,客户重放会从运行起点重新开始,已完成步骤可能再次执行。这并不能证明付款、消息或数据库写入真的重复了,却意味着操作人员必须检查追踪记录,确认重复效果是否安全。Inngest 的幂等性文档也提醒,外部服务可能已经接受请求,而编排平台还没记录结果;再次尝试就可能重复调用。检查点能保存编排内部的进度,却不自动保证所有外部效果“只发生一次”。

Inngest 表示已上线多项修复:查询在等待连接期间不再被取消;内部永久故障现在会把运行标记为失败;若此刻无法读取状态,工作会留在队列,待状态可用后恢复。公司还改进了网关路由和状态页更新。更大的架构调整——收拢到单一数据中心——仍在进行中,可靠性工作和服务 SLO 计划持续到年底。报告没有披露最终拓扑或独立的灾难恢复设计。

来源