Files
next-tjwater-server/docs/calculation-queue.md
T
jiang 05183aff93
Generic Container CI/CD / test-build-publish (push) Successful in 3m53s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 3m52s
feat: extend operational investigation workflows
2026-10-30 17:30:19 +08:00

9.4 KiB
Raw Blame History

MCP 后台计算队列

API 保留 uvicorn --workers 4。长计算在项目 BizDB 持久化入队,由独立计算进程领取;所有 API 进程读取相同任务状态,不依赖粘性会话。HTTP 返回预算由宿主管理,Server 默认 120 秒、Agent 默认 180 秒。Server 通过工具目录 _meta["io.tjwater/request-timeout"] 统一发布 defaultSeconds 和 maxSeconds,所有工具(包括状态和结果读取)使用同一配置来源。Server 兼容保留 timeout_seconds 参数;Agent 对模型隐藏并拒绝该参数,按目录预算代填。Server 请求上限为 240 秒,Agent 加 60 秒返回余量并封顶 270 秒,原生 MCP 与本地连接的总时限为 300 秒;上游 HTTP 的 5 分钟空闲超时保留。提交、查询状态和读取结果都应及时返回,不等待整个计算。模拟时间仍由 duration 表示。

后台执行时限由管理员配置 TJWATER_MCP_MAX_CALCULATION_TIMEOUT_SECONDS(默认 3600 秒),入队时单独写入任务记录。HTTP 参数不参与作业幂等参数比较,也不会缩短或延长后台执行。任务表的内部 timeout_seconds 列仍保存执行时限。HTTP 超时或断开只结束本次等待,后台作业需通过独立取消工具取消。

前端 / Agent → API × 4 → 项目 BizDB calculation_jobs
                              ↑
                      独立计算 supervisor
                              ↓
                    受管理的计算子进程组

工具契约

原工具名继续使用。以下工具提交后返回 QueryResult,其中 semantics=task,data 包含 job_id、status、tool 和 poll_after_ms;这只证明提交状态,不是分析结果或调查证据:

  • run_burst_location、run_leakage_identification、run_burst_detection
  • run_simulation、run_burst_simulation、run_contaminant_simulation
  • analyze_valve_closure、analyze_flushing、analyze_pressure_regulation
  • optimize_sensor_placement
  • save_simulation_scenario、create_sensor_placement 的 dry_run=false 执行阶段

后两项保留预览、摘要校验和原有审批;提交与执行时均校验权限及预览摘要,已有写入回执继续约束重复请求。analyze_valve_isolation、calculate_demand_allocation 保持同步执行。普通查询和其他写入不进入此队列。

MCP 工具 输入 返回
get_calculation_job job_id 状态、工具、时间和安全错误码
get_calculation_result job_id,可选 data_path/limit/offset 成功后读取原分析结果;未成功返回任务状态
list_calculation_jobs 可选 status/limit/offset 当前授权会话及调查轮次的任务列表
cancel_calculation_job job_id 排队任务可直接取消;运行任务先进入 cancel_requested

状态为 queued/running/cancel_requested/succeeded/failed/cancelled/timed_out/interrupted。返回创建、开始、结束和排队/结果到期时间;result_available 表示结果是否仍可读取。任务取消不等于在线模型写入回滚;发生失败、超时或中断时,需要核查已有写入,不能自动重算整个窗口。

当前使用普通 MCP 业务工具承载队列契约,没有宣称已实现协议级 Tasks 扩展。Server 和 Agent 目录必须配套更新,旧 Agent 不应接收新的队列返回契约。

身份、幂等和结果

任务记录保存提交人的元数据用户 ID、项目、原工具参数和受限授权快照,不保存 Bearer、数据库 DSN 或短期 context_handle。执行和读取结果时重新查询当前用户、成员关系、项目路由、权限及调查运行租约。任务不会因为短期 MCP 句柄失效而丢失;也不会借持久化任务绕过撤销的权限。

普通计算使用宿主从原生工具调用记录解析出的稳定调用 ID;同一次传输重试复用任务,不将新的业务计算自动按参数合并。重写工具同时保留原审批 request_id 幂等语义。不同参数复用同一操作编号会被拒绝。

调查的排队回执不写成分析证据。成功结果读取时,按原计算来源、任务及运行验证,并幂等地保存证据。未完成的计算阻止提交最终报告、insufficient_data 或将运行标记成功;中间进度仍可保存。

Agent 调查执行在原生回合空闲后由宿主短轮询等待,继续维持调查心跳。新作业进入终态即触发模型读取结果,其余作业继续后台运行。状态读取遇到网络、限流或临时服务错误,最多重试 3 次,采用指数退避和随机抖动;权限拒绝、上下文失效立即停止,重试耗尽明确报错,不重新提交或取消计算。普通聊天展示任务编号,可在当前会话后续查询。异常流结束时若无法安全继续处理新结果,会明确失败而不是虚报完成;Server 中的计算结果仍保留。

