凌晨两点,内部群弹出一条消息:"订阅数据已经两天没更新了。"两个负责统计的Lambda函数——calculateSubscriptionStats和calculatePartnerNotificationStats——原本只是慢,现在彻底停摆了。CloudWatch日志显示,它们反复撞向Lambda的15分钟硬上限(900,000毫秒),中途被强制终止,偶尔还会触发内存溢出错误。

问题比表面更严重。每次运行都卡在900,000毫秒这道红线,作业不是慢,是正在死亡。由于中途崩溃,下游数据要么过时,要么残缺。数据量持续增长,系统终于越过了悬崖。

打开网易新闻 查看精彩图片

排查后发现三个相互叠加的瓶颈。第一层:全量下载模式。DynamoDB访问层每次返回完整行数据,但高流量表(Addon、Bundle、Partner Notifications)里70%的字节都是下游用不到的字段——oldData、newData、嵌套的审计元数据。DynamoDB单页硬限制1MB,无效字段挤占空间,导致更多分页、更多网络往返、更多JSON解析开销,最终堆内存爆炸。

第二层:每日全表重算。calculatePartnerNotificationStats每次调用都扫描整张表,重新计算uniqueCustomers和monthlyCounts等累计指标。1万行时没问题,百万行且日增时,昨天的数据要连带着之前所有天数再读一遍——永无止境。

第三层:状态无持久化。累计值只在Lambda内存中维护,没有"上一状态"概念。Lambda既是计算节点又是唯一数据源,一旦崩溃,数字就错到下次成功运行为止。

修复分三层推进。第一层全面推行ProjectionExpression,只取必要字段,单页有效数据量提升3倍以上,网络往返和解析开销骤降。这是成本最低的突破口,也为后续改造腾出空间。

第二层引入增量计算架构。不再每日全表扫描,改为基于时间窗口的增量处理,新数据追加更新,历史结果持久化存储。累计指标从"重新发明"变成"接力传递",计算量与当日新增数据成正比,与历史总量脱钩。

第三层将状态外迁。累计值写入持久化存储,Lambda变为无状态纯计算节点。崩溃后可从断点恢复,下游数据可靠性不再绑定于单次运行的生死。

最终效果:同一作业从15分钟超时边缘降至亚秒级完成。更深层的变化是架构认知——Lambda不是数据库,不该承载状态;DynamoDB不是文件系统,扫描操作需要被敬畏。当数据规模跨越临界点,"能跑"和"能活"是完全不同的工程标准。