首页
/ 深入理解 Supabase Cron:基于 pg_cron 的 Postgres 内建定时任务模块

深入理解 Supabase Cron:基于 pg_cron 的 Postgres 内建定时任务模块

2026-09-06 16:25:05作者:虞亚竹Luna

本文以 Supabase 仓库中的模块文档 apps/www/content/md/modules/cron.md 为核心,系统讲解 Supabase Cron 这一 Postgres 模块:它如何用 pg_cron 扩展实现分钟级乃至秒级的任务调度,如何用 cron 表达式或自然语言定义计划,如何调用 SQL、数据库函数、Edge Function 或 HTTP Webhook 作为任务目标,以及任务运行历史如何被记录与监控。读完本文,你将掌握该模块的完整调度语法、四类任务目标的配置细节、底层数据模型(cron.job / cron.job_run_details)以及 Studio 与 SQL 两种管理路径的实现依据。

什么是 Supabase Cron

Supabase Cron 是一个运行在 Postgres 内部的定时任务模块,它基于开源扩展 pg_cron 来调度和管理周期性任务(recurring jobs)。按照模块文档的定义:你可以用标准 cron 语法或自然语言定义计划,并运行那些调用数据库函数、Supabase Edge Function 或远程 Webhook 的任务。

与常见的“外挂式”调度器(独立进程 + 消息队列)不同,Supabase Cron 的调度和执行引擎就嵌在你的 Postgres 实例里:pg_cron 扩展会在数据库中创建一个 cron schema,所有任务定义存放在 cron.job 表,每次任务运行的状态则记录在 cron.job_run_details 表中。官方指南 Cron Guide 对这一工作模型做了明确描述:

  • 任务可以运行 SQL 片段或数据库函数(数据库内部执行,零网络延迟),也可以发起 HTTP 请求(例如调用 Supabase Edge Function);
  • 任务的调度频率可以从“每 1 秒一次”一直覆盖到“每年一次”,取决于具体使用场景;
  • 官方给出的性能建议:并发运行的任务不要超过 8 个,单个任务运行时长不应超过 10 分钟(见 apps/docs/content/guides/cron.mdx 中的 note 说明)。

关键特性

模块文档列出了 Supabase Cron 的核心特性,这里逐条结合仓库实现加以印证:

特性 说明 仓库中的实现依据
Postgres 原生 任务直接在数据库内部调度和执行,无需外部调度器 全部调度 SQL 直接操作 cron.job / cron.job_run_details 系统表
Cron 语法 + 自然语言 既可用经典 cron 表达式,也可用英文描述生成计划 自然语言转换逻辑见 packages/ai-commands/src/sql/cron.ts
亚分钟级调度 最快可每 1–59 秒执行一次 pg_cron 特有的 x seconds 计划格式(下文详解)
实时监控 内置可观测性工具跟踪、调试任务运行 运行历史查询与清理逻辑见 packages/pg-meta/src/sql/studio/database/cron-jobs.ts
可扩展的任务目标 可触发数据库函数、Edge Function 或 HTTP Webhook Studio 表单支持 4 种任务类型(下文详解)
Dashboard 管理 通过直观的 UI 创建、编辑、监控任务 Studio 的 Integrations → Cron 页面,前端数据层见 apps/studio/data/database-cron-jobs/ 目录
SQL 驱动 用简单 SQL 管理任务,变更可纳入 Postgres 迁移 创建/启停/删除任务的底层全部是 SQL mutation
100% 开源 构建在 pg_cron 这一社区扩展之上 模块文档与 pg_cron 扩展指南

常见使用场景

模块文档列出的典型用途覆盖了“周期性自动化”的主要形态:

  • 周期性数据清理与归档——例如删除过期的运行历史、归档冷数据;
  • 定时报表生成——每天/每周汇总统计;
  • 周期性 API 调用或 Webhook 触发——主动推送数据到外部系统;
  • 数据库维护任务——VACUUM、重建索引等;
  • 定时缓存失效
  • 系统间周期性数据同步

值得注意的一个真实案例来自仓库自身:Studio 内置了一个名为 delete-job-run-details 的清理任务,用 cron 来清理 cron.job_run_details 中过期的运行记录(下文“运行历史”一节详述)——这正是“周期性数据清理”场景在生产代码里的直接体现。

调度计划:Cron 表达式与“x seconds”格式

标准 5 字段 cron 语法

Supabase Cron 使用 pg_cron 支持的 5 字段格式,字段顺序为:

字段 取值范围 说明
minute(分钟) 0–59 每分钟触发时使用 *
hour(小时) 0–23 每天固定时刻触发
day(日) 1–31 每月指定日期
month(月) 1–12 指定月份
weekday(星期) 0–6,0 代表周日 每周指定星期

仓库中 packages/ai-commands/src/sql/cron.ts 的自然语言转 cron 提示词给出了官方示例,可直接作为速查表:

* * * * *        每分钟
*/5 * * * *     每 5 分钟
0 0 1 * *       每月 1 日 00:00
0 0 * * *       每天午夜
0 3 * * 1       每周一 03:00

另外,Studio 的日程解析器对 pg_cron 特有符号做了归一化处理:pg_cron 用 $ 表示“月末最后一天”,而通用解析库 cronstrue 用 L 表示同一含义,CreateCronJobSheet.constants.ts 中的 convertCronToString 会在解析前把 $ 替换为 L,解析失败时回退显示原始表达式。

亚分钟级调度:“x seconds” 语法

模块文档的“Sub-minute scheduling”特性对应 pg_cron 的一个特殊能力:当计划低于分钟粒度时,不使用 5 字段格式,而是直接写 x seconds(x 为 1–59 的整数):

30 seconds       每 30 秒
15 seconds       每 15 秒
45 seconds       每 45 秒

这一点在 packages/ai-commands/src/sql/cron.ts 的专家提示词中被反复强调:“pg_cron uses 'x seconds' for second-based intervals, not 'x * * * *'”。模块文档“Technical Details”一节给出的 Minimum interval 为 1 秒,与 1 seconds59 seconds 的取值范围一致。

自然语言定义计划

模块文档宣称支持“plain English to define intervals”,仓库中对应的实现是 generateCron 函数:它把用户输入的时间描述交给 LLM,并通过 Function Calling 强制输出单一结果 cron_expression。从源码看,该实现有两个值得注意的工程细节:

  • 使用 gpt-4o-mini-2024-07-18 模型,temperature: 0、固定函数调用(tool_choice),以最大限度保证输出的确定性;
  • 对模型返回的 JSON 参数先经过 jsonrepair 修复再解析,防止格式抖动导致解析失败。

Studio 通过 apps/studio/routes/api/ai/sql/cron-v2.ts 将该能力暴露为内部 API,供 Dashboard 的 Cron 创建表单调用,实现“输入一句话 → 得到可写入 cron.job 的计划表达式”。

四类任务目标:SQL、函数、Edge Function、HTTP

模块文档说 Job targets 包括 “SQL statements, database functions, Edge Functions, HTTP endpoints”。Studio 的创建任务表单把这一能力细化为 4 个 Zod schema,定义在 apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet.constants.ts

1. sql_snippet(SQL 片段)

const sqlSnippetSchema = z.object({
  type: z.literal('sql_snippet'),
  snippet: z.string().trim().min(1),
})

直接执行任意 SQL,数据库内部完成,零网络延迟,是性能最好的目标类型。

2. sql_function(数据库函数)

const sqlFunctionSchema = z.object({
  type: z.literal('sql_function'),
  schema: z.string().trim().min(1, 'Please select one of the listed database schemas'),
  functionName: z.string().trim().min(1, 'Please select one of the listed database functions'),
  snippet: z.string().trim(),
})

schema + function name 定位已有函数;编辑任务时会保留原始 command 作为 snippet,方便用户手动改写。

3. edge_function(Supabase Edge Function)

