Skip to content

feat(worker): 调度配置提取到 DB + 网页端实时热重载 - #62

Open
Color2333 wants to merge 1 commit into
mainfrom
feat/worker-schedule-config
Open

feat(worker): 调度配置提取到 DB + 网页端实时热重载#62
Color2333 wants to merge 1 commit into
mainfrom
feat/worker-schedule-config

Conversation

@Color2333

Copy link
Copy Markdown
Owner

背景

用户反馈:网页端不能及时控制 worker。worker 启动时读一次 DB/env 配置注册 APScheduler job,之后固定。网页端改完 cron 必须重启 worker 容器才生效。其中 topic_dispatch(每整点)/cs_feed_dispatch(:05) 是硬编码apps/worker/main.pyweekly_graph 读 env WEEKLY_CRON@lru_cache 冻结),连 DB 都没写。daily_brief 虽在 DB 里但同样只启动时读一次。

另:今日 04:00 UTC 每日简报因磁盘满 → postgres 进 recovery mode 而失败(已手动补发,根因另案处理)。

方案:DB 可配置 + 轮询热重载(30s 延迟)

沿用现有三个模式,不引入新基础设施(不加 worker HTTP 端口、不加消息队列):

  1. DailyReportConfig 单例表 → 新增 WorkerScheduleConfig 单例表
  2. 共享 DB 通道(backend 写、worker 读)
  3. daemon 线程(IdleProcessor 模式)→ 新增 _config_poll_loop

架构

网页端 WorkerSettingsTab
  → PUT /settings/worker-schedule (写 WorkerScheduleConfig,bump updated_at)
  → worker 轮询线程每 30s 查 updated_at
  → 检测到变化 → reload_worker_schedule() 重排 4 个 job + start/stop idle_processor
  → 写 last_applied_at 回 WorkerScheduleConfig(供前端显示已生效)

改动清单(13 文件,3 新建)

后端

  • packages/storage/models.py — 新增 WorkerScheduleConfig 单例表(5 配置字段 + last_applied_at)
  • infra/migrations/versions/a7b8c9d0e1f2_add_worker_schedule_config.py新建 alembic 迁移(down_revision = f6a7b8c9d0e1)
  • packages/storage/repositories/worker_schedule.py新建 WorkerScheduleConfigRepository(get_config / update_config / update_last_applied_at)
  • packages/storage/repositories/__init__.py — 导出 WorkerScheduleConfigRepository
  • apps/api/routers/settings.py — 新增 WorkerScheduleConfigUpdate pydantic + GET/PUT /settings/worker-schedule 端点

