# Collection 查询现状与升级方案

## 1. 当前实现梳理

核心代码：
- `sync_state_machine/common/collection.py`
- `sync_state_machine/common/binding.py`
- `sync_state_machine/datasource/datasource.py`
- `sync_state_machine/sync_system/strategy_ops/*.py`

### 1.1 DataCollection 当前内存结构

`DataCollection` 目前核心是两张索引：
- `_nodes: Dict[node_id, SyncNode]`
- `_data_id_to_node_id: Dict[data_id, node_id]`

特点：
- `node_id` 查找是 O(1)
- `data_id` 反查 node 是 O(1)
- 其他查询（按状态/动作/绑定状态/业务字段）目前主要依赖遍历

### 1.2 查询路径

#### A. 通用筛选 `filter(...)`
- 先取 `_nodes.values()`
- 可按 `node_type` 过滤
- 可按 `node_filter`（节点顶层属性）过滤
- 可按 `filter`（`node.data` 内字段）过滤

复杂度：最差接近 O(N)

#### B. 状态筛选 `filter_by_state_ids(...)`
- 先调用 `filter(node_type=...)`
- 对每个候选节点，根据状态机配置逐条匹配 `binding_status/action/status/data_id_exist`
- `state_ids` 多个时，执行多次匹配

复杂度：约 O(N * |state_ids|)

### 1.3 调用热点

在 pipeline 中，以下路径高频调用状态筛选：
- bind 阶段（S00）
- create 阶段（S06）
- update 阶段（S07）
- delete 检查（S09）

对应代码：
- `sync_state_machine/datasource/datasource.py` 的 `_sync_nodes`
- `sync_state_machine/sync_system/strategy_ops/bind_ops.py`

### 1.4 BindingManager 当前查询

`BindingManager` 当前结构：
- `_bindings: Dict[node_type, Dict[local_id, remote_id]]`

其中：
- `get_remote_id`：O(1)
- `get_local_id`：当前是遍历同类型全部绑定，O(K)

当绑定数量很大时，`get_local_id` 会成为热点。

---

## 2. 本次已修正项

### 2.1 `filter(data_filter)` 查询中的不必要深拷贝

原实现在 `filter` 的 data 过滤里每个节点调用 `node.get_data()`，会触发深拷贝，数据量大时会产生明显 CPU/内存开销。

已改为只读访问 `node.data`：
- 不改变返回语义
- 降低过滤时的对象复制成本

变更文件：
- `sync_state_machine/common/collection.py`

### 2.2 类型标注修正

`node_filter` 的注解从 `callable` 修正为 `Callable[[SyncNode], bool]`，增强类型可读性和 IDE 体验。

---

## 3. 升级方案（针对三类目标）

目标：
1) 查询性能
2) 架构明确
3) 内存数据不过多

### 阶段 0（当前）
- 保持现有语义，先完成可观测性：记录各查询调用次数和耗时（尤其 `filter_by_state_ids` 与 `get_local_id`）

### 阶段 1（低风险，优先）

#### 1. 查询性能
- 新增二级内存索引（不替换现有结构，仅并行维护）：
  - `by_node_type: Dict[str, Set[node_id]]`
  - `by_status: Dict[SyncStatus, Set[node_id]]`
  - `by_action: Dict[SyncAction, Set[node_id]]`
  - `by_binding_status: Dict[BindingStatus, Set[node_id]]`
  - `by_type_status_action: Dict[(node_type,status,action), Set[node_id]]`
- `filter_by_state_ids` 先用组合索引缩小候选，再做状态机细匹配

#### 2. 架构明确
- 增加查询层接口（例如 `CollectionQueryEngine`），将“筛选逻辑”与“存储结构”分离
- 业务侧只依赖查询接口，不直接依赖 `_nodes`

#### 3. 内存控制
- 新增按需字段索引，仅为高频字段建立（如 `project_id/company_id/contract_id`）
- 索引集采用 `set[node_id]`，避免复制节点对象

### 阶段 2（中风险）

#### 1. 查询性能
- `BindingManager` 增加反向索引：
  - `remote_to_local: Dict[node_type, Dict[remote_id, local_id]]`
- 将 `get_local_id` 从 O(K) 降至 O(1)

#### 2. 架构明确
- 统一节点写路径（add/update/delete）触发索引增量更新
- 明确“不允许绕过 Collection 写入节点”

#### 3. 内存控制
- 增加可配置的“弱索引模式”：在低内存场景关闭部分次级索引

### 阶段 3（可选，较高风险）

#### 1. 查询性能
- 引入 SQLite 热查询表（内存 SQLite 或磁盘 SQLite）承接复杂组合查询
- 保留内存索引作为热点路径，DB 作为补充查询后端

#### 2. 架构明确
- 形成分层：
  - 热路径：内存索引
  - 冷路径：SQLite 查询
  - 归档：持久化 DB

#### 3. 内存控制
- 支持“分批加载 + 分批同步 + checkpoint”
- 将非活跃节点转冷存储，只在命中时回载

---

## 4. 启动/重置语义说明（避免误判）

当前默认不是每次全清空：
- 启动会先加载持久化，再执行 E01 归并清理
- 仅在 `persist.wipe_on_start=true` 时会在启动前删除持久化 DB

因此若做“分批/多轮 pipeline”，建议：
- 保持 `wipe_on_start=false`
- 通过显式 checkpoint 与增量范围控制保证状态一致性

---

## 5. 结论

短期最优路径：
- 先完成阶段 1 + 阶段 2（低/中风险），通常能解决大部分查询性能问题
- 再根据压测结果决定是否进入阶段 3（SQL 化冷查询）

这条路线兼顾：
- 速度（热路径仍在内存）
- 清晰性（查询层解耦）
- 内存边界（按需索引 + 分批加载）
