• erio's avatar
    feat(ops): allow retention days = 0 to wipe table on each scheduled cleanup · 4b6954f9
    erio authored
    Background / 背景
    
    The ops cleanup task currently rejects retention days < 1 in both validate
    and normalize, so operators who want minimal-history setups (e.g. high
    churn deployments that prefer near-realtime cleanup) cannot express that
    intent through the UI. The only options are 1+ days, which keeps at least
    24h of history regardless of cron frequency.
    
    ops 清理任务目前在 validate 和 normalize 两处都拒绝小于 1 的保留天数,
    让希望尽量不留历史的运维场景(高吞吐部署 + 想用近实时清理)无法通过 UI
    表达。最低只能配 1,等于不管 cron 多频繁,至少都会保留 24 小时的历史。
    
    Purpose / 目的
    
    Let admins set retention days to 0, meaning "every scheduled cleanup
    run wipes the corresponding table(s) entirely". Combined with a more
    frequent cron (e.g. `0 * * * *`) this yields effectively rolling cleanup.
    
    允许管理员把保留天数设为 0,语义为"每次定时清理时把对应表全部清空"。
    搭配更频繁的 cron(比如每小时整点)即可获得近似滚动清理的效果。
    
    Changes / 改动内容
    
    Backend
    
    - service/ops_settings.go: validate accepts [0, 365]; normalize only
      refills default 30 when value is < 0 (negative is treated as legacy
      bad data, 0 is honoured)
    - service/ops_cleanup_service.go: introduce `opsCleanupPlan(now, days)`
      returning `(cutoff, truncate, ok)`. days==0 returns truncate=true and
      short-circuits to a new `truncateOpsTable` helper that uses
      `TRUNCATE TABLE` (O(1), no WAL, no VACUUM pressure). days>0 keeps
      the existing batched DELETE path unchanged. Empty tables skip
      TRUNCATE to avoid the ACCESS EXCLUSIVE lock entirely
    - Extract `isMissingRelationError` helper to dedupe the "table not
      yet created" tolerance shared by both delete and truncate paths
    - Add unit tests for `opsCleanupPlan` (three branches) and
      `isMissingRelationError`
    
    后端
    
    - service/ops_settings.go: validate 接受 [0, 365];normalize 仅在 < 0
      时回填默认 30(负数视为脏数据,0 被尊重)
    - service/ops_cleanup_service.go: 抽 `opsCleanupPlan(now, days)` 返回
      `(cutoff, truncate, ok)`。days==0 → truncate=true,走新增
      `truncateOpsTable`(TRUNCATE TABLE,O(1),无 WAL、无 VACUUM 压力);
      days>0 仍走原批量 DELETE 路径,行为完全不变。空表跳过 TRUNCATE,
      避免无意义的 ACCESS EXCLUSIVE 锁
    - 抽 `isMissingRelationError` helper 复用 delete / truncate 两处的
      "表不存在"宽容判断
    - 补 `opsCleanupPlan` 三分支 + `isMissingRelationError` 单元测试
    
    Frontend
    
    - OpsSettingsDialog.vue: validation accepts [0, 365]; input min=0
    - i18n (zh/en): hint mentions "0 = wipe all on every cleanup",
      validation message updated to 0-365 range
    
    前端
    
    - OpsSettingsDialog.vue: 校验放宽到 [0, 365],input min 改 0
    - i18n(zh/en):hint 补"0 = 每次清理时清空所有",错误提示改 0-365
    
    Trade-offs / 取舍
    
    - TRUNCATE requires ACCESS EXCLUSIVE lock briefly, but ops tables only
      have the cleanup task as a writer, so the lock is invisible to other
      workloads
    - Empty-table guard avoids the lock when there is nothing to clean
    - Negative values are still treated as legacy bad data and replaced
      with default 30 to preserve compatibility
    4b6954f9
ops_cleanup_service.go 12.9 KB