Profile a Worker
Cloudflare 可以在已部署的 Worker 或 Durable Object 处理流量时对其进行采样,产出 pprof 文件。文件中每一帧(frame)对应一个函数,帧越宽表示消耗的 CPU 时间越多或分配的内存越多。
术语说明
Isolate:Worker 的一个运行副本。一次剖析仅采样一个已加载的 isolate。
CPU profile:记录 CPU 时间的去向,约每毫秒采样一次。
Heap profile:记录代码在哪里分配内存,每分配 512 kB 记录一次调用栈。它不显示保留内存(retained memory),因此单凭它无法证明存在内存泄漏。
Flat:函数自身的开销。Cum:函数自身及其调用的所有内容的开销。
- 选择目标
找到已部署的脚本名称。Alchemy 会根据应用、资源、stage 和一个后缀为其命名。从 Alchemy 状态或控制台读取,不要猜测。
选择版本。latest 是最近上传的版本,可能并未部署。若上传后没有部署,应使用已部署版本的 id。
对于 Durable Object,需要使用拥有该命名空间的 Worker、命名空间 id,以及 64 个字符的实例 id。
- 获取读取权限
使用以下之一:
带独立登录的 cf CLI。
一个具有 Workers Scripts Read 权限的 API token,仅本次运行从密钥存储中取出。
剖析是只读操作。不要把 token 加入仓库、.env.schema 或 CI。部署凭据保留在 Alchemy profiles 中。
- 采集期间发送流量
采集本身不会调用你的代码,只记录窗口期内实际运行的内容。
先对怀疑的路由启动压测循环,再开始采集。
在整个采集时长内保持循环运行。
若近期没有执行或没有已加载的 isolate,说明该版本没有流量。发送流量后重试。
- 采集
API 方式,采集 10 秒 CPU:
curl --fail --silent --show-error --request POST
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/workers/$SCRIPT/versions/latest/profile"
--header "Authorization: Bearer $TOKEN"
--header "Content-Type: application/json"
--data '{"duration_ms": 10000, "profile_type": "cpu"}'
--output worker-cpu.pprof
duration_ms 范围为 1000 到 50000。profile_type 为 cpu(默认)或 heap。
对于 Durable Object,在请求体中加入 namespace_id 和 actor_id。
CLI 方式:cf workers versions profile latest --worker-id "$SCRIPT" --duration-ms 10000 --profile-type cpu > worker-cpu.pprof。
控制台在 Worker 的 Observability 标签页下也有同样的采集入口,查看 Flamegraph。
采集有速率限制。遇到 HTTP 429 时,按 Retry-After 等待。
读取前先检查响应。失败返回 JSON,成功返回 protobuf。
file worker-cpu.pprof
文档称 API 返回 gzip。但在 2026-10-09 的一次采集中,返回的是原始 protobuf(application/vnd.google.protobuf)。pprof 两者都能读取。
- 读取结果
go tool pprof -top -nodecount=25 worker-cpu.pprof go tool pprof -top -lines -nodecount=15 worker-cpu.pprof go tool pprof -peek 'functionName' worker-cpu.pprof go tool pprof -http=:0 worker-cpu.pprof
先用 -top。忽略 (idle) 和 (program)。注意 (garbage collector):若占比高,说明与内存分配有关,接下来应采集 heap profile。
对较宽的帧使用 -peek 查看其调用方与被调用方。
函数名来自打包产物。若打包器保留了名称,则可读。位置指向打包文件与行号,例如 file:/bundle/worker.js:3348,即使上传了 source map 也是如此。可在打包产物或源码中搜索函数名。
在 Effect 程序中,fiber 运行时(runLoop、makePrimitiveProto、succeedWith)是每个请求都要付出的底层开销,应在它下面寻找你自己的函数。
- 针对一个发现采取行动
指出你负责的最宽函数及到达它的路由。
只做一处改动。常见的优化:把每次调用都计算的值改为只算一次、缓存已完成的值、停止构建会被丢弃的对象、移除只被开关半禁用的代码。
部署,发送相同流量,以相同时长再次采集,并对比同一帧。
在提交信息中记录改动前后的数字。
一次采集只对一个 isolate 采样数秒。在得出规律性结论前,先做两三次采集。说“本次采集显示”,而不是“该 Worker 总是如此”。
完成
产出剖析文件及采集它的命令。
列出你负责的顶层帧,含 flat 与 cum。
列出一处改动及其可量化的效果,或说明为何不值得改动。