Skip to content

finding(objectql): LifecycleService 的无 guard reap 每次 sweep 发一条无上限 DELETE —— 首次给存量大表加 retention 时是一条长事务 #5194

Description

@os-zhuang

观察类 finding,来自 #5179 的实现复核(PM 在派发里专门问过「清理批量要有上限」)。今天没有用户会撞到,但记录在案由 PM 定级。

事实(origin/main)

  • LifecycleService.reap()(packages/objectql/src/lifecycle/lifecycle-service.ts:777-783)在没有注册 reap guard 的对象上,一次 sweep 发的是单条 engine.delete(object, { where: { created_at: { $lt: cutoff }, ...onlyWhen }, multi: true }) —— 匹配多少行删多少行,没有 limit、没有分批;
  • 分批只存在于两条旁路:guardedReap(REAP_GUARD_BATCH_SIZE = 500 × REAP_GUARD_MAX_BATCHES_PER_SWEEP = 20,:924)和 Archiver(ARCHIVE_BATCH_SIZE = 500 × 20)。二者的注释都写明分批的理由是「bound one sweep's work, drain the backlog across sweeps」—— 同一个理由对无 guard 的 reap 同样成立,只是没落实;
  • 后果场景:一张已经积了大量行的表第一次被加上 retention(正是 service-queue: completed 任务行无人清理 —— purge() 零生产调用方、sys_job_queue 未声明 retention,队列表只增不减 #5179 对 sys_job_queue 做的事,也是任何新声明 lifecycle 的表的必经一次),那次 sweep 会发一条扫遍历史行的 DELETE。SQLite 上是一条长写事务(单连接池期间其它写者等锁),Postgres 上是一次大 autovacuum 债务。
  • 稳态下没有问题:每小时一扫,增量很小。

为什么单独记而不是在 #5179 里改

这是 LifecycleService 对所有 lifecycle 表的共性行为(sys_activity 14d、sys_job_run 30d 一直如此),不是队列表特有;在 #5179 的文件面(packages/services/service-queue)里也够不着。若要修,合适的形状是给无 guard 的 reap 也套上 BATCH × MAX_BATCHES_PER_SWEEP 的既有姿态(需要 driver 侧支持带 limit 的 delete,或退化为「读 id → 按 id 删」),代价是要在两种删除路径之间做取舍 —— 值得单独定夺,别当成 #5179 的搭车。

Found-during: #5179 / PR #5192

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions