edge-faas-cpp

Edge FaaS 冷启动缓解 —— 基于 EWMA + CUSUM 的预测调度

语言: English 中文版

[!WARNING] 目前并非所有代码文件都已完成整理(例如注释完善、代码结构调整、风格统一等)。部分工作已经进行,但仍有许多尚未完成。所有代码将在学期结束、项目收尾之前整理为一份专业的工程代码。现阶段还有些凌乱,敬请谅解。🙏🙏🙏

Prof: Ramesh Govindan

Proposal

Proposal Report: Proposal.pdf

Check #1

check #1 report: checkin1.pdf

#1 — 我们在干嘛

本项目用 C++ 从零构建了一个裸机 FaaS(函数即服务)工作节点,专门针对边缘无服务器环境中的冷启动问题

核心研究问题: 一个轻量级、O(1) 复杂度的预测启发式算法(EWMA + CUSUM + Little’s Law)能否在周期性流量下显著降低冷启动惩罚——同时比 ARIMA 这类重量级时序模型消耗少得多的资源开销?

核心设计原则

系统架构


#2 — 目前完成了什么

已完成(系统核心,全部跑通)

模块 文件 状态
C++ Epoll Reactor(ET 模式,非阻塞) tcp_server.hpp ✅ 完成
64 线程 DispatchPool(所有阻塞 I/O 在此) DispatchPool.hpp ✅ 完成
WorkerPool:fork + 缩容清道夫线程 WorkerPool.hpp ✅ 完成
Predictor:EWMA + CUSUM + Little’s Law Predictor.hpp ✅ 完成
动态 T 反馈(UpdateServiceTime web_server.hpp ✅ 完成
Python Worker(模拟 T=500ms AI 推理) worker.py ✅ 完成
两轮对比测试 + Drain 阶段 load_tester.py ✅ 完成

还没做的(距离论文级评估的差距)

缺项 类型 优先级
ARIMA 基线(Python statsmodels 代码 + 实验 🔴 高——导师明确要求
Reactive 基线(关掉预测器,只走冷启动兜底) 代码 + 实验 🔴 高——最坏情况对比,必须有
Static 基线(固定 N 个 worker,不扩缩容) 代码 + 实验 🟡 中——Pareto 曲线需要
内存 / CPU 开销测量 实验 🔴 高——Pareto 曲线的核心数据
预测器推理延迟测量 实验 🟡 中——证明 EWMA/CUSUM 远快于 ARIMA
CloudLab 裸机测试 环境 🟡 中——本机 loopback 没有真实网络噪声
周期性突发流量 workload(wrk) 实验 🟡 中——Proposal 中承诺用 wrk

#3 — 测试结果与心路历程

第一阶段 — V1.0:压力不够,结果无意义

日志:test_20260303_013519.log

V1.0 时 Python worker 处理的是 1×1 像素的图片,服务时间 T ≈ 0.001s。T 这么小,Little’s Law 算出来的结果是 N = ⌈60 × 0.001⌉ + 1 = 2,哪怕流量到了 60 RPS,2 个 worker 就够用了。预测器根本没有扩容的必要,冷启动从来不会发生。

更关键的问题是:当时还没做缩容(Scale-Down)。Cycle 1 期间 fork 出来的 worker 一直活着,Cycle 2 开始时进程池已经是满的,当然没有冷启动——这和 EWMA 预测没有任何关系,是因为 worker 从来没死过。

结果: P50=1.4ms,P95=1.7ms,P99=2.1ms,0 次冷启动。看起来很完美,实际上毫无意义。

根因: T 太小 → N 永远是 2 → 预测器无效。没有缩容 → Cycle 1 给 Cycle 2 免费”预热”了。


第二阶段 — 阈值 Bug:全部被标为 COLD

日志:test_20260303_022336.logtest_20260303_022743.log

worker.py 里加了 time.sleep(0.5) 之后(T=500ms),热路径延迟变成了约 502ms。但 load_tester.py 里的冷热判断阈值还是 rtt > 500——每个热请求(502ms)都被判为 COLD,结果 100% COLD,0 次温热。

修复方案:把阈值改为 700ms(热路径 ~502ms 和冷路径 ~800ms 的中点)。


第三阶段 — V2.0:参数校准,真实结果

日志:test_20260303_220742.log

修复阈值(700ms)、添加缩容(idle_timeout=6s)、在两轮之间加入 8 秒 drain 阶段(确保清道夫线程把所有 worker 都杀掉)后,得到了有意义的实验数据:

阶段 总计 WARM COLD P50 P99
C1-Warmup(2 RPS,10s) 20 18 2 503 ms 806 ms
C1-Spike(30 RPS,5s) 150 55 95 801 ms 806 ms
C1-Cooldown(2 RPS,6s) 12 12 0 502 ms 504 ms
[Drain:8s 零流量——清道夫杀掉所有 worker]          
C2-Warmup(2 RPS,5s) 10 8 2 515 ms 812 ms
C2-Spike(30 RPS,5s) 150 64 86 801 ms 804 ms

核心对比——仅看 Spike 阶段:

为什么 Cycle 2 会好一点? C1 Spike 结束后,EWMA 爬升到约 15 RPS,经过 Cooldown 和 Drain 衰减到约 2.9。Little’s Law 给出 ⌈2.9 × 0.5⌉ + 1 = 3 个 worker——比 C1 Warmup 时多 1 个。这个多出来的预热 worker 贡献了 C2 Spike 的那额外 +9 次热命中。

为什么提升幅度不大? CUSUM 是反应式的,不是预测式的:它在 Spike 第 2 秒才触发,而新 worker 需要 0.9s 冷启动,所以 CUSUM 的扩容对 Spike 阶段几乎来不及生效。Spike 阶段的改善主要来自 EWMA 记忆在 Warmup 阶段多预热了 1 个 worker,而不是 CUSUM 预见了流量峰值。根本上,30 RPS × 0.5s = 需要 15 个并发 worker,我们只预热了约 3 个,差距太大。


#4 — 接下来要完成的事

要达到论文级别的评估,按优先级顺序需要:

  1. 实现基线策略 —— Reactive(无预测器)、Static(固定 N)、ARIMA(Python statsmodels)。没有这三条基线,就没有 Pareto 曲线,也就没有论文 Claim 的落脚点。
  2. 测量开销 —— 各策略的 CPU 利用率和内存占用。这是与 ARIMA 区分的那条坐标轴。
  3. CloudLab 裸机部署 —— 真实的裸机节点 + 真实网络延迟。本机 loopback 结果干净,但不能代表边缘场景。

构建与运行

# 依赖
pip install Pillow

# 编译
make clean && make

# 运行服务(端口 8080)
./server

# 运行两轮对比测试
python3 load_tester.py

日志输出: logs/test_<时间戳>.log

Check #2

check #2 report: checkin2.pdf

相比 Check #1 的主要改动

改动 说明
CoW 模板进程 新增 worker_template.py,启动时一次性加载 Pillow,后续 worker 全部 CoW fork。每个 worker 冷启动从 ~800ms 降到 ~100ms
叙事重构 不再说”绕过 KVM 为了干净测量”。改为:边缘推理节点资源受限,跑不起 MicroVM,OS 进程池是合适的数据面
Reactive 基线 ./server reactive,纯响应式,无预测,作为冷启动下界对比
Static 基线 ./server static 15,固定 15 个 worker 全程保活,作为资源开销上界对比
ARIMA 基线 ./server arima,独立 Python 进程跑 ARIMA(2,1,2),验证重量级预测的开销代价
4 cycle 测试 load_tester.py 升级为 4 个周期(C1: warmup 8s;C2–C4: warmup 35s),给 ARIMA 足够历史数据
自动化实验脚本 run_experiments.sh 一键串行跑 4 个基线,自动归档日志到 logs/exp_<mode>_<ts>/
资源监控 resource_monitor.py 每秒采样 RSS、CPU、worker 数,输出 CSV 供 Pareto 分析

Check #2 Checklist

已完成:

待完成(Final Deliverable):

如何运行

# 依赖
pip install Pillow statsmodels

# 编译
make clean && make

# 单独运行某个 mode
./server ewma          # 默认,EWMA+CUSUM 预测
./server reactive      # 纯响应式
./server static 15     # 固定 15 个 worker
./server arima         # ARIMA 预测

# 运行 4-cycle 负载测试
python3 load_tester.py

# 一键跑完 4 个基线(推荐,自动归档)
./run_experiments.sh

# 单独跑某个基线
./run_experiments.sh ewma

日志输出: logs/exp_<mode>_<时间戳>/(包含 test.logserver.logresource.csv

目前的发现与问题

主要发现:

已知问题 / 局限:

Before Pre — 演示前最终结果

本节是 2026-04-30 课堂演示(CSCI 599)前整理的最终结果,对 Check #2 之后新增的 CoW 量化、Adaptive CUSUM 基线、Warmup Sweep 消融 做了系统化补充。所有图见 figures/pre/ 目录及 MANIFEST.md

相比 Check #2 的新增工作

新增 文件 / 命令 说明
CoW 冷启动量化 figures/plot_cow.pyslide05_cow.png 从 worker.py 模拟 cold start + server.log “Worker N ready (CoW fork)” 日志手测:Naive (exec Python + import) ≈ 900 ms vs CoW (fork from warm parent) ≈ 100 ms9× 提速 · 无 runtime 依赖
CUSUM 实测 trace figures/plot_rps_cusum.pyslide06_rps_cusum.png 用 sweep #3 真实 server.log(90 个 predictor tick、11 个 SPIKE DETECTED)重建 CUSUM 累积轨迹(drift=5, h=8),验证报警全部落在 ramp 爬升段
Adaptive CUSUM 基线 ./server ewma_adaptive 用 EWMSD(running σ)做 z-score 归一化,作为 fixed-drift CUSUM 的对照
Workload 设计可视化 figures/plot_workload.pyslide07_workload.png load_tester.py 的 4-cycle Bursty-Ramp 参数画成时间轴,标出 Ramp = CUSUM 检测窗口
5-mode 主结果 figures/plot_main_result.pyslide08_main_result.png 2026-04-20 重跑 5 个模式 × 4 cycle,cold counts 从各 load_tester_output.txt 的 SPIKE COMPARISON 表解析
Warmup-Sweep 消融 figures/plot_sweep.pyslide10_sweep.png sweep #1(W=5, 120 s 端点)+ sweep #3(W=10, 20, 35, 60 s 内点),对比 fixed vs adaptive

主结果(5 模式 × 4 cycle,2026-04-20 单 trial)

模式 C1 C2 C3 C4 总计 说明
Static-15(过度配置) 0 0 0 0 0 15 个 worker 全程钉死 —— 上界参照线
Adaptive CUSUM(EWMSD z-score) 0 0 0 0 0 W=35 落在 sweet spot
Fixed CUSUM(我们) 0 14 33 0 47 C3 出现 clock-aliasing 事件
Reactive(按 backlog 扩缩) 20 15 20 12 67 反应式基线
ARIMA(smoothed Target) 20 18 31 16 85 历史时序预测

总冷启动数 = 4 个 cycle 中 600 个 spike 请求里被判为 cold(RTT > 700 ms)的总数。

主要发现:

CoW Template — 9× 冷启动提速

启动一次 template Python 进程预先 import 好 Pillow + 建好 socket 骨架,后续每个 worker 通过 fork() 从 template 复制。Linux 的 copy-on-write 让 fork 几乎免费 —— Pillow 代码 / import 表是只读的,不会触发页复制。

No image. No snapshot. No registry. Just fork() from a warm parent.

CUSUM 实测 trace — 报警落在 ramp,不在 peak

上面 panel:蓝线是测得 RPS,橙虚线是 EWMA baseline(α=0.2)—— 故意滞后让 RPS 一脱离就显出 gap。

下面 panel:绿色是 CUSUM 累加器,越过红色虚线 h = 8 时 ★ 报警 —— 11 次报警全部落在 ramp 爬升段,0 次落在 peak 之后。这就是 “catch the ramp, not the peak” 的实证。

Workload 设计 — Bursty-Ramp × 4 cycles

每个 cycle 模拟一次”列车到站”周期:Warmup → Ramp(30 s) → Spike(30 RPS × 5 s) → Cooldown → Drain

Warmup-Sweep 消融 — Fixed vs Adaptive 各有 failure mode

W (s) Fixed CUSUM Adaptive CUSUM
5 48 287
10 45 135
20 0 0
35 0 0
60 0 0
120 32 0

Framing两个 failure modes,没有赢家。Fixed 适合紧节奏,Adaptive 适合松节奏。Predictor 不是一个选项 —— 是一个 knob。Adaptive 真正的贡献是 scale invariance + aliasing robustness,而不是更少的 cold starts。

已知局限 / Final Report 待办