fix(automation): 修正调度口径——作业每 5 分钟跑一轮,interval_seconds 只是单行重检间隔

P1a 把两个「间隔」混成了一个:next_run_at 用的是 job.interval_seconds,于是
作业 6 小时才跑一轮、每轮 200 条 = 800 条/天,全库 12,881 条要 16 天才能过
一遍 —— 等于没巡检,与规划里「账号 6 小时内被查一次」的目标差了两个数量级。

按规划原意修正:
- TICK_SECONDS 60 → 300:作业每个 tick(5 分钟)跑一轮;
- next_run_at 改为 finished + TICK_SECONDS,不再用 interval_seconds;
- interval_seconds 只作为「同一行多久重新检测」的选行 cutoff(选行逻辑本来就
  是这么用的,无需改);
- 容量口径随之更新:5 分钟一轮 × 200 条 = 40 条/分钟 ≥ 需要的 36 条/分钟,
  冷启动约 5.4 小时覆盖全库,稳态每轮一百多条;
- 前端标签由「巡检周期(小时)」改为「单账号重检间隔(小时)」,说明文案写清
  「每 5 分钟跑一轮」,避免运维误以为是作业周期。

同步更新规划文档 §7.2/§7.3(含两个间隔不能混的说明)与附录 C 的三段上线
步骤(立即跑一次 → 每轮 50 观察 → 每轮 200 启用)。
This commit is contained in:
yml2213
2026-10-02 19:54:31 +08:00
parent 2379598800
commit 7aaaeebe2b
5 changed files with 49 additions and 23 deletions
+13 -3
View File
@@ -258,7 +258,7 @@ class TestScheduler(_Base):
run = self.session.query(AutomationRun).one()
assert run.status == "failed" and "接口挂了" in run.message
def test_next_run_follows_interval_and_counters_persist(self):
def test_next_run_follows_tick_and_counters_persist(self):
from web.backend.services.automation import ensure_jobs
calls: list[str] = []
@@ -274,7 +274,12 @@ class TestScheduler(_Base):
self.session.query(AutomationJob).filter(AutomationJob.key == "probe").one()
)
assert job.last_status == "success"
assert job.next_run_at - job.last_run_at == timedelta(seconds=3600)
# 下一轮在一个 tick 之后(作业每个 tick 都跑),与 interval_seconds 无关
from web.backend.services.automation.scheduler import TICK_SECONDS
assert _aware(job.next_run_at) - _aware(job.last_run_at) == timedelta(
seconds=TICK_SECONDS
)
run = self.session.query(AutomationRun).one()
assert (run.scanned, run.valid, run.invalid) == (3, 2, 1)
@@ -334,6 +339,11 @@ class TestAutomationPermissions(_Base):
assert checker(current_user=operator).id == operator.id
def _aware(value):
"""库里读出的是 naive UTC,比较前补时区。"""
return value if value.tzinfo else value.replace(tzinfo=UTC)
_REAL_CHECK = ck_health.check_douyu_cookie
@@ -364,7 +374,7 @@ class TestAutomationApi(_Base):
def test_list_jobs_exposes_scheduler_and_job_state(self):
api = self._api()
payload = api.list_jobs(db=self.session, current=self.admin)
assert payload["scheduler"]["tick_seconds"] == 60
assert payload["scheduler"]["tick_seconds"] == 300
assert payload["scheduler"]["running"] is False
keys = [item["key"] for item in payload["jobs"]]
assert keys == [ck_health.JOB_KEY]