
背景问题
SummerMemory Graph(知识图谱可视化)的 /graph-data 接口响应耗时 5.3 秒,随着记忆文件不断增长,这个问题会指数级恶化。
问题分析
1. O(n²) SQL 查询瓶颈
原始代码对每对文件执行一次独立的 PostgreSQL 查询,计算 embedding 向量余弦距离:
for i, path1 in enumerate(path_list):
for path2 in path_list[i+1:]:
cur2.execute("""
SELECT MAX(1 - (m1.embedding <=> m2.embedding)) AS similarity
FROM memories m1, memories m2
WHERE m1.path = %s AND m2.path = %s
""", (path1, path2))
100 个文件 = 4950 次 SQL 查询,每次都做向量距离运算。
2. 无意义的噪音链接
链接由两部分组成:
- 目录关系链接(同目录即关联,weight=0.3):4950 条
- embedding 语义链接(weight>0.8):2086 条
大部分记忆文件都在 memory/ 目录下,导致每个节点和所有节点相连,图谱变成毛线团。
3. Cosmograph WASM 内存泄漏
动态更新 links 时,Cosmograph 的 WASM DuckDB 表生命周期管理有 bug,反复 setConfig 会导致 Array buffer allocation failed。
优化方案
性能优化:单次查询 + numpy 质心计算
# 一次查出所有 embedding
cur2.execute("SELECT path, embedding FROM memories WHERE embedding IS NOT NULL")
# Python 内存中计算每个文件的 embedding 质心
for path, embs in path_embs.items():
arr = np.array(embs)
centroid = normalize(arr).mean(axis=0)
centroids[path] = normalize(centroid)
# 内存中 O(n²) 算余弦相似度(向量点积)
sim = float(np.dot(c1, centroids[path2]))
结果
| 指标 | 优化前 | 优化后 | |------|--------|--------| | 接口耗时 | 5.3s | 0.35s | | SQL 查询次数 | 4950 次 | 1 次 | | 链接数 | 7036(70%噪音) | 891(纯语义) |
交互式相似度阈值
前端右侧边栏新增滑块控件,拖动即可实时调整链接过滤阈值:
- 拖动滑块 → debounce 300ms
- 请求后端
/graph-data?threshold=0.90 - 后端按阈值过滤返回 links
- 前端淡出→更新→淡入 + 重新启动模拟动画
这样避免了 Cosmograph WASM 内部表冲突的内存泄漏问题,阈值过滤逻辑全部在后端完成。
其他改进
- 左侧文件列表按
last_updated降序排列(最新更新的文件在最上面) - 去掉了目录关系的噪音链接,只保留 embedding 语义链接
- 节点数据新增
last_updated时间字段
技术栈
- 后端:Python + PostgreSQL(pgvector)+ numpy
- 前端:Vue 3 + Cosmograph
- 部署:阿里云 + 宝塔面板
源码
- GitHub:[Likefr/SummerMemory](https://github.com/Likefr/SummerMemory)
- 在线体验:[ai.likefr.com/graph](https://ai.likefr.com/graph)