递归 / 嵌套函数调用
Understanding rate limits when Edge Functions invoke each other
Edge Functions 可以使用 fetch() 调用其他 Edge Functions。这可以实现像函数链、分支/合并工作流以及递归处理这样强大的模式。为了保护平台稳定性并防止失控放大,Supabase 会对这些内部的函数到函数调用进行速率限制。
🌐 Edge Functions can call other Edge Functions using fetch(). This enables powerful patterns like function chaining, fan-out/fan-in workflows, and recursive processing. To protect platform stability and prevent runaway amplification, Supabase rate limits these internal function-to-function calls.
什么会被限速 #
🌐 What gets rate limited
速率限制适用于你的 Edge Functions 向项目内的其他 Edge Functions 发出的 出站 fetch() 调用。这包括:
🌐 Rate limiting applies to outbound fetch() calls made by your Edge Functions to other Edge Functions within your project. This includes:
- 直接递归:一个函数调用它自身
- 函数链式调用:函数A调用函数B
- 循环调用:函数A调用函数B,而函数B又调用函数A
- 分发模式:一个函数同时调用多个其他函数
对你 Edge Functions 的入站请求以及对外部 API(例如 Stripe、OpenAI)的请求不受此速率限制。只有从一个 Edge Function 向另一个 Edge Function 的出站调用才会被计数。
🌐 Inbound requests to your Edge Functions and requests to external APIs (e.g., Stripe, OpenAI) are not subject to this rate limit. Only outbound calls from one Edge Function to another Edge Function are counted.
速率限制预算 #
🌐 Rate limit budget
每个请求链(或 trace)都有 60 秒内 30 次请求 的限制。所有在同一个 trace 下的函数间调用都会计入这个限制。
🌐 Each request chain (or trace) has a budget of 30 requests within a 60-second window. All function-to-function calls that share the same trace count toward this budget.
例如,如果函数 A 调用函数 B,而函数 B 又调用函数 C,这三个调用都算在同一个 30 次请求的额度内。
🌐 For example, if Function A calls Function B, and Function B calls Function C, all three calls count toward the same 30-request budget.
一次追踪是指当一个 Edge 函数调用另一个函数时开始的一系列调用。预算是按每次追踪计算的,而不是按项目计算,所以同一个函数的两次独立调用各自都有自己的预算。
🌐 A trace is the chain of calls that starts when one Edge Function invokes another. The budget applies per trace, not per project, so two separate invocations of the same function each get their own budget.
处理速率限制错误 #
🌐 Handling rate limit errors
当超过速率限制时,调用另一个 Edge Function 会抛出 RateLimitError。这个错误包含一个 retryAfterMs 属性,指示在重试之前需要等待多久(以毫秒为单位)。你应该捕获这个错误并优雅地处理它:
🌐 When the rate limit is exceeded, calling another Edge Function throws a RateLimitError. This error includes a retryAfterMs property indicating how long to wait (in milliseconds) before retrying. You should catch this error and handle it gracefully:
import { createClient } from 'jsr:@supabase/supabase-js@2'const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)const supabase = createClient( Deno.env.get('SUPABASE_URL')!, // If you want to use a different api key, change 'default' to your preferred key name SUPABASE_PUBLISHABLE_KEYS['default'])Deno.serve(async (req) => { try { const { data, error } = await supabase.functions.invoke('other-function', { body: { foo: 'bar' }, }) if (error) throw error return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }) } catch (err) { if (err instanceof Deno.errors.RateLimitError) { // Use retryAfterMs to tell the client when to retry const retryAfterSeconds = Math.ceil(err.retryAfterMs / 1000) return new Response( JSON.stringify({ error: 'Service temporarily unavailable. Please retry later.' }), { status: 429, headers: { 'Content-Type': 'application/json', 'Retry-After': retryAfterSeconds.toString(), }, } ) } throw err }})你也可以使用 retryAfterMs 在你的函数中实现自动重试:
🌐 You can also use retryAfterMs to implement automatic retries within your function:
import { createClient } from 'jsr:@supabase/supabase-js@2'const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)const supabase = createClient( Deno.env.get('SUPABASE_URL')!, // If you want to use a different api key, change 'default' to your preferred key name SUPABASE_PUBLISHABLE_KEYS['default'])async function invokeWithRetry(functionName: string, payload: object, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const { data, error } = await supabase.functions.invoke(functionName, { body: payload, }) if (error) throw error return data } catch (err) { if (err instanceof Deno.errors.RateLimitError && attempt < maxRetries - 1) { // Wait for the recommended duration before retrying await new Promise((resolve) => setTimeout(resolve, err.retryAfterMs)) continue } throw err } }}避免速率限制的小贴士 #
🌐 Tips for avoiding rate limits
1. 批量操作而不是单次调用 #
🌐 1. Batch operations instead of individual calls
与其对每个项目调用一次函数,不如把多个项目批量放进一次调用:
🌐 Instead of calling a function once per item, batch multiple items into a single call:
// ❌ Avoid: One call per itemfor (const item of items) { await supabase.functions.invoke('process-item', { body: item })}// ✅ Better: Batch items into one callawait supabase.functions.invoke('process-items', { body: { items } })2. 限制递归深度 #
🌐 2. Limit recursion depth
如果你的函数是递归的,设置一个最大深度以防止无限调用链:
🌐 If your function is recursive, set a maximum depth to prevent unbounded call chains:
Deno.serve(async (req) => { const { depth = 0, data } = await req.json() if (depth >= 5) { // Stop recursion at max depth return new Response(JSON.stringify({ result: data })) } // Process and recurse with incremented depth const processed = processData(data) const { data: result } = await supabase.functions.invoke('my-function', { body: { depth: depth + 1, data: processed }, }) return new Response(JSON.stringify(result))})3. 对大工作量使用队列 #
🌐 3. Use queues for large workloads
处理大型数据集时,可以考虑使用 Supabase Queues 而不是递归函数调用。队列会自动处理回压,更适合高容量的工作负载。
🌐 For processing large datasets, consider using Supabase Queues instead of recursive function calls. Queues handle backpressure automatically and are better suited for high-volume workloads.
4. 使用共享库而不是单独的函数 #
🌐 4. Use shared libraries instead of separate functions
与其创建相互调用的独立 Edge 函数,不如创建一个共享函数库并直接导入。这可以完全避免 HTTP 开销和速率限制:
🌐 Instead of creating separate Edge Functions that call each other, create a shared library of functions and import them directly. This avoids HTTP overhead and rate limits entirely:
// supabase/functions/_shared/transform.tsexport function (: any) { // validation logic}export function (: any) { // transformation logic}export async function (: any) { // save logic}// supabase/functions/process-data/index.tsimport { save, transform, validate } from '../_shared/transform.ts'Deno.serve(async (req) => { const data = await req.json() const validated = validate(data) const transformed = transform(validated) const result = await save(transformed) return new Response(JSON.stringify(result))})5. 为非紧急处理添加延迟 #
🌐 5. Add delays for non-urgent processing
如果不需要立即处理,可以在调用之间添加延迟来分散负载:
🌐 If immediate processing isn't required, add delays between calls to spread the load:
async function processWithDelay(items: any[]) { for (const item of items) { await supabase.functions.invoke('process-item', { body: item }) await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay }}常见模式及其影响 #
🌐 Common patterns and their impact
| 模式 | 预算消耗 | 建议 |
|---|---|---|
| 基本链(A 到 B 到 C) | 低 | 一般安全 |
| 分支(A 到 B, C, D, E) | 中等 | 限制并发 |
| 深度递归(A 到 A 到 A...) | 高 | 设置最大深度 |
| 无限循环 | 非常高 | 尽量避免,使用队列 |
提高速率限制 #
🌐 Increasing rate limits
目前,所有计划的速率限制预算都是相同的。我们正在努力为不同的使用场景引入自定义限制。
🌐 Currently, all plans have the same rate limit budget. We are working on introducing custom limits for different use cases.
如果你的项目需要更高的速率限制,请通过 联系支持 提供你的使用案例详情。
🌐 If you need a higher rate limit for your project, contact support with details about your use case.