你部署了一个 Supabase Edge Function,本地用 supabase functions serve 跑稳定 ~100ms。生产环境第一次请求 2-5 秒,后面再请求又恢复 100-200ms——半小时不用又慢回去。这是典型的冷启动现象,几乎所有 serverless 平台都有,但 Supabase Edge Functions(基于 Deno isolate)的冷启动模式有自己的特点。
理解关键:Supabase Edge Function 长时间没流量会被 evict(Deno isolate 释放掉)。下次请求要重新 spawn isolate、加载代码、import 依赖、跑模块顶层初始化——这几步加起来就是你看到的 2-5 秒。
常见原因
按命中率从高到低:
1. Function 长时间无请求被休眠
Supabase Edge Functions 空闲 ~5-15 分钟会 evict。下次请求触发完整 cold start。
如何判断:用户报”凌晨 / 周末偶尔很慢”——典型的低流量时段冷启动。
2. import 了大依赖(特别是 npm 包)
import { Anthropic } from 'npm:@anthropic-ai/sdk'; // ~500KB
import { OpenAI } from 'npm:openai'; // ~1MB
import _ from 'npm:lodash'; // ~70KB
Deno 第一次跑要下载 + transpile 这些包,慢启动直接 +1-3 秒。
如何判断:function 顶部有多个 npm: import。
3. 模块顶层做了重活
// ❌ 顶层跑——每次 cold start 都执行
const config = await fetch('https://...').then(r => r.json());
const dbConnection = await connectDB();
Deno.serve(async (req) => { ... });
每次 isolate 启动这些都重跑,叠加在 cold start 上。
如何判断:function 顶部有 await 或长同步计算。
4. cold start 时打外部 API
Deno.serve(async (req) => {
// ❌ cold start 时这个 fetch 也算冷
const data = await fetch('https://slow-third-party.com/data');
...
});
upstream 自己慢 → 你 cold start + upstream slow 都摊到 first request。
如何判断:function 里第一个 await 调用是外部 API。
5. 包含大 JSON / WASM blob
在 function 顶部 import 一个 5MB 的 JSON 数据集或 WASM 模块,parse / instantiate 都耗时。
如何判断:repo 里 functions 目录下有大 .json / .wasm 文件。
6. 区域不匹配(用户在 Asia、function 在 US)
cold start 本身慢 + 跨地区网络延迟,体感 5+ 秒。
如何判断:Supabase Dashboard → Settings → Project → Region 跟主要用户区域不一致。
最短修复路径
Step 1:测量真实 cold start
# 等待 15 分钟无请求
# 然后第一次请求计时
time curl -X POST "https://xyz.supabase.co/functions/v1/my-fn" \
-H "Authorization: Bearer $KEY"
# 比对后续请求(warm)
time curl -X POST "..."
记录 cold vs warm 差值——这就是 cold start 真实代价。
Step 2:cron 保温(最简单粗暴)
如果可以接受额外 invocations:
-- Supabase Dashboard → Database → Cron
SELECT cron.schedule(
'keep-warm',
'*/5 * * * *', -- 每 5 分钟
$$ SELECT net.http_post(
url := 'https://xyz.supabase.co/functions/v1/my-fn',
headers := '{"Authorization": "Bearer ANON_KEY"}'::jsonb,
body := '{"warmup": true}'::jsonb
); $$
);
function 里识别 warmup 请求快速返回:
if (body.warmup) return new Response('ok');
成本:每月 ~8640 次额外 invocations(5 分钟一次),Supabase 免费档够。
Step 3:依赖瘦身
// ❌ 全 SDK
import Anthropic from 'npm:@anthropic-ai/sdk';
// ✅ 自己写 fetch(节省 import 时间)
async function callAnthropic(messages: any[]) {
return fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': Deno.env.get('ANTHROPIC_API_KEY')!,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({ model: 'claude-opus-4-7', messages }),
});
}
每个 SDK 替换为原生 fetch 可省 1-2 秒。
Step 4:把顶层初始化挪到 handler 里
// ❌ 顶层
const config = await loadConfig();
Deno.serve(async (req) => { ... });
// ✅ handler 里 lazy load + cache
let configPromise: Promise<Config> | null = null;
Deno.serve(async (req) => {
configPromise ??= loadConfig(); // 只在第一次请求时 load
const config = await configPromise;
...
});
warm 状态下 configPromise 已经 resolve,不重复 load。
Step 5:外部 API 异步化
Deno.serve(async (req) => {
const result = await quickProcess(req);
// 慢的外部 API 不阻塞响应
// 用 EdgeRuntime.waitUntil 让 Supabase 在响应后继续执行
// @ts-expect-error EdgeRuntime is a Supabase global
EdgeRuntime.waitUntil(slowExternalCall());
return Response.json(result);
});
Step 6:换平台 / 区域
如果冷启动对你业务关键、或者你流量分散在全球:
低冷启动场景:
- Cloudflare Workers(V8 isolate,cold start ~5ms)
- Vercel Edge Functions(类似)
- 永远 warm 的 Node serverless(Fly.io Machine、Railway)
只需修复 Supabase region:
- Dashboard → Project → Settings → Region
→ 选最近用户的 region
注意 region 一旦改不容易回退,先评估再改。
预防建议
- 设计时就估算冷启动可接受度:如果是后台同步任务可以慢,用户实时交互必须 < 500ms
- 对延迟敏感的 endpoint 用 cron 保温(小项目)或换 Cloudflare Workers(大流量)
- function 顶层零异步 import / 零 fetch / 零长计算,只声明
- 依赖能不引则不引;能用原生 fetch 替代就用
- 大 JSON 数据放 Supabase Storage / 外部 KV,按需读取
- 监控 p99 latency 区分 cold vs warm,alert 高 cold start 比例
- 用户主要在 Asia / EU 就选对应 region,跨大洲冷启动叠加网络延迟会爆