# 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 化冷查询) 这条路线兼顾: - 速度(热路径仍在内存) - 清晰性(查询层解耦) - 内存边界(按需索引 + 分批加载)