跳到主要内容

论文解读:Serverless in the Wild — Azure Functions 生产负载特征与冷启动优化

· 阅读需 19 分钟
Zhiyuan Pan
Blog Author

解读 USENIX ATC 2020 论文,基于 Azure Functions 大规模生产数据分析 Serverless 负载特征,并提出冷启动优化策略。

论文:Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider 作者:Mohammad Shahrad, Rodrigo Fonseca, Inigo Goiri 等 (Microsoft Azure & Microsoft Research) 会议:USENIX ATC 2020 数据集:https://github.com/Azure/AzurePublicDataset


1. 这篇论文要解决什么问题?

Serverless / FaaS(Function as a Service)已经成为云计算的主流范式之一。用户只需上传函数代码,云平台负责资源分配、容器管理和按量计费。但对于云提供商来说,有一个核心矛盾:既要让用户感觉函数"随时可用"(低延迟),又要尽量少占资源(低成本)

这个矛盾的焦点就是 冷启动(Cold Start) 问题。当函数的运行环境没有被预先加载到内存中时,平台需要临时拉起容器、加载运行时和用户代码,这个过程耗时可达数百毫秒甚至数秒——而大量函数本身的执行时间也就这么长。换句话说,冷启动延迟和函数执行时间在同一个数量级,这对用户体验的影响是致命的。

然而,在此之前,没有任何来自云提供商视角的大规模生产 FaaS 负载特征分析。之前的研究要么是用 benchmark 函数从外部去"逆向工程"平台行为,要么是在原型系统上做实验。没有人真正回答过:真实的 Serverless 工作负载长什么样?调用频率分布如何?执行时间分布如何?不同触发器的行为有什么差异?

这篇论文就是要填补这个空白:首次从提供商内部视角,全面刻画 Azure Functions 的生产 FaaS 负载特征,并基于这些洞察设计一个实用的冷启动管理策略。


2. 数据集与研究方法

2.1 数据收集

研究团队采集了 Azure 整个基础设施上 2019年7月15日至28日(两周) 的全量函数调用数据,包含四类数据集:

数据集内容粒度
调用计数每个函数的调用次数每分钟 bin
触发器类型每个函数的触发器静态属性
执行时间平均/最小/最大执行时间及样本数每30秒区间,每 worker
内存使用已分配/驻留内存的平均/最小/最大每5秒采样,每分钟聚合

2.2 数据局限性

  • 调用计数按1分钟 bin 统计,无法精确重建亚分钟级的到达间隔(Inter-Arrival Time),但对本文的分析粒度已足够。
  • 出于保密原因,不能公布函数和调用的绝对总数,但分布特征是完整的。
  • 团队公开了脱敏后的生产 trace:https://github.com/Azure/AzurePublicDataset

2.3 研究方法论

论文的方法论分两大块:

  1. 负载特征刻画(Characterization):分析函数/应用/触发器的分布、调用频率与模式、执行时间、内存使用等维度,提取对冷启动管理有意义的观察结论。
  2. 策略设计与评估:基于特征分析的洞察,设计 hybrid histogram 策略,通过仿真(基于真实 trace)和在 Apache OpenWhisk 上的实际实验进行评估。

3. 负载特征分析:核心发现

3.1 函数、应用与触发器

应用与函数的关系:在 Azure Functions 中,应用(Application)是资源分配和调度的基本单位,一个应用可以包含多个函数。数据显示:

  • 54% 的应用只有 1 个函数
  • 95% 的应用最多 10 个函数
  • 仅约 0.04% 的应用超过 100 个函数

触发器类型分布(这是理解调用模式的关键):

触发器类型占函数比例占调用比例特点
HTTP55.0%35.9%最常见,到达间隔不规则
Queue15.2%33.5%调用量高于函数占比,消息驱动
Timer15.6%2.0%函数多但调用少(大多 ≤1次/分钟),高度周期性
Event2.2%24.7%函数少但调用量巨大,自动化/批处理场景
Orchestration6.9%2.3%Azure Durable Functions
Storage2.8%0.7%数据库/文件系统变更触发
Others2.2%1.0%-

