perf(storage): 极简化任务历史并增加保留治理

停止持久化 provider 原始响应、兼容响应快照、attempt/event/outbox 重复 JSON,并由标准任务结果动态生成 Kling/Keling/Volces 兼容响应。

增加事件去重与预算、极简 callback 投递、7/30 天分批清理、安全删除条件、并发迁移索引及可实际恢复的任务域排除备份。历史清理默认关闭,待兼容协议和异步恢复在线验证后单独启用。

验证:Go 全量测试与 go vet、PostgreSQL 18 集成与实际备份恢复、迁移安全测试、bash -n、ShellCheck、Compose 配置和人工发布脚本测试均通过。
This commit is contained in:
2026-07-24 18:23:22 +08:00
parent 09375bfae7
commit 810dcfeee6
46 changed files with 2246 additions and 458 deletions
+43 -40
View File
@@ -454,13 +454,13 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
- `user_group_id` / `user_group_key`
- `user_group_policy_snapshot`:任务创建时解析出的用户组策略快照,用于审计和重试稳定性。
- `model`
- `request`
- `request`:唯一完整请求;`normalized_request` 为兼容列,新数据固定为 `{}`
- `status``queued``running``succeeded``failed``cancelled`
- `queue_key`:限流队列 key,例如 `${platformKey}-${model}``${provider}-${methodName}`
- `priority`
- `idempotency_key`
- `remote_task_id`
- `remote_task_payload`
- `remote_task_payload`:仅保存 provider 显式白名单内、跨进程恢复不可重算的 checkpoint;禁止保存提交响应、最终响应和完整请求副本。
- `run_mode``production``simulation`
- `simulation_profile`
- `simulation_seed`
@@ -472,9 +472,11 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
- `billings`
- `error`
Provider 原始响应只在内存中参与标准结果转换,不进入 `result.raw`。兼容协议响应由任务状态、标准 `result`、错误、时间和 `remote_task_id` 动态生成;兼容响应 body、Header、HTTP status 和 public ID 快照均不再写入。终态任务默认保留 30 天。
### 7.4 `gateway_task_attempts`
保存每一次客户端尝试,支持“上一个客户端失败,下一个客户端重试”的完整审计
保存每一次客户端尝试的标量分析信息,支持还原重试顺序和错误
- `task_id`
- `attempt_no`
@@ -487,25 +489,27 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
- `error_message`
- `remote_task_id`
- `simulated`
- `request_snapshot`
- `response_snapshot`
- `request_id` / `status_code`
- `upstream_submission_status`
- `response_started_at` / `response_finished_at` / `response_duration_ms`
- `started_at`
- `finished_at`
`request_snapshot``response_snapshot``usage``metrics``pricing_snapshot``request_fingerprint` 为兼容字段,新数据固定为空。错误信息最多 2KB,attempt 默认保留 7 天。
### 7.5 `gateway_task_events`
保存任务执行过程中的事件,用于控制台 SSE / WebSocket 重放、进度回调 outbox 投递与服务重启后的进度恢复
保存真实状态变化、真实失败 attempt、计费结果和真实策略动作,用于控制台 SSE / WebSocket 重放与 callback outbox 投递
- `task_id`
- `seq`:单任务内递增序号
- `event_type``queue_status``node_status``progress``partial_result``completed``failed`
- `event_type`代码白名单内的 accepted、running、终态、billing、attempt failed 和 policy 事件。
- `status`
- `progress`
- `message`
- `payload`
- `platform_id`
- `simulated`
- `created_at`
客户端断线重连时可通过 `Last-Event-ID` `afterSeq` 回放事件
`phase``progress``message` `payload` 为兼容字段,新数据固定为空。轮询和 synthetic progress 不写事件;同一 task 行锁内的连续重复状态被跳过且不递增 `seq`。普通成功任务最多 4 条事件,每个任务最多 16 条非终态事件,终态和计费事件不受此限制。事件默认保留 7 天
### 7.5.1 `gateway_task_callback_outbox`
@@ -514,13 +518,15 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
- `task_id`
- `event_id` / `seq`:与 `gateway_task_events` 对应,用于幂等与顺序控制。
- `callback_url`:当前使用的回调地址快照,避免配置变化影响已排队事件。
- `payload`回调给 server-main 的标准事件载荷
- `status``pending``delivering``delivered``failed``skipped`
- `payload`兼容列,新数据固定为 `{}`worker 根据 event 构造极简回调
- `status``pending``processing``delivered``failed`
- `attempts`
- `next_attempt_at`
- `last_error`
- `delivered_at`
请求体固定只包含 `taskId``seq``eventType``status``createdAt`,幂等键为 `<taskId>:<seq>`。2xx 成功;网络错误、408、429 和 5xx 重试,其他 4xx 直接失败,最多投递 10 次。`delivered` 保留 24 小时,`failed` 保留 7 天;任一 callback 仍处于保留期时不得删除对应 task。
### 7.6 `runtime_client_states`
保存平台客户端的运行时状态:
@@ -1044,8 +1050,8 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
| `retryable` | `boolean` | not null, default `false` | 是否可重试 |
| `simulated` | `boolean` | not null, default `false` | 是否模拟 |
| `remote_task_id` | `text` | nullable | 供应商任务 ID |
| `request_snapshot` | `jsonb` | not null, default `{}` | 本次请求快照 |
| `response_snapshot` | `jsonb` | nullable | 本次响应快照 |
| `request_snapshot` | `jsonb` | not null, default `{}` | 兼容列,新数据固定为 `{}` |
| `response_snapshot` | `jsonb` | nullable | 兼容列,新数据固定为 `{}` |
| `error_code` | `text` | nullable | 错误码 |
| `error_message` | `text` | nullable | 错误信息 |
| `started_at` | `timestamptz` | not null | 开始时间 |
@@ -1064,12 +1070,13 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
| `id` | `uuid` | PK | 事件 ID |
| `task_id` | `uuid` | FK | 所属任务 |
| `seq` | `bigint` | not null | 单任务递增序号 |
| `event_type` | `text` | not null | `queue_status``progress``partial_result``completed``failed` |
| `event_type` | `text` | not null | 代码白名单内的真实状态、失败、计费和策略事件 |
| `status` | `text` | nullable | 任务状态 |
| `phase` | `text` | nullable | `routing``queued``submit``polling``upload` |
| `progress` | `numeric` | nullable | 0 到 1 |
| `message` | `text` | nullable | 展示消息 |
| `payload` | `jsonb` | not null, default `{}` | 事件载荷 |
| `platform_id` | `uuid` | nullable | 策略事件平台 ID |
| `phase` | `text` | nullable | 兼容列,新数据为空 |
| `progress` | `numeric` | nullable | 兼容列,新数据为 0 |
| `message` | `text` | nullable | 兼容列,新数据为空 |
| `payload` | `jsonb` | not null, default `{}` | 兼容列,新数据固定为 `{}` |
| `simulated` | `boolean` | not null, default `false` | 是否模拟 |
| `created_at` | `timestamptz` | not null | 创建时间 |
@@ -1087,8 +1094,8 @@ Gateway Web Console 默认使用 HttpOnly OIDC Cookie 会话解决标签页隔
| `event_id` | `uuid` | FK nullable | 对应 `gateway_task_events.id` |
| `seq` | `bigint` | not null | 单任务事件序号 |
| `callback_url` | `text` | not null | 投递地址快照,来自 `TASK_PROGRESS_CALLBACK_URL` |
| `payload` | `jsonb` | not null | 回调载荷 |
| `status` | `text` | not null | `pending``delivering``delivered``failed``skipped` |
| `payload` | `jsonb` | not null | 兼容列,新数据固定为 `{}` |
| `status` | `text` | not null | `pending``processing``delivered``failed` |
| `attempts` | `int` | not null, default `0` | 已投递次数 |
| `next_attempt_at` | `timestamptz` | not null | 下次投递时间 |
| `last_error` | `text` | nullable | 最近错误 |
@@ -1615,26 +1622,15 @@ POST ${TASK_PROGRESS_CALLBACK_URL}
Authorization: Bearer ${SERVER_MAIN_INTERNAL_TOKEN}
Content-Type: application/json
Idempotency-Key: ${taskId}:${seq}
X-EasyAI-Event-Type: task.progress
X-EasyAI-Event-Type: task.completed
```
```json
{
"eventId": "uuid",
"taskId": "uuid",
"externalTaskId": "server-main-task-id",
"userId": "user-id",
"tenantId": "tenant-id",
"apiKeyId": "optional",
"kind": "images.generations",
"model": "gpt-image-1",
"seq": 12,
"event": "progress",
"status": "running",
"phase": "polling",
"progress": 0.42,
"message": "Generating video frames",
"payload": {},
"seq": 3,
"eventType": "task.completed",
"status": "succeeded",
"createdAt": "2026-05-09T12:00:00Z"
}
```
@@ -1642,7 +1638,7 @@ X-EasyAI-Event-Type: task.progress
`server-main` 收到后不重新执行任务,只做三件事:
1.`Idempotency-Key``taskId + seq` 幂等落库/去重。
2. 根据 `externalTaskId``taskId``userId``tenantId` 找到原业务会话或任务频道
2. 根据 `taskId` 找到原业务会话或任务频道;需要完整结果时调用 task detail
3. 通过原 WebSocket 网关推送给业务前端,保持前端订阅协议不大改。
### 12.3 进度持久化要求
@@ -1822,15 +1818,22 @@ TASK_PROGRESS_CALLBACK_URL=http://easyai-server-main:3000/internal/platform/task
TASK_PROGRESS_CALLBACK_ENABLED=true
TASK_PROGRESS_CALLBACK_TIMEOUT_MS=5000
TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS=10
AI_GATEWAY_TASK_CLEANUP_ENABLED=false
AI_GATEWAY_TASK_RETENTION_DAYS=30
AI_GATEWAY_TASK_ANALYSIS_RETENTION_DAYS=7
AI_GATEWAY_TASK_CLEANUP_INTERVAL_SECONDS=300
AI_GATEWAY_TASK_CLEANUP_BATCH_SIZE=1000
```
运行时要求:
- `BaseClient` / runtime 每产生一个进度事件,先写 `gateway_task_events`,再写 `gateway_task_callback_outbox`
- 首次上线先保持清理关闭;兼容协议和异步恢复验收通过后,显式改为 `AI_GATEWAY_TASK_CLEANUP_ENABLED=true`,再启动历史压缩和 7/30 天删除
- runtime 仅在任务真实状态变化时写 `gateway_task_events`;事件 payload 固定为空,轮询不会生成事件。
- callback worker 以 `0083_task_history_minimal_storage` 的应用时间为边界,只投递新 outbox;旧 pending/processing callback 在启用治理后分批标记为 `failed`,不会向业务端补发形成历史回调风暴。
- callback worker 使用 `SERVER_MAIN_INTERNAL_TOKEN` 调用 `TASK_PROGRESS_CALLBACK_URL`
- `server-main` 收到事件后进入主服务内部推送流程,再由原 WebSocket 网关推送给业务前端。
- Gateway 控制台仍可通过 SSE 读 `gateway_task_events`,用于运维诊断,不影响业务前端推送通道。
- 如果 `server-main` 短暂不可用,Gateway 按 outbox 重试;超过最大次数后标记 `failed`,控制台可手动 replay
- 如果 `server-main` 短暂不可用,Gateway 按 outbox 重试;超过最大次数后标记 `failed` 并保留 7 天供诊断
### 14.3 中期:前端直接打 Gateway