排队期限与结果保留

每项目最多 100 个未结束作业(queued/running/cancel_requested),跨 API 进程用数据库事务锁串行执行幂等检查与容量准入;同一请求的重试优先返回已有作业。排队最长 3600 秒,超过后标为 timed_out,错误码为 QUEUE_TIMEOUT;开始执行后使用独立的执行期限。提交、查询和 Worker 领取都会核查到期任务,所以 Worker 停止时,查询也能结束过期排队。部署监控仍须覆盖 Worker 进程状态和最老排队任务年龄,队列到期机制不代替服务可用性监控。

结果正文在终态后保留 7 天,到期由队列维护清除,读取返回 RESULT_EXPIRED。任务状态、输入、幂等标识和已归档调查证据不随之删除,也不会因结果过期重新计算。状态列表只读取状态字段,不加载完整计算结果。

并发、取消和恢复

一个 supervisor 默认全项目合计并发 2 项,独立于 API 的 4 个进程。它持有元数据库的会话级 advisory lock,第二个 supervisor 会拒绝启动;当前不是多台计算节点各自扩容的模式。各项目轮转领取,使用 PostgreSQL 短事务 SKIP LOCKED,租约代次约束心跳和结果写回。

每项任务有独立子进程组;取消、执行超时或失去租约时先停止进程组,再写终态。守护管道在 supervisor 被强杀后关闭,负责终止仍在运行的算法进程。API 重启不影响计算 supervisor;计算进程崩溃后,旧任务标为中断,不自动重放。

原生临时文件写入独立任务目录。临时模型数据库创建前登记到该目录的账本,正常结束或取消后只清理明确登记的临时数据库。清理失败返回 CLEANUP_FAILED 并保留恢复目录;启动时检查项目、任务和执行代次,确认旧租约过期后恢复清理。无法验证归属或清理失败时停止领取新工作。

部署

  1. 先对每个目标项目的 BizDB 依次应用 resources/sql/026_calculation_jobs.sql 和 resources/sql/027_calculation_job_lifecycle.sql,包括未来项目使用的相应业务模板。已有 026 的库只需追加 027。不要应用到 TimescaleDB。027 增加时间字段与索引,不清除结果;已有结果从首次迁移起获得完整 7 天保留期。必须先迁移,再加载新 API/Worker 代码。
  2. 配置 CALCULATION_WORKER_PROJECT_IDS 为明确的项目 UUID 列表,配置现有元数据库连接及数据库路由解密密钥。Worker 不需要新 OAuth 密钥;仅部署侧工作进程读取这些设置。
  3. 同步发布 Server 和 Agent,在接入长计算前启动独立 Worker。不要从 API lifespan 启动,不要把调查 Worker 当作计算 Worker。

从 Server 仓库启动:

conda run -n server python scripts/run_calculation_worker.py

systemd 模板:infra/systemd/tjwater-calculation-worker.service。Docker 使用同一 Server 镜像的独立服务:

docker compose -f infra/docker/docker-compose.yml --profile calculations up -d calculation-worker

Compose 使用 ../../.env 注入配置,需确保其中数据库地址在容器内可达。calculation-state volume 保存任务恢复账本,不能当作临时缓存删除。非容器启动可用 CALCULATION_WORKER_STATE_DIR 指定持久目录。

并发默认 2、轮询默认 2 秒、租约 30 秒、心跳 5 秒。算法内部并行度仍消耗额外 CPU/内存;调整并发必须依据实际资源测量。

回滚前暂停接收新的队列计算并等待或明确取消在途任务,核对有副作用任务的写入。保留任务表、结果和恢复目录;旧同步版本不支持继续处理这些作业,不能直接删除记录后重新提交。

验证

conda run -n server python -m pytest tests/unit/test_mcp_calculation_jobs.py tests/unit/test_calculation_worker.py tests/unit/test_calculation_cleanup.py tests/unit/test_mcp_query.py tests/unit/test_mcp_analysis.py tests/unit/test_operational_tasks.py -q

真实 PostgreSQL 并发测试为 tests/integration/test_calculation_queue_storage.py。它要求 CALCULATION_QUEUE_TEST_DSN 指向名称以 tjwater_calc_test_ 开头的专用可丢弃数据库,不能指向业务库。覆盖四连接重复提交、参数冲突、并发领取、项目和用户隔离、取消竞争、失效代次拒绝和过期租约恢复。

这些验证不代替真实项目算法运行、浏览器断线重连或部署验收。生产接入还应观察超过 5 分钟的真实计算、恢复后结果读取和原生临时资源清理。