一个关键洞察:不同触发器的"函数占比"和"调用占比"严重不对称。Event 触发器仅占 2.2% 的函数,却贡献了 24.7% 的调用量;Timer 触发器占 15.6% 的函数,却仅贡献 2% 的调用量。这意味着一刀切的资源管理策略注定低效。

触发器组合:64% 的应用至少有一个 HTTP 触发器,29% 至少有一个 Timer 触发器。43% 的应用只用 HTTP,13% 只用 Timer。大约 15.8% 的应用同时有 Timer 和其他触发器。

3.2 调用频率与模式

这是论文中最震撼的发现之一:

  • 调用频率跨越 8 个数量级:最不活跃的函数两周内可能只被调用一次,最活跃的函数每秒被调用成千上万次。
  • 45% 的应用平均每小时被调用不超过 1 次
  • 81% 的应用平均每分钟被调用不超过 1 次
  • 但是,最活跃的 18.6% 的应用贡献了 99.6% 的总调用量

这是一个极端的长尾分布。对于云提供商来说,这意味着绝大多数应用的资源保持成本相对于其实际使用时间来说极其昂贵。

时间模式:整体调用量呈现清晰的日夜和工作日/周末周期性,但有约 50% 的调用量构成一个不随时间变化的基线负载。

3.3 到达间隔时间(IAT)的变异性

论文用变异系数(CV = 标准差/均值) 来衡量每个应用调用间隔的规律性:

  • CV = 0:完全周期性(理想的 Timer 触发器)
  • CV = 1:泊松过程(无记忆的随机到达)
  • CV > 1:到达间隔高度不规则

实际结果:

  • 只有约 50% 的纯 Timer 应用 CV = 0(多 Timer 组合会提高 CV)
  • 约 10% 的无 Timer 应用 CV 接近 0(外部周期性调用者,如 IoT 设备)
  • 约 40% 的应用 CV > 1,意味着到达间隔的波动性远超泊松过程
  • 只有很小比例的应用 CV 接近 1(纯泊松)

结论:真实 FaaS 负载的到达模式远比简单的周期性或泊松模型复杂,这给冷启动预测带来了巨大挑战。

3.4 函数执行时间

  • 50% 的函数平均执行时间 < 1 秒
  • 50% 的函数最大执行时间 < 3 秒
  • 75% 的函数最大执行时间 < 10 秒
  • 90% 的函数最大执行时间 < 60 秒
  • 执行时间分布很好地拟合对数正态分布(log mean = -0.38, sigma = 2.36)

对比参照:Azure 上 63% 的 VM 生命周期超过 15 分钟,而 FaaS 函数绝大多数在秒级完成。这意味着 FaaS 对平台快速拉起资源的要求远高于传统 VM 场景

核心推论:函数执行时间与冷启动时间在同一数量级 -- 这让冷启动优化成为 FaaS 性能的关键瓶颈。

3.5 内存使用

  • 90% 的应用最大内存 < 400MB
  • 50% 的应用最大内存 < 170MB
  • 前 90% 的应用之间存在约 4x 的内存使用变异
  • 调用频率与内存使用之间没有强相关性
  • 内存使用与函数执行时间之间也没有强相关性

3.6 三大核心 Takeaway

论文从冷启动和资源管理角度总结了三个关键观察:

  1. 执行时间极短:75% 的函数最大执行时间 < 10 秒,与冷启动时间在同一量级,必须减少冷启动或加速冷启动。
  2. 绝大多数应用调用不频繁:81% 的应用每分钟最多调用 1 次,但不到 20% 的应用产生 99.6% 的调用。为不频繁调用的应用常驻内存代价太高。
  3. 到达间隔变异性大:40% 的应用 CV > 1,预测下一次调用时间对很多应用来说非常困难。

4. 冷启动管理:问题定义与设计挑战

4.1 核心概念

论文将冷启动管理抽象为为每个应用设置两个参数:

  • Pre-warming window(预热窗口):函数执行结束后,策略等待多久再卸载应用,然后在预测的下一次调用之前重新加载。如果为 0,表示执行后不卸载。
  • Keep-alive window(保活窗口):应用镜像被加载到内存后(无论是预热还是执行后),保持存活的时间。

当前行业标准做法很粗暴:固定 keep-alive 时间。AWS Lambda 用 10 分钟,Azure 用 20 分钟。所有应用一视同仁,执行完后就保持 10/20 分钟然后卸载。

这种做法的问题:

  • 对高频应用:10 分钟足够,大多数调用都是 warm start
  • 对低频应用:10 分钟远远不够,下一次调用可能在数小时甚至数天后,白白占了 10 分钟内存还是冷启动
  • 对所有应用用同一个时间:高频应用不需要这么长,低频应用不够长,两头都不讨好

4.2 五大设计挑战

  1. 调用难以预测:很多应用不用 Timer,HTTP/Event 触发器的到达时间不规则。
  2. 应用异质性强:调用频率和模式差异跨越 8 个数量级,一刀切的策略必然低效。
  3. 不频繁调用的应用:调用稀疏的应用需要时间学习其模式,新应用更是如此。
  4. 追踪开销:需要为每个应用维护状态,单应用追踪成本必须很小。
  5. 执行开销:50% 以上的执行 < 1 秒,策略更新必须极快,不能拖慢关键路径。

5. Hybrid Histogram 策略:核心设计

5.1 策略总览

Hybrid histogram 策略由三个组件组成,针对不同类型的应用分而治之:

新调用到来
|
v
更新应用的 IT 分布 (直方图)
|
v
[直方图是否具有代表性?] -- 检查 CV 是否 < 阈值 (默认 CV=2)
| |
是 否
| |
v v
[OOB IT 是否太多?] 使用标准 keep-alive (保守策略)
| | pre-warm = 0, keep-alive = 直方图范围
否 是
| |
v v
使用直方图 使用 ARIMA 时间序列预测
确定窗口 预测下一次 IT

5.2 组件一:Range-Limited Histogram(范围受限直方图)

这是整个策略的核心数据结构。为每个应用维护一个紧凑的直方图,追踪其空闲时间(Idle Time, IT)的分布

关键设计:

  • IT(Idle Time):上一次函数执行结束到下一次调用到来的时间间隔。对于 81% 的低频应用(每分钟最多 1 次调用),IT 近似等于 IAT。
  • Bin 粒度:1 分钟,在直方图大小和策略精度之间取得平衡。
  • 范围上限:可配置,例如 4 小时(240 个 bin)。超过范围的 IT 被标记为 "Out of Bounds (OOB)"。
  • 存储开销:每个应用 240 个整数 = 960 字节。

从直方图到 pre-warm/keep-alive 窗口

  • Pre-warming window = 直方图的第 5 百分位(head):在大多数调用到来之前的安全时间,过了这个时间就开始预热。
  • Keep-alive window = 直方图的第 99 百分位(tail):覆盖绝大多数调用的保活时间。
  • 额外加 10% 的 margin:pre-warm 减少 10%,keep-alive 增加 10%,增加容错空间。

直觉理解:如果一个应用的 IT 分布集中在 5-15 分钟之间,那么执行结束后等 ~4.5 分钟再预热(5th percentile - 10%),然后保活到 ~16.5 分钟(99th percentile + 10%)。这样既能覆盖绝大多数调用(避免冷启动),又不会过度占用内存。

5.3 组件二:Standard Keep-alive Fallback(标准保活回退)

当直方图不具有代表性时,策略回退到一个保守方案:

什么时候直方图不具代表性?

  • 观察到的 IT 样本太少(新应用或极低频应用)
  • 应用的调用模式正在发生变化(从一种模式转向另一种)

如何判断? 计算直方图各 bin 计数的 CV。如果 CV < 阈值(默认 2),认为直方图具代表性;否则,说明 IT 分布过于集中在少数 bin(尖峰分布),直方图不可靠。

使用 Welford 在线算法 高效追踪 CV,无需存储原始数据。

回退策略:pre-warming window = 0(不预热,执行后不卸载),keep-alive window = 直方图范围(如 4 小时)。这相当于一个较长的固定 keep-alive,在学习阶段保守地减少冷启动。

5.4 组件三:ARIMA 时间序列预测

对于 IT 经常超出直方图范围(大量 OOB)的应用——即调用非常不频繁但有一定规律的应用——使用 ARIMA 模型 预测下一次 IT。

  • 使用 auto_arima(pmdarima 包)自动搜索最优 (p, d, q) 参数
  • 每次调用后更新模型(因为这类应用调用本来就不频繁)
  • 预测的 IT 值加 15% margin:pre-warm = predicted IT * 85%,keep-alive = predicted IT * 15% * 2

ARIMA 的计算成本较高(初始建模 ~26.9ms,后续预测 ~5.3ms),但因为只用于极少数应用(仅 0.7% 的调用涉及 ARIMA),整体开销可控。而且这些应用本身就会经历冷启动,几毫秒的预测开销相比冷启动时间(数百毫秒到数秒)微不足道。


6. 实现细节

6.1 Apache OpenWhisk 实现

论文在 Apache OpenWhisk(IBM Cloud Functions 的开源版本,用 Scala 编写)上实现了 hybrid histogram 策略:

  • Controller / Load Balancer:管理直方图和策略元数据,每次调用后更新 pre-warm 和 keep-alive 参数。这是策略逻辑的核心位置,因为所有调用都经过 Load Balancer。
  • API 扩展:在 ActivationMessage 中添加 keep-alive 字段,随调用请求一起发送给 Invoker。
  • Invoker:修改 ContainerProxy 模块,根据收到的 keep-alive 参数决定容器卸载时间。同时发布 pre-warming 消息。

6.2 Azure Functions 生产实现

论文也描述了在 Azure Functions 生产环境中的实现(针对 HTTP 触发的应用,逐步上线):

  • Azure Functions 有一个 Controller 通过 HTTP 与 worker 通信,异步收集 worker 状态更新来填充直方图。
  • 直方图存储:内存中保存(240 个整数 / 960 字节每应用),每小时备份到数据库。
  • 每日新建直方图,保留两周历史,便于追踪模式变化。
  • 所有策略决策在关键路径之外异步执行,包括更新直方图、备份数据库、调度预热事件、控制 worker keep-alive 时长。

7. 实验评估

7.1 仿真实验

实验设置:基于真实 trace 的一周数据构建仿真器,生成每个应用的调用时间序列,推断每次调用是否为冷启动,追踪内存浪费时间。

核心结果

固定 keep-alive 的困境

  • 10 分钟 keep-alive:75th 百分位应用冷启动率约 50.3%
  • 1 小时 keep-alive:降至约 25%,但内存浪费大幅增加
  • 存在明显的 Pareto 前沿——冷启动率和内存浪费不可兼得

Hybrid histogram 的表现

  • 使用 4 小时范围的直方图,冷启动率降低约 2.5 倍(相比 10 分钟固定 keep-alive)
  • 内存消耗持平或更低
  • 在 Pareto 前沿上,hybrid 策略形成了一条平行但更优的曲线——在相同冷启动率下用更少内存,或在相同内存下有更低冷启动率

各组件的贡献

  • 直方图本身:是最大的贡献者,显著降低冷启动率
  • Pre-warming:进一步减少内存浪费(执行后先卸载,需要时再预热),代价是极少量额外冷启动
  • CV 检查:CV 阈值从 0 增加到 2 有明显收益,再高则收益递减。默认 CV=2
  • ARIMA:将 100% 冷启动的应用比例从 10.5% 降至 5.2%(排除仅调用一次的应用后,从 6.9% 降至 1.7%)

Head/Tail 百分位的敏感性

  • 使用 5th/99th 百分位(排除极端异常值)相比 0th/100th,冷启动率几乎不变,但内存浪费减少约 15%

7.2 OpenWhisk 真实实验

实验设置:19 台 VM(1 台 Controller + 18 台 Invoker),68 个随机选取的中等活跃度应用,每个实验运行 8 小时,共 12,383 次函数调用。

结果

  • 冷启动行为与仿真结果一致,验证了仿真的可靠性
  • Hybrid 策略将 Invoker VM 上 worker 容器的内存消耗降低了 15.6%
  • 平均函数执行时间降低 32.5%,99 百分位执行时间降低 82.4%(因为消除了 warm 容器的语言运行时 bootstrap 时间)

