feat: workerize external generation
This commit is contained in:
@@ -48,6 +48,14 @@
|
||||
- 验证方式:关闭任一创作入口后,新建创作请求返回 `creation_entry_disabled`;公开作品列表 / 详情 / 启动 / 运行态动作不返回该错误;进入平台首页不弹“平台首页:creation_entry_disabled”;关闭态入口卡显示锁定状态且不显示 `10-20泥点数`。
|
||||
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
## 2026-06-03 外部内容生成改为持久队列加 worker 角色
|
||||
|
||||
- 背景:拼图首图、图集、音频等外部生成链路长期占用 `api-server` HTTP handler,导致扩容只能放大 API 进程,且 HTTP 超时和外部 provider 波动会直接影响创作入口。
|
||||
- 决策:外部生成任务统一进入 SpacetimeDB `external_generation_job` 持久队列,由 `api-server` 的 `external-generation-worker` 进程角色 claim lease 后执行;HTTP 角色只做鉴权、表单/状态初始化、入队和返回 `queued/running/completed/failed` 操作状态。生产通过 systemd worker 模板增加实例数或提高 `GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY` 动态扩缩容,`GENARRATIVE_PROCESS_ROLE=all` 仅用于本地 smoke。拼图 `compile_puzzle_draft`、结果页 `generate_puzzle_images` 与 `generate_puzzle_ui_background` 已接入 worker;业务写回必须在 SpacetimeDB transaction 内校验 `external_generation_job` 的 `job_id + worker_id + lease_token`、job kind、owner 和 source entity,其中首图 worker 的前置 `compile_puzzle_agent_draft` 也必须带 guard。worker 核心业务写回失败不能返回内存快照并把 job 标成 completed;失败态业务写回成功后才能把 job 标成 failed,失败态未写回则保留租约等待后续重领。拼图业务失败不自动重试,只保留 lease 过期后的崩溃重领,避免钱包扣退费幂等漂移。生产发布会启用默认 `genarrative-external-generation-worker@1.service` 并等待 worker active,worker 停机时停止 claim 新任务并 drain 当前任务。
|
||||
- 影响范围:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/spacetime-client/src/external_generation.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`deploy/systemd/genarrative-external-generation-worker@.service`、`scripts/deploy/production-api-deploy.sh`、`scripts/jenkins-server-provision.sh`、拼图 `compile_puzzle_draft`、拼图 `generate_puzzle_images`、拼图 `generate_puzzle_ui_background`、生产 env 模板和运维文档。
|
||||
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:server-rs-ddd`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,并用 `GENARRATIVE_PROCESS_ROLE=all npm run dev` smoke 至少一次 queued -> worker 完成链路。
|
||||
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 2026-06-03 最近创作只复用创作模板入口
|
||||
|
||||
- 背景:底部加号创作入口的“最近创作”最初由真实作品架摘要驱动,但页面曾按作品标题、摘要和生成状态渲染独立最近创作卡,和其它模板页签的卡片样式及点击语义不一致。
|
||||
@@ -1247,3 +1255,11 @@
|
||||
- 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`server-rs/crates/spacetime-client/src/auth.rs`、对应生成 bindings。
|
||||
- 验证方式:`cargo check -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth password --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run check:spacetime-schema`、`npm run check:encoding`、`cargo test -p api-server spacetime_unavailable_router_returns_service_unavailable_for_requests --manifest-path server-rs/Cargo.toml -- --nocapture`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-06-03 外部生成 worker lease 使用 SpacetimeDB 时间和 token 栅栏
|
||||
|
||||
- 背景:外部生成 worker 支持多进程动态缩扩容后,长任务超过单次 lease、worker 本机时钟漂移或复用 worker id 都可能导致同一任务被重复领取并被过期执行者回写。
|
||||
- 决策:`external_generation_job` 新增末尾字段 `lease_token`;`claim` 使用 SpacetimeDB `ctx.timestamp` 计算 lease,生成本次 claim token;worker 执行期间调用 `renew_external_generation_job_lease_and_return` 续租;`complete/fail` 必须带 `worker_id + lease_token` 才能回写。拼图 `compile_puzzle_draft` 的 dedupe key 包含本次 `extgen-` job id,避免同一 session 的失败或完成 job 吞掉后续重新生成。拼图首图前置 `compile_puzzle_agent_draft`、图片保存、UI 背景与失败态业务写回同样必须携带 lease guard,并在 `compile_puzzle_agent_draft`、`save_puzzle_generated_images`、`save_puzzle_ui_background`、`mark_puzzle_draft_generation_failed`、`mark_puzzle_level_generation_failed` 的 SpacetimeDB 事务内校验。
|
||||
- 影响范围:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/spacetime-module/src/puzzle.rs`、`server-rs/crates/module-puzzle/src/commands.rs`、`server-rs/crates/spacetime-client/src/external_generation.rs`、`server-rs/crates/spacetime-client/src/puzzle.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`server-rs/crates/api-server/src/puzzle/generation.rs`。
|
||||
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml`、`GENARRATIVE_PROCESS_ROLE=all npm run dev` 后检查 `/healthz`。
|
||||
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
@@ -15,6 +15,38 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 外部生成 worker 业务失败重试会撞上钱包扣退费幂等
|
||||
|
||||
- 现象:同一个外部生成 job 如果第一次业务失败后退款,再用同一个业务资源 ID 自动重试并成功,钱包 `consume` ledger 可能因为同 ID 已存在而跳过,最终出现“失败已退、成功不再扣”的余额漂移。
|
||||
- 原因:资产操作扣费和退款都用稳定 ledger id 做幂等;这能保护 lease 过期后的崩溃重领不重复扣费,但不适合“已明确失败且已退款”的自动业务重试。
|
||||
- 处理:拼图 `puzzle_compile_draft` 首期设置 `max_attempts=1`,业务失败直接 failed,只保留 running lease 过期后的崩溃重领。后续若要恢复自动 retry,必须先引入 attempt-aware billing 或可配对撤销的账本接口。
|
||||
- 验证:检查 `external_generation_job.max_attempts`、worker 失败回写和钱包 ledger;失败后草稿进入 failed,重试应由用户重新触发新任务,而不是旧 job 自动 pending。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/api-server/src/asset_billing.rs`、`server-rs/crates/spacetime-module/src/runtime/profile.rs`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
|
||||
|
||||
## 外部生成队列不再由 HTTP 进程兜底执行
|
||||
|
||||
- 现象:拼图首关生成接口返回 `queued`,但生成页长时间不完成,重启 `genarrative-api.service` 也没有推进任务。
|
||||
- 原因:HTTP 角色只入队,不再直接调用外部 provider;如果没有运行 `GENARRATIVE_PROCESS_ROLE=external-generation-worker` 或 `all` 的进程,`external_generation_job` 会停留在 `pending/running`,直到有 worker claim。
|
||||
- 处理:生产用 `systemctl enable --now genarrative-external-generation-worker@1.service` 启动至少一个 worker;首次 API deploy 会在默认 worker pattern 下自动启用并启动 `@1`,并等待 worker active。扩容继续启动 `@2.service` 等实例,缩容停止多余实例;worker 收到停机信号后会停止 claim 新任务并等待当前任务完成。本地 smoke 可临时用 `GENARRATIVE_PROCESS_ROLE=all npm run dev`。
|
||||
- 验证:`systemctl status 'genarrative-external-generation-worker@*.service'` 能看到 worker 实例;队列任务被 claim 后 `worker_id` 与 `lease_expires_at` 会更新,完成后 session 进入 ready 或 failed。
|
||||
- 关联:`deploy/systemd/genarrative-external-generation-worker@.service`、`deploy/env/external-generation-worker.env.example`、`server-rs/crates/spacetime-module/src/external_generation.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 外部生成 worker 业务写回必须同事务校验 lease guard
|
||||
|
||||
- 现象:worker `complete/fail` 已校验 `worker_id + lease_token`,但如果玩法 session / work profile 写回在此之前单独调用,过期 worker 仍可能先写入业务状态,随后才在 job complete/fail 阶段失败;带计费包装的旧 worker 还可能因为 stale guard 错误触发补偿退款。
|
||||
- 原因:队列状态栅栏只保护 `external_generation_job` 自身,不会自动保护玩法 procedure。业务写回必须自己带 claim 后的 `job_id / worker_id / lease_token`,并在同一个 SpacetimeDB transaction 内校验 job 仍为 `running`、lease 未过期、job kind、owner 和 source entity 匹配。
|
||||
- 处理:拼图首图 worker 的前置 `compile_puzzle_agent_draft`、`save_puzzle_generated_images`、`save_puzzle_ui_background`、`mark_puzzle_draft_generation_failed` 和 `mark_puzzle_level_generation_failed` 已接入 `external_generation_job` lease guard;api-server 的资产扣费包装遇到这类 stale worker lease guard 错误时不执行补偿退款,错误文本包含 `external_generation_job 当前不是 running 状态` 或 `external_generation_job 不存在` 时也按 stale guard 处理。后续迁移其它玩法 worker 时必须复用该模式,不能只在 worker 进程内保存一份 token。
|
||||
- 验证:`cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server asset_operation_billing_does_not_refund_stale_worker_lease_errors --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/spacetime-module/src/puzzle.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`server-rs/crates/api-server/src/asset_billing.rs`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
|
||||
|
||||
## 外部生成 worker 核心业务写回失败不能完成 job
|
||||
|
||||
- 现象:worker 已经生成图片并拿到本地合成 session 快照,但 SpacetimeDB 业务写回因连接、旧 wasm 或 lease guard 失败没有真实落库;如果此时仍把 `external_generation_job` 标成 `completed`,前端只会看到队列完成而 session 长时间不变化,后续也没有 worker 会重领修复。
|
||||
- 原因:同步 HTTP handler 的“外部 provider 已成功但 SpacetimeDB 短暂不可用时返回内存快照”降级语义,不能直接搬进异步 worker。worker 的完成状态必须代表核心业务事实已经持久化。
|
||||
- 处理:worker 路径的 `save_puzzle_generated_images` / `save_puzzle_ui_background` 等核心业务写回失败时直接返回错误;只有核心写回已经成功后的非关键投影回写才允许降级记录 warning。业务失败态也必须先写回 session / work profile,写回成功后才允许把队列 job 标为 failed;失败态未写回时保留租约,等待 lease 过期后重领。生产首装和首次 API deploy 都必须至少启用一个 worker 实例,例如 `systemctl enable --now genarrative-external-generation-worker@1.service`。
|
||||
- 验证:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server asset_operation_billing_does_not_refund_stale_worker_lease_errors --manifest-path server-rs/Cargo.toml`,并在 smoke 时确认 queued 任务被 worker 消费后 session 真实更新。
|
||||
- 关联:`server-rs/crates/api-server/src/puzzle/draft.rs`、`server-rs/crates/api-server/src/puzzle/generation.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
|
||||
|
||||
## 平台异步错误必须带来源弹窗,不要只显示裸错误
|
||||
|
||||
- 现象:用户先后触发多个拼图或草稿生成时,旧请求失败后会在当前页面显示“图片生成失败”等裸错误,容易误判为当前正在看的拼图失败;错误文本也不便复制给开发排查。
|
||||
@@ -1761,3 +1793,11 @@
|
||||
- 处理:推荐页拖拽只校验当前是否有作品、多作品可切换以及是否正在提交动画,不再要求登录;登录态相关操作仍由点赞、改造等按钮自身权限控制。
|
||||
- 验证:`npx vitest run src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 覆盖访客态纵向滑动不弹登录且触发下一条推荐。
|
||||
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
|
||||
|
||||
## 外部生成 worker 不能只靠 worker_id 判定 lease owner
|
||||
|
||||
- 现象:外部生成任务超过单次 lease、worker 机器时钟漂移,或 systemd 实例误复用同一 `GENARRATIVE_EXTERNAL_GENERATION_WORKER_ID` 时,同一 job 可能被新 worker 重领,但旧 worker 仍在执行并尝试 complete/fail。
|
||||
- 原因:如果队列只校验 `worker_id`,过期执行者仍可能覆盖当前 lease;如果 claim 使用 worker 本机绝对时间,动态扩缩容时的时钟漂移会造成提前抢占或长期锁死。
|
||||
- 处理:`external_generation_job` 使用 SpacetimeDB `ctx.timestamp` 计算 claim/renew/complete/fail 时间,并在 claim 时生成 `lease_token`;worker 长任务期间调用 renew,complete/fail 必须携带同一个 `worker_id + lease_token`,且 lease 尚未过期。排查时先看 job 快照里的 `attempt`、`worker_id`、`lease_expires_at` 和 `lease_token` 是否按 claim 递增切换。
|
||||
- 验证:`cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml` 应覆盖 token 和时长 helper;`npm run check:spacetime-schema` 应确认新增字段在表末尾且有默认值。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
|
||||
|
||||
Reference in New Issue
Block a user