Skip to content

perf(a_memorix): 持久化增加脏检测,无变更时跳过全量保存 - #1973

Open
CGl123 wants to merge 16 commits into
Mai-with-u:devfrom
CGl123:fix/perf-main-thread
Open

CGl123 wants to merge 16 commits into
Mai-with-u:devfrom
CGl123:fix/perf-main-thread

Conversation

@CGl123

@CGl123 CGl123 commented Aug 6, 2026

Copy link
Copy Markdown

问题

主进程主线程长期 CPU 100%(实测主进程 ~110%、主线程 99.9% R 状态),重启后暂时缓解但会复发。

根因

A_Memorix 持久化链路存在「无意义/重复全量保存」:

  1. 每次 ingest 无条件全量同步持久化ingest_service.py:448 每次 ingest_text 末尾无条件 _persist(),无脏标记/节流。
  2. episode 物化循环每 1 秒触发全量保存_episode_materialization_loop 默认 1 秒轮询,只要 rebuilt_items 非空就 _persist()(实际不写图/向量,是纯重复保存)。
  3. 摘要写回一次触发 2-3 次全量保存

单次 _persist() 全量序列化 graph(3493 节点)+ vector(9777 向量)+ SQLite(357MB),且存储位于 juicefs(FUSE)上,实测单次墙钟约 1 秒,并同步阻塞主事件循环。

改动

为图/向量存储增加变更脏检测,无未落盘变更时跳过全量保存:

  • graph_store.py_last_saved_graph_revision + has_pending_changes();save 成功后记录、load 后同步
  • vector_store.py_revision/_last_saved_revision + has_pending_changes();add/restore/delete 递增、load 重置
  • runtime_dependency_service.py_persist() 开头脏检测,图/向量均无变更时跳过;force_vectors/向量重建路径不跳过;无法判断的对象保守视为有变更(hasattr 防御)
  • 关闭时 _close_runtime 仍无条件 _persist(),正常退出时强制落盘兜底

验证(真实负载:群聊 + 私聊约 2 小时)

指标 修复前 修复后
主进程 CPU ~110%(持续满载) 最高 30%
图存储保存频率 活跃时接近每秒(约 7200 次/2h) 53 次/2h(平均间隔 2.4min,最大 12.6min)
保存时机 无脑每秒刷 与 14 次「聊天摘要自动写回」逐条对齐(只在真实 ingest 后保存)
  • pytests/A_memorix_test/708 passed,0 回归(另有 4 个失败为既有环境问题,已在原始代码上验证同样失败,与本次改动无关)
  • 日志可印证:保存时刻与真实记忆活动(摘要写回)逐条对齐;无对话时段完全无保存

风险与回退

  • 唯一风险:脏标记漏标导致变更未落盘。缓解:图走统一 _record_graph_change 入口、vector 四个写入入口全覆盖、关闭时强制落盘兜底、保守的 hasattr 策略(拿不准就保存)
  • 单个 commit,可独立回退

后续可优化(不在本 PR 范围)

  • _persist 移出主事件循环(需给 graph 写入路径加线程锁)
  • 摘要写回多次 persist 合并
  • 表达学习避免每批全量加载向量索引

Summary by CodeRabbit

  • 性能优化
    • 仅在检测到实际变更时执行图数据和向量数据的持久化,减少不必要的保存操作。
    • 持久化前自动确认已启用的稀疏索引可用,提升运行效率与稳定性。
  • 可靠性改进
    • 更准确地识别图存储和向量存储中尚未保存的变更,确保需要保存的数据不会被跳过。
  • 兼容性
    • 固定仪表板组件版本,提升运行环境的一致性。

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ac7f8152-430f-4d33-a2b7-8501f8a45cfa

📥 Commits

Reviewing files that changed from the base of the PR and between 9bdf22a and 3b24c08.

