refactor(workflow): 隔离逻辑运行键为 node_group_id
将 checkpoint、异步任务与段结果缓存的唯一键从 execution_id 改为 node_group_id,区分换参重跑与续跑语义;恢复/续跑复用执行记录的组标识,换参重跑则换新组并软删旧组残留,消除软删墓碑导致同键重存失效的问题。
This commit is contained in:
+50
@@ -929,3 +929,53 @@ COMMENT ON COLUMN black_deacon_exec_workflow.user_id IS '执行用户ID(数字
|
||||
--------------------终局回填业务实扣:exec_workflow.actual_amount(用户钱包实际扣除金额,元;区别于 total_fee=模型按次费用合计)---------------------
|
||||
ALTER TABLE black_deacon_exec_workflow ADD COLUMN IF NOT EXISTS actual_amount NUMERIC(15,2) NOT NULL DEFAULT 0; -- 业务扣费(settle/cancel 结算实收;失败/未结算=0)
|
||||
COMMENT ON COLUMN black_deacon_exec_workflow.actual_amount IS '业务扣费(用户钱包实际扣除金额,元;结算/取消回填实收,失败/未结算=0,区别于 total_fee=模型按次费用合计)';
|
||||
|
||||
--------------------三表键按逻辑运行(node_group_id)隔离:exec_workflow 补列回填 + async/segment 加列换键 + checkpoint 活行归组(2026-09-04,详见《工作流状态表键按逻辑运行隔离技术设计.md》)--------------------
|
||||
-- 根因:flow_checkpoint 软删(execution_id 键)后同键 ON CONFLICT 重存不清 deleted_at → 续跑读不到断点从图头转圈。
|
||||
-- 方案:exec_workflow 持久化 node_group_id(逻辑运行标识:续跑复用、forceNewRun 换新 uuid);
|
||||
-- checkpoint_id / async / segment 唯一键一律纳入该组;同组重复只 Upsert 重置、软删即终态不复活、无物理删。
|
||||
-- 幂等:全程 ADD COLUMN IF NOT EXISTS / UPDATE 仅改空值 / DROP+CREATE UNIQUE IF EXISTS。
|
||||
-- 1) exec_workflow.node_group_id:仓库此前漏建该列 DDL(生产早期手工加列则此处 no-op)。
|
||||
-- 历史行回填自身 id(=其遗留 checkpoint/async/segment 旧键):部署前中断/失败可续跑的 exec 无缝衔接到新组键续跑;
|
||||
-- 已带真实 uuid 现役组的行(新代码产生的)不受影响。
|
||||
ALTER TABLE black_deacon_exec_workflow ADD COLUMN IF NOT EXISTS node_group_id VARCHAR(64) NOT NULL DEFAULT '';
|
||||
COMMENT ON COLUMN black_deacon_exec_workflow.node_group_id IS '节点组ID(逻辑运行标识:续跑复用,forceNewRun 换新 uuid)';
|
||||
UPDATE black_deacon_exec_workflow SET node_group_id = id::text WHERE node_group_id IS NULL OR node_group_id = '';
|
||||
|
||||
-- 2) async/segment 加列并按 exec 现役组回填。NULLIF 防空组并到 execution_id::text(=旧"exec 作用域"读集,等价不丢不并)
|
||||
ALTER TABLE black_deacon_flow_async_task ADD COLUMN IF NOT EXISTS node_group_id VARCHAR(64);
|
||||
ALTER TABLE black_deacon_flow_segment_result ADD COLUMN IF NOT EXISTS node_group_id VARCHAR(64);
|
||||
UPDATE black_deacon_flow_async_task a SET node_group_id = COALESCE(NULLIF(e.node_group_id,''), a.execution_id::text)
|
||||
FROM black_deacon_exec_workflow e WHERE a.node_group_id IS NULL AND e.id = a.execution_id;
|
||||
UPDATE black_deacon_flow_segment_result s SET node_group_id = COALESCE(NULLIF(e.node_group_id,''), s.execution_id::text)
|
||||
FROM black_deacon_exec_workflow e WHERE s.node_group_id IS NULL AND e.id = s.execution_id;
|
||||
-- exec 行缺失的孤儿兜底(理论无):按 execution_id 文本伪组,保证唯一性成立
|
||||
UPDATE black_deacon_flow_async_task SET node_group_id = execution_id::text WHERE node_group_id IS NULL;
|
||||
UPDATE black_deacon_flow_segment_result SET node_group_id = execution_id::text WHERE node_group_id IS NULL;
|
||||
ALTER TABLE black_deacon_flow_async_task ALTER COLUMN node_group_id SET NOT NULL;
|
||||
ALTER TABLE black_deacon_flow_segment_result ALTER COLUMN node_group_id SET NOT NULL;
|
||||
|
||||
-- 3) 换唯一键:旧 (execution_id,node_id,segment_index) → 新 (node_group_id,node_id,segment_index)
|
||||
DROP INDEX IF EXISTS uk_async_task_exec_node_seg;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_async_task_group_node_seg ON black_deacon_flow_async_task(node_group_id, node_id, segment_index);
|
||||
DROP INDEX IF EXISTS uk_segment_result_exec_node_idx;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_segment_result_group_node_idx ON black_deacon_flow_segment_result(node_group_id, node_id, segment_index);
|
||||
|
||||
-- 4) checkpoint 表零 DDL:把仍活的断点从"checkpoint_id=executionId(数值串)"归一到"checkpoint_id=exec 现役组",
|
||||
-- 使新组键读到部署前中断/失败的断点。历史 exec 组=自身 id → 天然不变;仅 uuid 现役组发生重命名
|
||||
-- (单 exec 至多一行活断点;组为 uuid 不与遗留数值串冲突;守卫排除空串/自同值防并桶)
|
||||
UPDATE black_deacon_flow_checkpoint c SET checkpoint_id = e.node_group_id
|
||||
FROM black_deacon_exec_workflow e
|
||||
WHERE c.deleted_at IS NULL AND c.checkpoint_id = e.id::text
|
||||
AND e.node_group_id IS NOT NULL AND e.node_group_id <> '' AND e.node_group_id <> e.id::text;
|
||||
|
||||
-- 5) 存量毒槽自愈:旧 forceNewRun 三清把"该 exec 仍可续跑"的 checkpoint 槽软删成了墓碑;uk_checkpoint_id 无条件唯一、
|
||||
-- 墓碑仍占槽,Save 的 ON CONFLICT 只会再 upsert 进墓碑且 deleted_at 不清 → 续跑永远读不到断点 = "从头转圈"现场。
|
||||
-- 软删即终态、不复活:被占槽不可再用,故给"可续跑(status 1/3)但当前槽无活断点"的 exec 重指全新 uuid 组,
|
||||
-- 下次续跑写新活行、此后断点正常落库可断点续跑。代价:这批 exec 历史段/异步缓存(按旧 id 组)随之失联,
|
||||
-- 属毒槽必然代价、只影响存量毒槽行;正常失败(槽上活断点在)的 exec 不动、断点续跑无缝衔接。
|
||||
UPDATE black_deacon_exec_workflow e
|
||||
SET node_group_id = gen_random_uuid()::text
|
||||
WHERE e.status IN (1, 3)
|
||||
AND NOT EXISTS (SELECT 1 FROM black_deacon_flow_checkpoint c
|
||||
WHERE c.deleted_at IS NULL AND c.checkpoint_id = e.node_group_id);
|
||||
|
||||
Reference in New Issue
Block a user