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

每个请求链的预算至少为 每分钟 5,000 次请求。在更繁忙的地区,这个预算可能会更高。同一个请求链中的所有函数到函数的调用都共享这个预算。

🌐 Each request chain has a budget of at least 5,000 requests per minute. In busier regions, this budget may be higher. All function-to-function calls within the same request chain share this budget.

例如,如果函数A调用函数B,而函数B又调用函数C,这三个调用都算在同一个预算池里。

🌐 For example, if Function A calls Function B, and Function B calls Function C, all three calls count toward the same budget pool.

处理速率限制错误 #

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

1
import { createClient } from 'jsr:@supabase/supabase-js@2'
2
3
const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
4
5
const supabase = createClient(
6
Deno.env.get('SUPABASE_URL')!,
7
// If you want to use a different api key, change 'default' to your preferred key name
8
SUPABASE_PUBLISHABLE_KEYS['default']
9
)
10
11
Deno.serve(async (req) => {
12
try {
13
const { data, error } = await supabase.functions.invoke('other-function', {
14
body: { foo: 'bar' },
15
})
16
17
if (error) throw error
18
19
return new Response(JSON.stringify(data), {
20
headers: { 'Content-Type': 'application/json' },
21
})
22
} catch (err) {
23
if (err instanceof Deno.errors.RateLimitError) {
24
// Use retryAfterMs to tell the client when to retry
25
const retryAfterSeconds = Math.ceil(err.retryAfterMs / 1000)
26
return new Response(
27
JSON.stringify({ error: 'Service temporarily unavailable. Please retry later.' }),
28
{
29
status: 429,
30
headers: {
31
'Content-Type': 'application/json',
32
'Retry-After': retryAfterSeconds.toString(),
33
},
34
}
35
)
36
}
37
throw err
38
}
39
})

你也可以使用 retryAfterMs 在你的函数中实现自动重试:

🌐 You can also use retryAfterMs to implement automatic retries within your function:

1
import { createClient } from 'jsr:@supabase/supabase-js@2'
2
3
const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
4
5
const supabase = createClient(
6
Deno.env.get('SUPABASE_URL')!,
7
// If you want to use a different api key, change 'default' to your preferred key name
8
SUPABASE_PUBLISHABLE_KEYS['default']
9
)
10
11
async function invokeWithRetry(functionName: string, payload: object, maxRetries = 3) {
12
for (let attempt = 0; attempt < maxRetries; attempt++) {
13
try {
14
const { data, error } = await supabase.functions.invoke(functionName, {
15
body: payload,
16
})
17
if (error) throw error
18
return data
19
} catch (err) {
20
if (err instanceof Deno.errors.RateLimitError && attempt < maxRetries - 1) {
21
// Wait for the recommended duration before retrying
22
await new Promise((resolve) => setTimeout(resolve, err.retryAfterMs))
23
continue
24
}
25
throw err
26
}
27
}
28
}

避免速率限制的小贴士 #

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

1
// ❌ Avoid: One call per item
2
for (const item of items) {
3
await supabase.functions.invoke('process-item', { body: item })
4
}
5
6
// ✅ Better: Batch items into one call
7
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:

1
Deno.serve(async (req) => {
2
const { depth = 0, data } = await req.json()
3
4
if (depth >= 5) {
5
// Stop recursion at max depth
6
return new Response(JSON.stringify({ result: data }))
7
}
8
9
// Process and recurse with incremented depth
10
const processed = processData(data)
11
const { data: result } = await supabase.functions.invoke('my-function', {
12
body: { depth: depth + 1, data: processed },
13
})
14
15
return new Response(JSON.stringify(result))
16
})

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:

1
// supabase/functions/_shared/transform.ts
2
export function validate(data: any) {
3
// validation logic
4
}
5
6
export function transform(data: any) {
7
// transformation logic
8
}
9
10
export async function save(data: any) {
11
// save logic
12
}
1
// supabase/functions/process-data/index.ts
2
import { save, transform, validate } from '../_shared/transform.ts'
3
4
Deno.serve(async (req) => {
5
const data = await req.json()
6
const validated = validate(data)
7
const transformed = transform(validated)
8
const result = await save(transformed)
9
return new Response(JSON.stringify(result))
10
})

5. 为非紧急处理添加延迟 #

🌐 5. Add delays for non-urgent processing

如果不需要立即处理,可以在调用之间添加延迟来分散负载:

🌐 If immediate processing isn't required, add delays between calls to spread the load:

1
async function processWithDelay(items: any[]) {
2
for (const item of items) {
3
await supabase.functions.invoke('process-item', { body: item })
4
await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay
5
}
6
}

常见模式及其影响 #

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