⛔ Files ignored due to path filters (2)
  • depends-data/repository-metrics.svg is excluded by !**/*.svg, !depends-data/**
  • depends-data/star-history.svg is excluded by !**/*.svg, !depends-data/**
📒 Files selected for processing (2)
  • pyproject.toml
  • requirements.txt

Walkthrough

本次变更为 GraphStore 和 VectorStore 增加未落盘变更跟踪。runtime persistence service 在确认无变更时跳过全量持久化。项目依赖将 maibot-dashboard 固定为 1.6.3

Changes

A_Memorix 持久化优化

Layer / File(s) Summary
存储版本与脏状态跟踪
src/A_memorix/core/storage/graph_store.py, src/A_memorix/core/storage/vector_store.py
GraphStore 和 VectorStore 记录当前版本与最近保存版本。新增 has_pending_changes(),并在保存和加载流程中同步版本状态。向量新增、恢复和删除操作会更新版本号。
运行时持久化跳过逻辑
src/A_memorix/core/runtime/services/runtime_dependency_service.py
在非强制保存、无需重建向量且相关存储无变更时,确保启用的稀疏索引已加载,并提前结束持久化流程。缺少脏检测接口的存储按存在变更处理。

Dashboard 依赖版本固定

Layer / File(s) Summary
固定 maibot-dashboard 版本
pyproject.toml, requirements.txt
两个依赖配置文件都将 maibot-dashboard 从最低版本约束改为 ==1.6.3

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 3b24c

The change is merge-ready after normal checks; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant RuntimeDependencyService
  participant GraphStore
  participant VectorStore
  participant SparseIndex
  RuntimeDependencyService->>GraphStore: 检查 has_pending_changes()
  GraphStore-->>RuntimeDependencyService: 返回图存储脏状态
  RuntimeDependencyService->>VectorStore: 检查 has_pending_changes()
  VectorStore-->>RuntimeDependencyService: 返回向量存储脏状态
  RuntimeDependencyService->>SparseIndex: 确保启用的稀疏索引已加载
  RuntimeDependencyService-->>RuntimeDependencyService: 无变更时跳过全量持久化
Loading

Possibly related PRs

  • Mai-with-u/MaiBot#1579:该 PR 也修改了 GraphStore 和 VectorStore,但涉及不同的存储行为。
  • Mai-with-u/MaiBot#1900:该 PR 涉及 GraphStore 和 VectorStore 的持久化状态跟踪,本次变更在此基础上扩展运行时持久化逻辑。

Suggested reviewers: sengokucola, a-dawn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了本次 PR 的主要变更,即增加持久化脏检测并在无变更时跳过全量保存。
Description check ✅ Passed 描述完整说明了问题、根因、改动、验证结果、风险和回退方案,虽未逐项填写模板勾选项,但不影响整体完整性。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/perf-main-thread
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/A_memorix/core/storage/graph_store.py`:
- Around line 1581-1582: 仅在加载完整成功且无需修复时建立 clean 基线:在 graph_store.py
的加载流程中,若矩阵或边哈希映射被修复则保持 dirty,或先持久化修复结果后再更新 _last_saved_graph_revision;在
vector_store.py 的完整加载成功路径之后再重置 revision,加载失败时保留旧状态或禁止复用该实例。
- Around line 1659-1662: 扩展图与向量存储的脏状态版本跟踪,避免仅凭现有 revision 相等而跳过未持久化变更:在
src/A_memorix/core/storage/graph_store.py:1659-1662 的 has_pending_changes
及相关路径中,为 _edge_hash_map 的新增、删除和重建更新 revision;在
src/A_memorix/core/storage/vector_store.py:2575-2583
为压缩、清空和失败事务引入独立提交版本,并在保存未完成时保留 dirty 状态。
- Around line 1470-1471: 更新 GraphStore.save() 中围绕 _last_saved_graph_revision
的保存逻辑,避免将传入备用目录 data_dir 的成功保存标记为绑定目录 self.data_dir 已落盘;参照 VectorStore.save()
的绑定目录校验,限制 data_dir 必须等于 self.data_dir,并仅在校验通过且保存成功后更新
_last_saved_graph_revision。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 52431f11-efc7-4b8e-95d2-ddcdf3c94608

📥 Commits

Reviewing files that changed from the base of the PR and between 051f136 and b68e689.

📒 Files selected for processing (4)
  • changelogs/changelog.md
  • src/A_memorix/core/runtime/services/runtime_dependency_service.py
  • src/A_memorix/core/storage/graph_store.py
  • src/A_memorix/core/storage/vector_store.py

Comment on lines +1470 to +1471
# 全部落盘成功后才记录已保存版本,供脏检测使用
self._last_saved_graph_revision = self._graph_revision

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

不要把备用目录的保存结果标记为绑定目录已落盘。

GraphStore.save(data_dir=...) 允许传入其他目录,但这里无条件更新全局 _last_saved_graph_revision。如果实例绑定目录 A、却先保存到目录 B,目录 A 仍可能没有这次变更。runtime_dependency_service.py 的后续脏检测会跳过目录 A 的保存。

请限制 data_dir 必须等于 self.data_dir,或按目录分别记录已保存版本。VectorStore.save() 已采用绑定目录校验。

建议修复
+        if self.data_dir is not None and data_dir.resolve() != self.data_dir.resolve():
+            raise ValueError("GraphStore 只能向初始化时绑定的 data_dir 提交")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 全部落盘成功后才记录已保存版本,供脏检测使用
self._last_saved_graph_revision = self._graph_revision
if self.data_dir is not None and data_dir.resolve() != self.data_dir.resolve():
raise ValueError("GraphStore 只能向初始化时绑定的 data_dir 提交")
# 全部落盘成功后才记录已保存版本,供脏检测使用
self._last_saved_graph_revision = self._graph_revision
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/A_memorix/core/storage/graph_store.py` around lines 1470 - 1471, 更新
GraphStore.save() 中围绕 _last_saved_graph_revision 的保存逻辑,避免将传入备用目录 data_dir
的成功保存标记为绑定目录 self.data_dir 已落盘;参照 VectorStore.save() 的绑定目录校验,限制 data_dir 必须等于
self.data_dir,并仅在校验通过且保存成功后更新 _last_saved_graph_revision。

Comment thread src/A_memorix/core/storage/graph_store.py
Comment thread src/A_memorix/core/storage/graph_store.py
@CGl123
CGl123 force-pushed the fix/perf-main-thread branch from b68e689 to 9bdf22a Compare August 6, 2026 13:46
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants