# 节点状态持久化与人工介入规范 > **代码实现**:`sync_system_new/common/state_reset.py` --- ## 1. 持久化规则 ### 1.1 持久化目标 | 目标 | 说明 | |------|------| | **可恢复性** | 系统重启后能从上次状态继续 | | **可观测性** | 失败和异常状态要保留,供人工查看 | | **可追溯性** | 失败信息持久化,不自动重试,需人工确认 | | **绑定稳定性** | 手工绑定一旦建立,永不自动改变 | ### 1.2 分阶段重置设计 状态重置分为两个阶段执行: #### 阶段1:加载时重置(Collection.load_from_persistence) | 字段 | 性质 | 持久化 | 阶段1重置 | |------|------|--------|-----------| | `node_id` | 恒定标识 | ✅ 必须 | ❌ 不重置 | | `data_id` | 业务主键 | ✅ 必须 | ❌ 不重置 | | `data` | 业务数据 | ✅ 必须 | ✅ **重置**(每次重新取数) | | `origin_data` | 原始快照 | ✅ 必须 | ✅ **重置**(每次重新取数) | | `depend_ids` | 依赖列表 | ❌ **不持久化** | ✅ **初始化为[]**(bind 阶段从 data 实时计算) | | `binding_status` | 绑定状态 | ✅ 必须 | ✅ **重置为 UNCHECKED** | | `action` | 同步意图 | ✅ 保存 | ❌ **保留**(用于阶段2判断) | | `status` | 执行状态 | ✅ 保存 | ❌ **保留**(全部保留,不重置) | | `error` | 错误信息 | ✅ 保存 | ✅ **重置为None**(清空上次错误) | | `context` | 扩展上下文 | ✅ 保存 | ❌ 不重置 | #### 阶段2:数据加载后清理(Pipeline._cleanup_create_failed_zombies) 在数据加载完成后、策略执行前,执行CREATE失败节点清理: 1. **检查条件**: - 本地节点 `action == CREATE`(上次尝试创建) - 本地节点 `status == FAILED`(创建失败) 2. **清理动作**: - **删除本地失败节点**(从 collection 中移除) - 如果有绑定记录: - 解除绑定关系 - 删除远程 collection 中的僵尸节点 3. **最后重置 action**: - 所有剩余节点的 `action` 重置为 NONE - 准备进入策略阶段 4. **下次同步的自动恢复**: - 被删除的节点会从数据源重新加载 - 如果远程实际已创建成功(网络波动导致未获取到结果)→ 绑定阶段会自动绑定上 - 如果远程实际也失败了 → 重新尝试创建 5. **UPDATE 阶段保护**: - UPDATE 操作会跳过所有 `status == FAILED` 的节点 - 防止 UPDATE 阶段覆盖 CREATE/UPDATE 失败的状态和错误信息 - 确保失败节点的 action 和 error 信息保持不变,直到下次同步清理 > **说明**: > - `data` / `origin_data`:每次运行从数据源重新获取,保证数据最新 > - `depend_ids`:**不持久化**,每次 bind 阶段从 data 的业务字段(如 contract_id, project_id)实时提取 > - `binding_status`:持久化用于审计,加载时重置为 `UNCHECKED`,由 bind() 重新判定 > - `error`:**阶段1清空**,避免上次错误影响本次运行,新错误会在各阶段重新生成 > - `action`:**阶段1保留**用于识别 CREATE 失败的节点,**阶段2清理后重置** > - `status`:**阶段1全部保留**(包括 FAILED、PRECONDITION_FAILED 等),不自动重置 > - **CREATE 失败节点会被自动删除并重新加载**,UPDATE/DELETE 失败保留需人工处理 > - **UPDATE 操作会跳过 FAILED 节点**,避免覆盖失败状态和错误信息 ### 1.3 绑定记录 vs binding_status | 概念 | 存储位置 | 持久性 | 如何改变 | |------|---------|--------|---------| | **绑定记录** | `bindings` 表 | **永久保留** | 只能手工 bind/unbind,或 CREATE 失败自动清理 | | **binding_status** | `nodes` 表 | 保存但可重判 | 每次 bind() 重新判定 | ### 1.4 CREATE 失败的自动恢复 当 CREATE 操作失败时,会产生"僵尸绑定": - 绑定记录已建立(local_id → remote_node_id) - 但远程节点的 `data_id` 为空(API 未返回真实 ID) **下次同步时的自动恢复流程**: 1. 阶段1加载:保留 `action=CREATE` 2. 阶段2清理:检测到僵尸绑定,自动清理 3. 本地节点恢复为 MISSING 状态 4. 策略阶段重新判定,再次尝试 CREATE --- ## 2. 人工介入规范 ### 2.1 介入时机 | 时机 | 场景 | 操作 | |------|------|------| | **运行前** | 查看上次失败记录 | 查看持久化的 error,决定是否修复后重试 | | **bind() 后** | 出现 ABNORMAL/WARNING | 检查数据一致性,手工修复或确认 | | **bind() 后** | 1:N / N:N 映射 | 手工绑定指定远程记录 | | **sync() 后** | 批量失败 | 分析失败原因,决定重试策略 | ### 2.2 binding_status 与介入需求 | binding_status | 含义 | 需要介入 | 说明 | |----------------|------|---------|------| | `UNCHECKED` | 未检查 | ❌ | 等待 bind() 判定 | | `NORMAL` | 绑定正常 | ❌ | 自动处理 | | `MISSING` | 无绑定记录 | ❌ | 自动创建 | | `WARNING` | 本地有绑定但远程无数据 | ⚠️ 可选 | 可能是远程删除,需确认 | | `ABNORMAL` | 状态不一致 | ✅ **必须** | 需人工排查 | | `DEPENDENCY_ERROR` | 依赖未就绪 | ❌ | 等依赖解决后自动重试 | ### 2.3 人工绑定操作 ```python # 手工绑定(1:N 场景) binding_manager.bind(node_id="local_xxx", data_id="remote_xxx") # 手工解绑 binding_manager.unbind(node_id="local_xxx") ``` 绑定记录一旦建立,永不自动改变,只能手工操作。 ### 2.4 职责分工 | 职责 | 系统自动 | 人工介入 | |------|---------|---------| | 1:1 自动绑定 | ✅ | - | | 1:N / N:N 绑定 | ❌ | ✅ | | NORMAL 节点同步 | ✅ | - | | MISSING 节点创建 | ✅ | - | | CREATE 失败重试 | ✅(自动清理僵尸) | ⚠️(需确认后手动重置) | | UPDATE 失败重试 | ❌(保留FAILED状态) | ✅(需确认后手动重置) | | ABNORMAL 处理 | ❌ | ✅ | | WARNING 确认 | ❌ | ✅(可选) | --- ## 3. 总结 1. **分阶段重置**: - 阶段1(加载时):只重置 binding_status,**保留 action/status/error** - 阶段2(数据加载后):清理 CREATE 失败僵尸,然后重置 action 2. **失败状态持久化**:FAILED 状态和 error 信息保留,不自动重试 3. **绑定记录**:永久保留,除非 CREATE 失败自动清理或手工操作 4. **人工介入**:失败需确认后手动重置,ABNORMAL/WARNING 需人工处理,1:N 绑定需手工指定