跳转到内容
类型解锁
阶段生产环境
难度简单
工作自动化任务
用于个人创始人
用于后端开发者
服务Cron
服务文件
为何选 HoodyHTTP 原生
类型解锁
阶段生产环境
难度简单
工作自动化任务
用于个人创始人
用于后端开发者
服务Cron
服务文件
为何选 HoodyHTTP 原生
类型解锁
阶段生产环境
难度简单
工作自动化任务
用于个人创始人
用于后端开发者
服务Cron
服务文件
为何选 HoodyHTTP 原生
类型解锁
阶段生产环境
难度简单
工作自动化任务
用于个人创始人
用于后端开发者
服务Cron
服务文件
为何选 HoodyHTTP 原生
CRON · FILES · 自我退役

会安排自己退休的清理任务

在清理开始那天 provision 一个托管 cron 条目。把 expires_at 设在最后一次预期运行之后几天。脚本每晚一片片地切完工作,然后在没东西可清时把自己 DELETE 掉。无需日历提醒、无僵尸 crontab、无年度「清理清理者」复盘。

Cron 文档
cleanup-stale-uploads.sh
# tail of cleanup-stale-uploads.sh
removed=$(find /uploads -mtime +90 -delete -print | wc -l)
echo "$removed files removed"

# self-aware tail: nothing left → retire
if [ -z "$(ls /uploads)" ]; then
  curl -fsS -X DELETE \
    "$CRON_URL/entries/$ENTRY_ID"
fi

会自我察觉的尾巴。cron 条目就是它自己脚本的最后一行。

按构造就具有有限生命的清理

三个离散阶段。每次过渡都是机械的,没有一个需要人来记。条目自己知道何时活儿干完、何时日历位也该结束。

生命周期 · 调度 → 切片 → 自删除一个条目,一段有限的生命
1 · SCHEDULE

在清理开始那天 POST 一个条目

schedule @daily,command 指向切片脚本,expires_at 设在最后一次预期运行之后一点。截止日在条目里——不在 Notion 文档里、不在 Slack 串里。

2 · SLICE

脚本每晚轻轻地切一刀

每次运行删一片过期数据,这样数据库不会被锤。Day 1 可能清 247 个文件,Day 6 只剩 1 个。节奏受真正剩下多少东西约束。

3 · SELF-DELETE

活儿干完,条目退役

脚本最后的一段检查目标是否已空。如果是,触发 DELETE /entries/[self]。如果出于某种原因没触发,几天后 expires_at 兜底。

两个独立的触发器——脚本自己的检查和 API 的 expires_at——汇合到同一个结果:一行不会活过自身用途的 crontab。

两次 HTTP 调用,一次有限清理

Hoody Cron 是系统 crontab 之上的一个 JSON-CRUD 包装。POST 创建条目,DELETE 移除条目,expires_at 是兜底。每晚跑的那个脚本知道自己什么时候做完——所以由它来调用 DELETE。

create-entry.sh
第 0 天 · POST
# day 0 — provision the cleanup
curl -X POST \
  https://cron.containers.hoody.com/users/me/entries \
  -H "Content-Type: application/json" \
  -d '["schedule":"@daily","command":"/srv/jobs/cleanup-stale-uploads.sh","expires_at":"2026-05-05T00:00:00Z"]'

# response
HTTP/1.1 201 Created
{ "id":"f3a1", "expires_at":"2026-05-05T00:00:00Z", "enabled":true }
cleanup-stale-uploads.sh
第 7 天 · DELETE
# inside the cron command itself
if [ -z "$(ls /uploads)" ]; then
  curl -X DELETE \
    "$CRON_URL/entries/$ENTRY_ID"
fi

# response
HTTP/1.1 204 No Content
# entry f3a1 was here. f3a1 deleted itself.

$ENTRY_ID 是 POST 返回的 UUID。通过命令行传递,使脚本可从文件或 argv 读取。无论哪种方式,cron 条目自行删除。

自我退役的清理解锁了什么

重要的不是删除本身。重要的是三个月后没人需要记得这些东西的存在。

无压力节奏

一片片来,而不是一次干完

@daily 每 24 小时一次。脚本删一片过期数据——几千个文件、几千行——然后退出。数据库保持平静;负载曲线看上去像什么都没发生。

无僵尸 CRONTAB

这一行不会比工程师活得更久

expires_at 作为 JSON 在条目里。它一触发,这一行就从系统 crontab 里被移除。三任工程师之后,没人会翻 200 行 crontab 想 cleanup-stale-uploads-v3 还在干啥。

两个触发器,一个结果

自我 DELETE + expires_at 兜底

活儿干完那晚脚本 DELETE 掉自己。如果有 bug 跳过了那条路径,几天后 expires_at 让条目退役。两个独立机制;其中一个一定会触发。

它的扩展是这样的形状

每个托管条目就是 API 注入到系统 crontab 里的一行 JSON。扩展受限于 cron 自己能容纳什么,而不是 Hoody。

  1. 运行节奏1 / 天

    @daily 是清理的标准节奏。如果你需要更频繁,可以一路用 5 字段表达式直到 * * * * *——分钟级精度。

  2. 条目寿命expires_at

    条目上的一个 ISO-8601 时间戳。一旦过期,API 会在下一次扫描时把这一行移除。清理永远不会赖在自己的截止日之后。

  3. 拆除 HTTPDELETE · 204

    在运行中的 command 内部对 DELETE /users/[user]/entries/[id] 调用之所以可行,是因为 cron 守护进程不锁自己的 crontab——API 安全地把变更扫进来。

标准 5 字段 cron 表达式加上宏(@hourly, @daily, @weekly, @monthly, @yearly)。按用户隔离;每个系统用户拥有自己的 crontab。Hoody Kit 的 Cron 页面同时记录了托管条目和原生 crontab 访问,如果你需要更老的形态。

清理每晚跑,直到要清的东西消失。

BEFORE · 日历提醒、僵尸 cron 行AFTER · 一个知道自己何时该停的 cron 条目
BEFORE// TODO: delete this when /uploads is empty (2026?)未来的你只会读一次,然后再也不读的注释。
现在DELETE /entries/$ENTRY_ID · expires_at: 2026-05-05由脚本触发的拆除,加上写进条目本身的截止日。
Cron API 参考

它替代了什么

凡是清理任务理应自我消失的地方——它替代的是这些模式:

  • 僵尸清理 cron 任务2022 年留下的行,没人敢删
  • // TODO: delete this when X is empty一条变成永久的注释
  • 手动的清洁脚本队友想起来时季度跑一次
  • 永远不删除自己的 Kubernetes CronJobsttlSecondsAfterFinished 在 pod 上,而不是在调度上
  • 自制的「会自我察觉」生命周期管理器一个看着你其他服务的自家服务
  • Terraform 管理的生命周期策略plan、apply、六个月后还在 tfstate 里

Provision 这次清理。设好它的退役日。然后走开。

阅读 Cron 文档

阅读其他内容