const edgeFunctionSchema = z.object({
  type: z.literal('edge_function'),
  method: z.enum(['GET', 'POST']),
  edgeFunctionName: z.string().trim().min(1, 'Please select one of the listed Edge Functions'),
  timeoutMs: z.coerce.number().int().gte(1000).lte(5000).default(DEFAULT_TIMEOUT), // 默认 1000ms
  httpHeaders: httpHeadersSchema,
  httpBody: z.string().trim().optional() /* 必须为合法 JSON */,
  snippet: z.string().trim(),
})

调用约束很明确:仅支持 GET/POST;超时时间 timeoutMs 必须在 1000–5000 毫秒 之间,默认 1000 毫秒;请求头是 name/value 键值对数组(名称和值均必填);请求体如填写必须是合法 JSON。

4. http_request(远程 HTTP 端点)

const httpRequestSchema = z.object({
  type: z.literal('http_request'),
  method: z.enum(['GET', 'POST']),
  endpoint: httpEndpointUrlSchema({ ... }), // 必须以 http:// 或 https:// 开头
  timeoutMs: z.coerce.number().int().gte(1000).lte(5000).default(DEFAULT_TIMEOUT),
  httpHeaders: httpHeadersSchema,
  httpBody: z.string().trim().optional(),
  snippet: z.string().trim(),
})

与 Edge Function 相同的超时与 JSON 体约束,区别仅在于端点可以是任意合法 URL。

表单层面对计划表达式的校验同样值得注意:schedule 字段会先尝试匹配标准 cron 模式(cronPattern),并配合 supportsSeconds 标志放行 x seconds 格式,两者都不满足才报校验失败——这与 pg_cron 的双格式能力一一对应。

底层数据模型与运行历史监控

模块文档的“Real-time monitoring”特性——“track and debug scheduled jobs”“job run history with status, duration, and error details”——对应到数据库层就是两张系统表:

  • cron.job:任务定义表,核心列为 jobidjobnameschedulecommandactive
  • cron.job_run_details:每次运行一张记录,含 start_timeend_timestatus(成功/失败)、错误信息等,由此可推算出运行时长。

Studio 与 SQL 元数据服务共用的查询逻辑集中在 packages/pg-meta/src/sql/studio/database/cron-jobs.ts,其中有两段实现很能说明“监控”是如何落地的:

1. 任务列表 + 最近一次运行状态。 getCronJobsSql 用 CTE 先从 cron.job_run_details 中按 jobid, status 分组找出每个任务的最近一次运行时间(MAX(start_time)),再 LEFT JOINcron.job,最终输出 jobid / jobname / schedule / command / active / latest_run / status

WITH latest_runs AS (
  SELECT jobid, status, MAX(start_time) AS latest_run
  FROM cron.job_run_details
  GROUP BY jobid, status
), most_recent_runs AS ( ... )
SELECT job.jobid, job.jobname, job.schedule, job.command, job.active,
       mr.latest_run, mr.status
FROM cron.job job
LEFT JOIN most_recent_runs mr ON job.jobid = mr.jobid
ORDER BY job.jobid
LIMIT :limit OFFSET :page * :limit;

2. 运行历史的批量清理。 cron.job_run_details 会随运行次数无限增长,仓库内置了一个自举式清理任务:用 cron.schedule 注册一个名为 delete-job-run-details 的任务,计划表达式 0 12 * * *(每天 12:00),命令是删除 end_time 早于保留期阈值的记录。为避免一次性大事务锁表,它配套了按 ctid(物理行位置)分片删除的 SQL getDeleteOldCronJobRunDetailsByCtidSql,以及用 pg_relation_size(oid) / block_size 计算总页数用于分批迭代的辅助查询;批大小常量 CTID_BATCH_PAGE_SIZE = 5000 定义在 apps/studio/data/database-cron-jobs/database-cron-jobs.utils.ts,源码注释说明其依据是默认 128 MB shared buffers 约可容纳 1.6 万页,取 5000 页/批既限制扫描范围又允许批间放行其他查询。

-- getScheduleDeleteCronJobRunDetailsSql 生成的注册 SQL(interval 为保留期)
SELECT cron.schedule(
  'delete-job-run-details',
  '0 12 * * *',
  'DELETE FROM cron.job_run_details WHERE end_time < now() - interval :interval;'
);

这段源码同时印证了模块文档“SQL-based”特性:任务注册就是普通的 SELECT cron.schedule(...) 语句,完全可以放进 Postgres migration 里做版本管理。

两种管理路径:Dashboard 与 SQL

模块文档强调“Dashboard management”与“SQL-based”并存的定位,仓库实现也严格对应这两条路径:

  • Dashboard(Studio)路径:任务页位于 Integrations → Cron。前端数据层位于 apps/studio/data/database-cron-jobs/,基于 TanStack Query 封装了完整的 mutation 集合:创建(database-cron-jobs-create-mutation.ts)、启停切换(-toggle-mutation.ts)、手动运行(-run-mutation.ts)、删除(-delete-mutation.ts),以及列表、计数、运行历史的无限滚动查询。从源码结构看,这些 mutation 最终都收敛到统一的 executeSql——即 UI 操作本质上就是代理执行 SQL,与直接写 SQL 完全等价,因此 Dashboard 里看到的任务和 cron.job 表里的记录是同一份数据。此外,Studio 的 Logs 页面还单独提供了 pg_cron 相关日志视图([apps/studio/routes/project/ref/logs/pgcronlogs.tsx](https://gitcode.com/GitHubTrending/supa/supabase/blob/e0280cb650d29ded05c080e35a52d22bf9dd84b9/apps/studio/routes/project/ref/logs/pgcron-logs.tsx](https://gitcode.com/GitHub_Trending/supa/supabase/blob/e0280cb650d29ded05c080e35a52d22bf9dd84b9/apps/studio/routes/project/ref/logs/pgcron-logs.tsx?utm_source=gitcode_repo_files)),对应“Real-time monitoring”特性中的排障能力。
  • SQL 路径:直接用 cron.schedule / cron.unschedule 管理任务,或用 SQL 查询 cron.jobcron.job_run_details。e2e 测试 e2e/studio/features/cron-jobs.spec.ts 覆盖了 Cron 任务页的创建与操作链路。

技术细节汇总与使用限制

将模块文档“Technical Details”一节与仓库证据合并,可得到完整的速查结论:

项目 取值 依据
底层扩展 pg_cron(开源) pg_cron 扩展指南
最小调度间隔 1 秒(1 seconds 格式,1–59 秒) 模块文档 + cron.tsx seconds 规则
计划格式 5 字段 cron 语法(minute/hour/day/month/weekday)或自然语言(经 LLM 转为 cron 表达式) 同上
任务目标 SQL 片段、数据库函数、Edge Function、HTTP 端点(GET/POST,超时 1000–5000ms) CreateCronJobSheet.constants.ts
监控 cron.job_run_details 运行历史:状态、起止时间、错误详情 cron-jobs.ts
数据模型 cron schema 下的 cron.jobcron.job_run_details Cron Guide
并发与时长建议 并发任务 ≤ 8,单任务运行 ≤ 10 分钟 Cron Guide

由于 pg_cron 的调度器(launcher/worker)与 Postgres 实例强耦合,实例重启、扩展升级等场景也可能影响任务行为。仓库的 troubleshooting 区提供了两篇针对性排障文档,可作为线上问题的第一手参考:pg_cron launcher 因唯一键冲突崩溃pg_cron 调试指南

小结

Supabase Cron 的价值在于把“调度器”变成了数据库的一部分:任务定义就是 cron.job 里的一行数据,运行历史就是 cron.job_run_details 里的查询结果,注册、启停、清理都可以用普通 SQL 表达并纳入 migration。对开发者而言,掌握三件事即可覆盖绝大多数场景——用 5 字段 cron 表达式或 x seconds 格式定义计划(必要时借助自然语言转换)、在 SQL 片段/函数/Edge Function/HTTP 四类目标中按延迟敏感度选型、通过 Studio 的 Cron 页面或 SQL 查询监控运行历史;同时记住“并发不超过 8、单任务不超 10 分钟”的容量建议,即可在 Postgres 内部稳定地跑起周期性自动化。

登录后查看全文
热门项目推荐
相关项目推荐