递归 / 嵌套函数调用
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
每个请求链的预算至少为 每分钟 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:
1import { createClient } from 'jsr:@supabase/supabase-js@2'23const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)45const supabase = createClient(6 Deno.env.get('SUPABASE_URL')!,7 // If you want to use a different api key, change 'default' to your preferred key name8 SUPABASE_PUBLISHABLE_KEYS['default']9)1011Deno.serve(async (req) => {12 try {13 const { data, error } = await supabase.functions.invoke('other-function', {14 body: { foo: 'bar' },15 })1617 if (error) throw error1819 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 retry25 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 err38 }39})你也可以使用 retryAfterMs 在你的函数中实现自动重试:
🌐 You can also use retryAfterMs to implement automatic retries within your function:
1import { createClient } from 'jsr:@supabase/supabase-js@2'23const SUPABASE_PUBLISHABLE_KEYS = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)45const supabase = createClient(6 Deno.env.get('SUPABASE_URL')!,7 // If you want to use a different api key, change 'default' to your preferred key name8 SUPABASE_PUBLISHABLE_KEYS['default']9)1011async 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 error18 return data19 } catch (err) {20 if (err instanceof Deno.errors.RateLimitError && attempt < maxRetries - 1) {21 // Wait for the recommended duration before retrying22 await new Promise((resolve) => setTimeout(resolve, err.retryAfterMs))23 continue24 }25 throw err26 }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 item2for (const item of items) {3 await supabase.functions.invoke('process-item', { body: item })4}56// ✅ Better: Batch items into one call7await 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:
1Deno.serve(async (req) => {2 const { depth = 0, data } = await req.json()34 if (depth >= 5) {5 // Stop recursion at max depth6 return new Response(JSON.stringify({ result: data }))7 }89 // Process and recurse with incremented depth10 const processed = processData(data)11 const { data: result } = await supabase.functions.invoke('my-function', {12 body: { depth: depth + 1, data: processed },13 })1415 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.ts2export function validate(data: any) {3 // validation logic4}56export function transform(data: any) {7 // transformation logic8}910export async function save(data: any) {11 // save logic12}1// supabase/functions/process-data/index.ts2import { save, transform, validate } from '../_shared/transform.ts'34Deno.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:
1async 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 delay5 }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.