任务缓存与队列稳定性
热点回源保护、并发租约、故障边界和上线验收
详情读取边界
任务中心打开详情只读取任务和日志,不在该路径调用画布结果同步。后台同步遇到短剧领域项目任务(domainProjectId 等于任务 projectId)或没有生成节点关联时跳过画布写回,避免把短剧项目 ID 当作画布 ID 查询。
章节提取采用独立提示词操作 chapter_assets_extract,受保护输出契约为 chapter-assets/v1,要求 characters、scenes、props 三个数组。场景和道具要求非空的 name、description、prompt;角色继续执行原角色契约校验。写入候选资产时场景映射为 environment,继续使用现有服务的项目归属、去重与写入校验,不新增数据库字段。
范围与事实源
当前任务仍由数据库持久化、进程内 worker 领取和执行。本轮是热点读取和租约的止血修复,不是独立任务系统迁移,也不能替代线上事故取证。
- 数据库继续作为任务、额度、账务和权限的事实源。没有修改表结构或新增迁移。
- 新增缓存是每个后端实例的有界内存读缓存,不是通用业务缓存。实例增加时,缓存容量与不同实例的回源量会随之增加。
- handler 负责 SSE 协议和本地工作区上下文;service 负责读缓存、并发协调和执行生命周期;repository 负责有条件的租约写入与事务。
画布任务摘要与刷新恢复
任务列表的摘要通过 clientContext.nodeId 返回画布节点关联,不为恢复任务返回完整 inputJson、渠道配置或密钥。前端优先读取摘要中的节点 ID,任务详情仍可从输入元数据取得节点 ID;原有创作会话、章节和镜头工作流上下文继续保留。
已持久化任务 ID 的节点直接按 ID 恢复。缺少任务 ID 时,画布查询当前项目最近 100 条任务摘要并按节点 ID 匹配;这不是全量历史扫描,超过该范围的旧任务仍可能无法自动发现。任务读取继续遵循当前用户与项目的访问边界,不新增数据库字段或迁移。
多张图片的画布绑定
固定一次多张的图片任务,在创建前把各输出的画布节点 ID 按顺序写入 metadata.outputNodeIds。taskbinding.Bind 根据 outputIndex 核对原先指定的目标,再沿用任务、画布、资源和素材归属及就绪状态校验;绑定请求不能临时换一个目标。没有该数组的旧任务继续使用单一 metadata.nodeId。
画布候选共享一个任务与提交操作 ID,各自保存输出序号。停止等待或提交结果不确定时保留这些信息;来自文本节点的候选同样阻止再次付费创建。缺少某个输出时立即提示并保留已取得的结果。
历史素材读取使用操作开始时的账号及会话序号,在请求前和返回后校验;账号切换后不把旧响应写入当前素材库。
从历史记录插入固定多张任务时,优先复用该任务已经交付的本机素材和资源,逐个序号新建节点,不重发生成请求,也不修改旧任务的绑定目标。保存完成后把全部新节点同步到当前画布,保留同时发生的其他编辑。
热点读取保护
| 读路径 | 缓存 / 回源限制 | 故障行为 |
|---|---|---|
| worker / 渠道全局并发策略 | 2 秒 TTL,1 条目、1 个同时回源,正文预算 1 KiB | 读取失败不使用默认高并发;暂停投递或返回明确错误 |
| 文本任务 SSE 回放 | 750 毫秒 TTL,256 条目、8 个不同 key 同时回源,正文预算 8 MiB | 同 key 合并回源;超过容量返回繁忙,不创建无界的不同 key 等待队列 |
| 模型路由目录 | 沿用 30 秒 TTL,刷新串行,失败冷却 2 秒,数据库回源期限 5 秒 | 首次失败明确返回错误;已有快照只能在同版本、最多 5 分钟的原有过期窗口内降级 |
读缓存按 LRU 淘汰,正文预算是估算值,不等于进程总内存上限;回源中的数据、当前请求和等待中的连接不包含在正文预算内。超大结果仅返回给当前读取者,不保留为缓存。普通失败冷却 1 秒,不返回过期成功值;请求取消和超时不缓存。回源异常退出释放容量和等待者,原始 panic 仍交给原有错误处理。
SSE 缓存 key 包含用户、任务和 after 游标,回源仍检查归属;返回副本避免调用者改写其他连接的增量。GET /api/tasks/:id/text-events 的事件格式、递增序号、Last-Event-ID / ?after= 和 15 秒心跳不变。终态最多受短缓存窗口延迟;已经建立的流遇到回源失败仍发送 error 并关闭。/text-deltas 普通查询没有纳入此缓存。
这是合并相同游标的轮询读取,不是事件驱动推送;不同游标和不同实例仍可能分别查询数据库。连接总量、客户端重连退避及任务删除后的缓存失效仍需后续治理。
worker 与渠道槽位
- worker 槽位是 1 分钟租约(应用层以
workerSlotLeaseDuration注入)。执行期间每 15 秒同时续期槽位与数据库任务租约;数据库任务租约默认 45 秒。 - 每次领取以本次槽位 token 作为执行 owner,不同领取之间不共享 owner。领取数据库操作有 5 秒期限,执行超时策略读取有 3 秒期限。
- 续期失败、槽位过期或任务租约失效时,执行 context 被取消。已经失效的槽位不能通过续期复活;释放幂等,且只移除本次 token。
- 续期只在 owner 匹配且租约未过期时成功。终态结果事务按 owner 条件写入,条件未命中时报错,不覆盖已被重新领取的任务。
- 渠道满载时从 200 毫秒开始指数退避,上限 2 秒,每轮在半窗口至完整窗口之间随机等待,避免固定节奏的同步争抢。空闲时仍先立即尝试领取,取消 context 能立即结束等待。
边界:租约不是外部供应商的幂等保障,上游请求不一定响应取消。渠道等待发生在领取 worker 槽位之后,等待循环没有积压上限或公平调度。