Worker 调度重构(核心)

  • apps/worker/main.py
    • schedulerrun_worker() 局部变量提升到模块级 + get_scheduler() 访问器(镜像 get_idle_processor
    • _read_schedule_config() — 从 DB 读所有 cron + idle 开关,DB 不可用回退默认值
    • _register_all_jobs(scheduler, cfg) — 统一注册 4 个 job(replace_existing=True 等价 reschedule)
    • reload_worker_schedule() — 重读 DB → reschedule + sync idle → 写 last_applied_at
    • _config_poll_loop(stop) — daemon 线程每 30s 查 WorkerScheduleConfig.updated_at + DailyReportConfig.updated_at,变化即调 reload
    • _sync_idle_processor(enabled) — 按配置开关 start/stop,避免无谓循环
    • run_worker() 重写:模块级 scheduler → 初始注册 → 轮询线程 → start

附带修复(同一 PR)

  • packages/ai/brief_service.py:600 — 删除 if not recipient: 块里局部 from packages.storage.db import session_scope,用模块级 import(第 18 行已有)。修 recipient 已传入时 session_scope 未绑定导致 generated_content 写入失败的 bug(今日补发简报时触发)
  • scripts/auto_deploy.shdocker compose up -d 后加 docker image prune -f + builder prune -f,防磁盘满再次搞崩 postgres

前端

  • frontend/src/types/index.ts — 新增 WorkerScheduleConfig interface
  • frontend/src/services/api.ts — 新增 workerScheduleApi(getConfig / updateConfig)+ import/re-export
  • frontend/src/pages/Settings.tsx — 注册第 5 个 tab worker(Clock 图标)
  • frontend/src/components/settings/WorkerSettingsTab.tsx新建 tab 组件:
    • 3 个 cron 文本输入(onBlur 保存,带默认值 hint,镜像 EmailSettingsTab cron_expression 模式)
    • 1 个 idle_processor_enabled 开关
    • 同步状态指示器(last_applied_at vs updated_at 判定已生效/等待同步)+ 刷新按钮

测试

  • tests/test_repositories.py — 新增 TestWorkerScheduleConfigRepository(4 个测试:单例创建 / 单例复用 / 部分更新 / last_applied_at 写回)

验证

  • ✅ 后端 pytest:73 passed + 2 skipped(新增 4 个测试)
  • ✅ 前端 tsc --noEmit:无错误
  • ✅ 前端 vite build:构建成功
  • ruff check + ruff format:通过(pre-commit 全绿)

不在本次范围

用户选了"只先把调度参数提取出来",以下暂不做:

  • worker_retry_max / paper_concurrency / cost_guard / skim_score_threshold / user_timezone 等 env-only 运行参数
  • idle_processor 的 batch_size / check_interval(只加 on/off 开关)
  • HTTP push 即时重载(30s 轮询已满足"及时",后续可升级)

部署步骤

  1. 合 PR 到 main
  2. auto_deploy.sh 自动拉取重建(含 docker image prune)
  3. 需手动在 backend 容器跑 docker compose exec backend alembic upgrade head 建新表(auto_deploy.sh 不自动跑 alembic)
  4. 重启 worker 容器使其读新表 + 启动轮询线程

验证场景

  • 网页端改 topic_dispatch_cron*/10 * * * * → 30s 内 worker 日志出现 🔄 worker 调度已热重载 + last_applied_at 更新
  • 改回 0 * * * * → 同步生效
  • idle_processor_enabled → 闲时线程停止;开 → 重启
  • 同步状态指示器:绿色"已生效" / 黄色"等待 worker 同步中"

将 worker 的 4 个 cron 调度(topic_dispatch / cs_feed_dispatch /
daily_brief / weekly_graph)从硬编码/env 提取到 DB 单例表 WorkerScheduleConfig,
网页端修改后 worker 轮询线程 30s 内检测 updated_at 变化并热重载 APScheduler job,
无需重启容器。

核心改动:
- 新表 WorkerScheduleConfig(model + alembic 迁移 + repository)
- 后端 GET/PUT /settings/worker-schedule 端点
- worker scheduler 提升到模块级 + get_scheduler() 访问器
- _read_schedule_config / _register_all_jobs / reload_worker_schedule /
  _config_poll_loop(30s 轮询 + daemon 线程,镜像 IdleProcessor 模式)
- _sync_idle_processor 按配置开关 start/stop 闲时处理器
- worker 写回 last_applied_at 供前端显示已生效状态
- 前端新增 Worker / 调度 tab(3 个 cron 输入 + idle 开关 + 同步状态指示器)

附带修复:
- brief_service.py publish(): 删除 if not recipient 块里局部 import 的
  session_scope,用模块级 import(第 18 行已有)。修复 recipient 已传入时
  session_scope 未绑定导致 generated_content 写入失败的 bug
- auto_deploy.sh: docker compose up -d 后加 docker image prune -f +
  builder prune -f,防磁盘满再次搞崩 postgres

测试:73 passed + 2 skipped(新增 4 个 WorkerScheduleConfigRepository 测试);
前端 tsc + vite build 通过。

部署:合 PR 后需在 backend 容器手动 alembic upgrade head 建新表
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant