Skip to content
Edge Functions

递归 / 嵌套函数调用

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
  • 分发模式:一个函数同时调用多个其他函数

速率限制预算 #

🌐 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.

处理速率限制错误 #

🌐 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 item
for (const item of items) {
await supabase.functions.invoke('process-item', { body: item })
}
// ✅ Better: Batch items into one call
await 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.ts
export function (: any) {
// validation logic
}
export function (: any) {
// transformation logic
}
export async function (: any) {
// save logic
}
// supabase/functions/process-data/index.ts
import { 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.