跳转到内容
use-cases / cleanup-jobs-self-retire / hero
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 条目就是它自己脚本的最后一行。

use-cases / cleanup-jobs-self-retire / lifecycle

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

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

LIFECYCLE · SCHEDULE → SLICE → SELF-DELETE一个条目,一段有限的生命
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。

use-cases / cleanup-jobs-self-retire / mechanism

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

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

create-entry.sh
DAY 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
DAY 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——脚本可以从条目命令行传入的文件里读到,也可以在运行时从 $HOODY_ENTRY_ID 读。无论哪种,cron 条目都在删除 cron 条目自己。

use-cases / cleanup-jobs-self-retire / powers

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

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

无压力节奏

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

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

无僵尸 CRONTAB

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

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

两个触发器,一个结果

自我 DELETE + expires_at 兜底

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

use-cases / cleanup-jobs-self-retire / capacity

它的扩展是这样的形状

每个托管条目就是 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 访问,如果你需要更老的形态。

use-cases / cleanup-jobs-self-retire / punchline

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

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

它替代了什么

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

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

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

阅读 Cron 文档
use-cases / cleanup-jobs-self-retire / related

阅读其他内容