7.3 策略开销

  • 策略更新的平均延迟:仅 835 微秒(sigma = 245.5 微秒)
  • 相比 OpenWhisk 的语言运行时初始化(~10ms)和容器初始化(~100ms),完全可以忽略
  • ARIMA 建模:初始 ~26.9ms,后续预测 ~5.3ms(仅 0.7% 的调用需要)
  • CPU 利用率:Controller 仅增加 4-6%(10-300 rps 基准测试范围内)

8. 与现有方案的对比

维度固定 Keep-alive (AWS/Azure)Hybrid Histogram (本文)
策略粒度所有应用统一每个应用自适应
预热能力有(基于 IT 分布预测)
适应性直方图持续更新,CV 检查 + ARIMA 回退
冷启动率 (75th)~50%(10分钟)~20%(降低约 2.5x)
内存开销基线持平或更低
实现复杂度极低中等(直方图 + CV + ARIMA)
运行时开销835 微秒/调用
元数据开销960 字节/应用

与缓存策略的关系:论文在 Related Work 中特别讨论了冷启动管理与缓存管理的异同。两者有相似性(都是在保留"热"数据和释放内存之间权衡),但有两个关键差异:

  1. FaaS 中资源按服务计费,应用被打包为容器部署,冷启动策略主动卸载而非被动淘汰。
  2. 大多数缓存算法优化聚合指标(加权命中率),而本文的策略针对每个应用单独优化以最大化用户满意度。

本文的策略最接近于 TTL-based cache 中根据访问重置 TTL 的方法,但额外引入了 pre-warming(类似 temporal prefetching),这在之前的 TTL 缓存研究中没有被考虑。


9. 论文的局限性与未来方向

局限性

  1. 数据粒度:调用计数以 1 分钟为 bin,无法精确重建亚分钟级 IAT。对于每秒数百次调用的高频应用,直方图的 1 分钟粒度可能不够精细。
  2. 并发冷启动未深入讨论:论文提到由于使用 full-server 实例,不到 1% 的应用会经历并发导致的冷启动(负载突增需要新实例),但这个问题在更细粒度的资源管理下会更重要。
  3. 内存数据不完整:不是所有应用都有内存使用数据,仿真中假设所有应用使用相同内存量。
  4. 仅针对单实例场景:未考虑应用 scale-out 时多实例的冷启动管理。
  5. ARIMA 的可替代性:论文承认 ARIMA 模型可以被其他预测模型替代,并未深入探索更先进的预测方法。

未来方向

  • 更细粒度的应用模式研究(function chaining、编排模式等)
  • 结合内存实际使用量的差异化资源管理
  • 探索更先进的时间序列预测模型替代 ARIMA
  • 多实例 scale-out 场景的冷启动优化
  • 跨应用的资源共享与协调

10. 个人总结与思考

这篇论文的核心价值在于两点:第一手的生产数据务实的工程设计

数据层面的贡献是不可替代的。在这篇论文之前,学术界对 Serverless 工作负载的理解基本靠猜测和小规模实验。论文揭示的几个数字非常有冲击力:8 个数量级的调用频率差异、81% 的应用每分钟最多调用一次、18.6% 的应用产生 99.6% 的调用、40% 的应用 CV > 1。这些数字直接否定了很多想当然的假设(比如"大多数函数被频繁调用"或"到达过程近似泊松")。

策略设计体现了工程美学。Hybrid histogram 策略的每一个组件都有明确的针对性:直方图处理"规律"的应用,CV 检查处理"还不了解"的应用,ARIMA 处理"规律但超出直方图范围"的应用。更重要的是,整个策略的开销极低(835 微秒/调用,960 字节/应用),这在生产系统中至关重要。很多学术工作提出了更"聪明"的预测模型,但忽略了实现开销——对于 50% 执行时间不到 1 秒的函数来说,一个 100ms 的预测模型就是灾难。

一个值得思考的问题:论文的数据来自 2019 年 7 月。如今 Serverless 的使用模式是否发生了显著变化?随着 Serverless 被更多地用于 AI 推理、流处理等场景,执行时间和内存需求的分布可能已经大不相同。但论文建立的分析框架和策略设计思路依然具有参考价值——核心问题(冷启动 vs 资源浪费的权衡)和解决思路(per-application adaptive policy based on IT distribution)是通用的。

公开的 Azure Functions trace 数据集也为后续研究提供了宝贵的基准,被大量后续工作引用和使用。