Neo4j查询npm依赖时变长路径深度≥12永久运行无返回如何解决
问题描述
我正在实现Node.js/JavaScript生态下指定npm包的全依赖查询功能,涉及全量npm仓库数据的爬取分析,数据模型为包与包版本的关联关系,一个包可对应多个版本。目前我已尝试所有可行方案仍无法解决问题,特来求助。
当前数据库共存储113339030条依赖关系、19753269个版本节点,所有代码运行正常,直到遇到react-scripts这个依赖量极大的包:它的直接+传递依赖会导致所有查询失效。现有公开依赖可视化工具中,一个查询后永远无法返回结果,另一个生成的依赖图过大难以分析。
原有PostgreSQL方案
最初我使用PostgreSQL的递归公共表表达式实现查询,代码如下:
with recursive cte as ( select child_id from dependencies where dependencies.parent_id = 16674850 union select dependencies.child_id from cte left join dependencies on cte.child_id = dependencies.parent_id where cte.child_id is not null ) select * from cte;
该查询返回1726条结果符合预期,公开可查的同版本react-scripts依赖数为1445条,偏差在可接受范围内。但我需要获取节点的访问路径,PostgreSQL使用UNION无法实现该需求,使用UNION ALL会导致查询复杂度和耗时大幅上升,因此我选择改用Neo4j实现。
Neo4j方案遇到的问题
我的节点属性如下:
version_id: 整数name: 字符串version: 字符串
我编写了如下基础查询语句,目标是查询version_id为16674850的Version节点的所有依赖:
MATCH p = (a:Version {version_id: 16674850})-[:DEPENDS_ON*..11]->(b:Version) return DISTINCT b;
我已为version_id建立索引:
CREATE INDEX FOR (version:Version) ON (version.version_id)
当变长路径的深度上限设为11及以下时查询正常,一旦深度上限设为12及以上,查询就会永久运行无返回。
我的Neo4j部署在Docker中,已经调整了内存配置:
- NEO4J_dbms_memory_heap_initial__size=2G - NEO4J_dbms_memory_heap_max__size=2G - NEO4J_dbms_memory_pagecache_size=1G
我为这个问题已经花费了6周时间,不想放弃这个软件依赖分析图项目,恳请各位提供解决方案,非常感谢!
2021年9月28日补充:我已上传样本数据集,可用于复现问题,包含737.1MB的版本数据文件和1.7GB的依赖关系数据文件,数据导入脚本如下:
neo4j-admin import \ --database=deps \ --skip-bad-relationships \ --id-type=INTEGER \ --nodes=Version=import/versions.csv \ --relationships=DEPENDS_ON=import/dependencies.csv
解决方案
你遇到的核心问题是npm依赖图普遍存在大量循环依赖和重复路径,当查询深度超过11时,Cypher默认的路径遍历逻辑会出现路径爆炸,导致计算量指数级上升,可参考以下方案优化:
- 使用节点去重的遍历逻辑
npm依赖图中同一个版本节点可能被多条路径引用,原生Cypher的变长路径查询会保留所有可能的路径,导致内存和计算开销指数级上升。改用APOC库的专用遍历过程,强制不重复访问同一个节点,从根源避免路径爆炸:
MATCH (a:Version {version_id: 16674850}) CALL apoc.path.subgraphNodes(a, { relationshipFilter: "DEPENDS_ON>", maxLevel: 20, avoidReuse: true }) YIELD node RETURN node AS b
如果需要同时获取访问路径,可以改用apoc.path.expandConfig过程,配置uniqueness: "NODE_PATH"保证单条路径中不会出现重复节点,同时返回所有不重复的路径信息。
2. 分层遍历替代全路径匹配
参考你之前PostgreSQL用UNION去重的逻辑,改为分层迭代查询:每一层只保留之前从未访问过的节点,逐层向下遍历,既可以保留节点的层级和路径信息,也不会出现路径爆炸问题,整体查询复杂度仅和总依赖节点数线性相关。
3. 优化配置适配大图查询
你的数据集总大小超过2GB,现有2G堆内存、1G页缓存的配置不足以支撑深度遍历,建议将堆内存调整到4G以上,页缓存调整到2G,同时适当调大查询内存阈值,避免大查询被系统自动中断。
4. 预计算依赖闭包
如果需要高频查询全量依赖,可离线预计算每个版本的所有依赖节点,存入单独的属性或者关系表,查询时直接读取预计算结果,查询耗时可降到毫秒级。
内容的提问来源于stack exchange,提问作者zemirco

