first commit

This commit is contained in:
strepsiades
2026-03-09 16:31:42 +08:00
commit 4a9c444b10
486 changed files with 52479 additions and 0 deletions
@@ -0,0 +1,152 @@
# 节点状态持久化与人工介入规范
> **代码实现**`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 绑定需手工指定