Files
ttstd-infinite-canvas/docs/content/docs/backend/task-runtime-stability.mdx

64 lines
7.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 任务缓存与队列稳定性
description: 热点回源保护、并发租约、故障边界和上线验收
---
# 任务缓存与队列稳定性
## 范围与事实源
当前任务仍由数据库持久化、API 进程内 worker 领取和执行,Redis 负责跨实例并发协调。本轮是热点读取和租约的止血修复,不是独立任务系统迁移,也不能替代线上事故取证。
- 数据库继续作为任务、额度、账务和权限的事实源。没有把任务或账本迁入 Redis,没有修改表结构或新增迁移。
- 新增缓存是每个后端实例的有界内存读缓存,不是通用 Redis 业务缓存。实例增加时,缓存容量与不同实例的回源量会随之增加。
- handler 负责 SSE 协议和登录上下文;service 负责读缓存、并发协调和执行生命周期;repository 负责有条件的租约写入与事务。
## 热点读取保护
| 读路径 | 缓存 / 回源限制 | 故障行为 |
| --- | --- | --- |
| worker / 渠道全局并发策略 | 2 秒 TTL,1 条目、1 个同时回源,正文预算 1 KiB | 读取失败不使用默认高并发;暂停投递或返回明确错误 |
| 文本任务 SSE 回放 | 750 毫秒 TTL,256 条目、8 个不同 key 同时回源,正文预算 8 MiB | 同 key 合并回源;超过容量返回繁忙,不创建无界的不同 key 等待队列 |
| Redis 路由版本检查 | 250 毫秒 TTL,1 个同时回源 | 失败保留原有本地版本回退;不保证 Redis 故障时跨实例实时失效 |
| 模型路由目录 | 沿用 30 秒 TTL,刷新串行,失败冷却 2 秒,数据库回源期限 5 秒 | 首次失败明确返回错误;已有快照只能在同版本、最多 5 分钟的原有过期窗口内降级 |
读缓存按 LRU 淘汰,正文预算是估算值,不等于进程总内存上限;回源中的数据、当前请求和等待中的连接不包含在正文预算内。超大结果仅返回给当前读取者,不保留为缓存。普通失败冷却 1 秒,不返回过期成功值;请求取消和超时不缓存。回源异常退出释放容量和等待者,原始 panic 仍交给原有错误处理。
并发策略保存或重置成功后立即清除本实例缓存,其他实例在 2 秒 TTL 后重新读取。计费、配额和其他调用 `RuntimePolicy()` 的强校验路径仍直接读取数据库。
SSE 缓存 key 包含用户、任务和 `after` 游标,回源仍检查归属;返回副本避免调用者改写其他连接的增量。`GET /api/tasks/:id/text-events` 的事件格式、递增序号、`Last-Event-ID` / `?after=` 和 15 秒心跳不变。终态最多受短缓存窗口延迟;已经建立的流遇到回源失败仍发送 error 并关闭。`/text-deltas` 普通查询没有纳入此缓存。
这是合并相同游标的轮询读取,不是事件驱动推送;不同游标和不同实例仍可能分别查询数据库。连接总量、客户端重连退避及任务删除后的缓存失效仍需后续治理。
## worker 与渠道槽位
- worker 全局槽位由固定 45 分钟 TTL 改为 1 分钟租约,每 15 秒与数据库任务租约一起续期;数据库任务租约仍为 45 秒。
- 每次领取生成独立 owner,不再让一个进程的所有执行共享 owner。领取数据库操作有 5 秒期限,启动策略读取有 3 秒期限。
- Redis 协调客户端关闭透明命令重试;槽位领取、续期与释放使用最多 2 秒期限。Redis 获取槽位失败时不降级成本机无限执行。
- 槽位丢失、过期或续期失败会取消该执行的 context。已经失效的槽位不能通过续期复活;释放幂等且只移除本次 token。释放失败记录日志,由租约过期回收。
- 共享 Redis 槽位键按集合内最晚的租约保留过期余量,避免短任务或新版本的短租约把旧长任务槽位提前删除。旧版本脚本本身不会获得这一保护,仍建议排空后发布。
- 渠道满载时从 200 毫秒开始指数退避,上限 2 秒,每轮在半窗口至完整窗口间随机等待,避免固定每秒五次同步争抢。空闲时仍先立即尝试领取,取消能立即结束等待。
- worker 主进度、终态、结果事务及延迟回查要求匹配 owner 且租约仍有效。过期执行者不能覆盖已被重新领取的任务;条件更新没有命中时,终态协调器不得继续退款或完成会话。
**边界**:租约不是外部供应商的幂等保障。上游请求不一定响应取消;提供商状态回调、部分进度和关联资源写入尚未全部传播 owner,不能宣称所有副作用都已 fencing。渠道等待仍发生在领取 worker 槽位之后,也没有新增全局积压上限或公平调度。其他用途的槽位未统一改为续期模型。
## 上线与回滚
1. 先保存故障时段的 Redis 内存、淘汰数量、连接数、延迟和重启记录,以及数据库慢查询、连接池等待、任务 queued/running 数量与最老等待时长。必要日志必须脱敏,不能导出密钥和完整用户正文。
2. 先在测试环境验证 Redis 与 PostgreSQL 双实例:长任务超过原租期仍只占一个槽位;停止 worker 后槽位恢复;Redis 断连时暂停领取;旧 owner 写入失败;SSE 断线续传无重复或漏终态。
3. 采用维护窗口排空旧执行者,再发布新版本。Compose 当前默认优雅退出时间由 `CANVAS_SHUTDOWN_TIMEOUT` 控制,默认 10 分钟,不足以保证最长 60 分钟渲染自然完成;发布前核对活跃任务,不能把进程退出视为任务排空成功。
4. 保留 `canvas:slots:*` 等现有 Redis key。旧进程的 45 分钟分数可能暂时占槽,应先确认旧执行者已结束并观察租约自然回收,**禁止用 FLUSHDB、批量删槽位或批量重投收费任务来解堵**。
5. 回滚不需要数据迁移,但旧版本仍有原来的固定租期和缺失写入保护。避免新旧版本混跑,不应把回滚视为稳定性问题已经解除。
本次不部署、不清理线上缓存、不修改线上任务或触发真实收费请求。仅凭代码修复和本地测试,不能认定某次线上事故根因已确认或故障已消除。
`TestRuntimeCoordinatorRedisLeasePreservesLongestTTL` 在 PATH 中存在 `redis-server` 时自动启动临时 Unix socket Redis,检查长短租约混用、容量、过期拒绝续期及幂等释放;它不读取 `REDIS_URL`,不连接部署环境。缺少二进制时明确跳过,不视为 Redis 集成已通过。
## 下一阶段
- 可复用业务数据的 Redis 缓存与主动失效,任务状态快照和事件通知,缓存故障时的总量保护。
- 独立 worker 进程、可靠投递 / outbox、渠道前置排队、积压上限和公平性;按任务类型定义幂等、重试、延迟和补偿。
- 将执行 owner 传播到所有提供商回调和副作用;明确上游已提交但本地未落库时的查询恢复,不盲目重复生成。
- 后台手动投递、暂停、恢复和审计;完整的请求 / 任务 / 回调关联日志与脱敏正文留存。
- 事件驱动 SSE、连接准入和重连退避,替换按连接的数据库轮询。