Files
ecm_sync_system/docs/collection_query_and_upgrade_plan.md
T
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

5.0 KiB
Raw Blame History

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(节点顶层属性)过滤
  • 可按 filternode.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_idO(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_idsget_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 化冷查询)

这条路线兼顾:

  • 速度(热路径仍在内存)
  • 清晰性(查询层解耦)
  • 内存边界(按需索引 + 分批加载)