Supabase Edge Functions timeout 是否对得上
不要先换模型。打开 Supabase Function Logs,把一次请求拆成四个时间点:进入函数、发起 LLM 请求、收到第一段 token、写完响应。只要首包超过 20 秒,用户体感就会像页面挂住。
| 位置 | 常见现象 | 第一刀 |
|---|---|---|
| 冷启动 | 第一笔请求慢,第二笔正常 | 减少依赖、预热关键路径 |
| LLM 请求 | 上游很久无首包 | streaming、短 timeout、fallback 模型 |
| 数据库 | 生成完了但返回慢 | 写入改异步,索引补齐 |
| 响应流 | 前端一直转圈 | 确认 SSE header 和 flush |
Supabase Edge Functions timeout:最短处理路径
第一步,给外部调用设硬超时。我一般把首包等待设在 15-25 秒,完整回答不要无上限等待。失败时返回「任务已进入后台」而不是空白页。
第二步,改成流式输出。Deno 里用 ReadableStream 或 SDK streaming,把首段内容尽快推给前端。用户看到文字开始滚动,投诉会少很多。
第三步,拆掉附属动作。usage 统计、邮件、Slack 通知、向量写入,都不要挡主响应。必要时只先写一条 job id,后续后台补齐。
第四步,记录每个模型的首包时间和完成时间。不要只记总耗时,后面你会分不清是 Supabase、数据库还是上游模型慢。
为什么 Edge Function 容易暴露 LLM 延迟?
Edge Function 的好处是离用户近、部署轻,代价是它不是长时间 agent 容器。Supabase 文档里有函数限制、idle timeout 和 wall clock 的说明;LLM 又天然有冷模型、长上下文、工具调用排队的问题。两边叠在一起,很容易误判成「平台抽风」。
如果你的产品是 AI 简历改写、SEO 标题生成、客服草稿,Edge Function 很合适;如果是多步研究、网页抓取、RAG 重排,就应该从请求响应模式改成 job 模式。
Supabase Edge Functions timeout 继续排查清单
- LLM 请求是否有 AbortController
- Supabase client 是否每次重复初始化大依赖
- 是否在函数内同步写多张表
- 前端 fetch 是否被浏览器或代理提前断开
- SSE 是否设置
text/event-stream - 失败时是否有 fallback 模型或缓存回答
还没恢复时,单独查 Supabase Edge Functions timeout
把功能分两层:短问题继续走 Edge Function,长任务进入队列,由后台 worker 处理。模型侧可以先接一个 OpenAI compatible 网关,统一做超时、重试、模型切换;我自己的小产品会预留多模型统一计费的 API 中转作为备选路径,避免一次供应商抖动拖垮整站。
我会怎么改代码结构?
我不会让前端直接等一个「万能生成接口」。第一层 /api/chat 只负责校验登录、创建任务、返回 stream;第二层 runLLM() 只做模型调用;第三层 persistResult() 负责落库和计费。三层之间传 requestId,日志里永远能串起来。
对需要长推理的功能,我会让接口先返回 job id,前端显示「正在生成」,每 2 秒轮询一次状态。用户刷新页面也不丢,因为任务状态已经在数据库里。这样做看起来比一次请求麻烦,但产品上线后最省心:你可以单独重跑失败任务,也能把高成本任务排队处理。
还有一个细节:不要把 prompt、完整回答和错误堆栈全写进同一张业务表。prompt 可能很长,错误堆栈可能含敏感上下文。我通常拆成 jobs、messages、usage_logs 三张表,前端只读必要字段。排查超时时,看 usage 和 job 状态就够,不必每次打开用户内容。
最后给自己留一个手动开关。比如把 force_background=true 写进配置表,事故时所有长回答都转后台;把 model_tier=cheap 写进环境变量,账单异常时先保住服务。小团队最需要的不是完美架构,而是凌晨两点能把火压住的开关。
复盘模板怎么写?
每次超时事故后,我会写四行:影响了哪些用户、哪一段耗时异常、临时怎么止血、长期改哪一个限制。不要写成情绪化的「模型不稳定」。如果是首包慢,就调模型和流式;如果是落库慢,就补索引;如果是前端断开,就改重试提示。复盘越具体,下次越少靠猜。
我还会把这次事故转成一个自动化检查:上线前跑一次短 prompt、长 prompt、失败 prompt,确认三种路径都有可读响应。AI 功能最怕只测成功样例,真实用户一定会提交超长输入、空输入和重复点击。把这些限制提前写进脚本,才算真正修完。