深入理解 Supabase Cron:基于 pg_cron 的 Postgres 内建定时任务模块
本文以 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 seconds~59 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:任务定义表,核心列为jobid、jobname、schedule、command、active;cron.job_run_details:每次运行一张记录,含start_time、end_time、status(成功/失败)、错误信息等,由此可推算出运行时长。
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 JOIN 回 cron.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/pgcron-logs.tsx?utm_source=gitcode_repo_files)),对应“Real-time monitoring”特性中的排障能力。 - SQL 路径:直接用
cron.schedule/cron.unschedule管理任务,或用 SQL 查询cron.job、cron.job_run_details。e2e 测试 e2e/studio/features/cron-jobs.spec.ts 覆盖了 Cron 任务页的创建与操作链路。
技术细节汇总与使用限制
将模块文档“Technical Details”一节与仓库证据合并,可得到完整的速查结论:
| 项目 | 取值 | 依据 |
|---|---|---|
| 底层扩展 | pg_cron(开源) | pg_cron 扩展指南 |
| 最小调度间隔 | 1 秒(1 seconds 格式,1–59 秒) |
模块文档 + cron.ts 的 x 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.job、cron.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 内部稳定地跑起周期性自动化。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00