Neo4j Cypher查询找到未连接节点速度太慢。

7
给定以下Neo4j模式(简化但显示了重要点)。有两种类型的节点NODEVERSIONVERSION通过VERSION_OF关系连接到NODEVERSION节点确实具有两个属性fromuntil,表示有效时间跨度-可以都是NULL(在Neo4j术语中不存在)表示无限制NODE可以通过HAS_CHILD关系连接。同样,这些关系也有两个属性fromuntil,表示有效时间跨度-可以都是NULL(在Neo4j术语中不存在)表示无限制

编辑:版本节点和HAS_CHILD关系上的有效日期是独立的(即使示例巧合地显示它们对齐)。

enter image description here

这个例子展示了两个节点NODEABA 有两个版本VERSION: AV1 直到6/30/17和从7/1/17开始的 AV2,而B只有一个无限制的版本BV1。在6/30/17之前,B通过HAS_CHILD关系连接到A
现在的挑战是查询图形以获取所有在特定时间不是child(即根节点)的节点。根据上面的例子,如果查询日期为6/1/17,则查询应该仅返回B,但如果查询日期为8/1/17,则应返回BA(因为从7/1/17起A不再是B的child)。
当前查询与上述查询大致相似:
MATCH (n1:NODE)
OPTIONAL MATCH (n1)<-[c]-(n2:NODE), (n2)<-[:VERSION_OF]-(nv2:ITEM_VERSION)
WHERE (c.from <= {date} <= c.until)
AND (nv2.from <= {date} <= nv2.until)
WITH n1 WHERE c IS NULL 
MATCH (n1)<-[:VERSION_OF]-(nv1:ITEM_VERSION)
WHERE nv1.from <= {date} <= nv1.until
RETURN n1, nv1 
ORDER BY toLower(nv1.title) ASC 
SKIP 0 LIMIT 15

这个查询一般情况下运行良好,但在处理大型数据集(与实际生产数据集相当)时开始变得非常缓慢。使用 20-30k 个 NODE(大约有两倍数量的 VERSION),真正的查询在运行 Mac OS X 上的小 Docker 容器上大约需要 500-700 毫秒,这是可以接受的。但是,在具有 1.5M 个 NODE(大约有两倍数量的 VERSION)的情况下,真正的查询在裸机服务器上(除了 Neo4j 之外没有其他运行内容)需要略多于 1 分钟。这是不可接受的。

我们有什么选项来调整此查询吗?是否有更好的方法来处理 NODE 的版本控制(我怀疑这不是性能问题)或关系的有效性?我知道无法对关系属性进行索引,因此可能有更好的模式用于处理这些关系的有效性。

任何帮助或哪怕最微小的提示都将不胜感激。

Michael Hunger 的回答 后编辑:

  1. Percentage of root nodes:

    With the current example data set (1.5M nodes) the result set contains about 2k rows. That's less than 1%.

  2. ITEM_VERSION node in first MATCH:

    We're using the ITEM_VERSION nv2 to filter the result set to ITEM nodes that have no connection other ITEM nodes at the given date. That means that either no relationship must exist that is valid for the given date or the connected item must not have an ITEM_VERSION that is valid for the given date. I'm trying to illustrate this:

    // date 6/1/17
    
    // n1 returned because relationship not valid
    (nv1 ...)->(n1)-[X_HAS_CHILD ...6/30/17]->(n2)<-(nv2 ...)
    
    // n1 not returned because relationship and connected item n2 valid
    (nv1 ...)->(n1)-[X_HAS_CHILD ...]->(n2)<-(nv2 ...)
    
    // n1 returned because connected item n2 not valid even though relationship is valid
    (nv1 ...)->(n1)-[X_HAS_CHILD ...]->(n2)<-(nv2 ...6/30/17)
    
  3. No use of relationship-types:

    The problem here is that the software features a user-defined schema and ITEM nodes are connected by custom relationship-types. As we can't have multiple types/labels on a relationship the only common characteristic for these kind of relationships is that they all start with X_. That's been left out of the simplified example here. Would searching with the predicate type(r) STARTS WITH 'X_' help here?


:VERSION节点的from和until日期与HAS_CHILD关系上的from和until日期之间是否存在任何相关性?如果它们对齐,将关系指向相关的:VERSION节点可能会更好。 - InverseFalcon
@InverseFalcon 不是的。关系和节点的“版本控制”是独立的。也许这个例子不是最好的,因为 AV1 的版本有效性恰好与 HAS_CHILD 关系相同。但这些可以是任意日期。 - Stefan Gehrig
如果您知道要搜索哪些关系类型,可以将它们全部列出,例如 -[:X_FOO|:X_BAR*]-> - Michael Hunger
@MichaelHunger:这样做比无限制的搜索好吗?X_关系的大约数量为13.5M,而总关系数为19.5M-因此数据库中约70%的关系是自定义的。 - Stefan Gehrig
@MichaelHunger:如果我添加所有可用的标签,我会得到更多的数据库访问。这有任何意义吗? - Stefan Gehrig
2个回答

3

您使用的Neo4j版本是什么?

在您的示例日期中,您的150万个节点中有多少百分比将被发现作为根节点?如果没有限制,返回多少数据?也许问题不在于匹配,而在于最后的排序?

我不确定为什么您在第一部分中有VERSION节点,至少您没有将它们描述为确定根节点的相关节点。

您没有使用关系类型。

MATCH (n1:NODE) // matches 1.5M nodes
// has to do 1.5M * degree optional matches
OPTIONAL MATCH (n1)<-[c:HAS_CHILD]-(n2) WHERE (c.from <= {date} <= c.until)
WITH n1 WHERE c IS NULL
// how many root nodes are left?
// # root nodes * version degree (1..2)
MATCH (n1)<-[:VERSION_OF]-(nv1:ITEM_VERSION)
WHERE nv1.from <= {date} <= nv1.until
// has to sort all those
WITH n1, nv1, toLower(nv1.title) as title
RETURN n1, nv1
ORDER BY title ASC 
SKIP 0 LIMIT 15

1
我认为改进的好的开始是使用索引匹配节点,这样您可以快速获取一个较小的相关子集进行搜索。目前您的方法必须检查所有的:NODEs及其上的所有关系和模式,这对于大量数据来说是不可扩展的,正如您所发现的那样。
目前,您的图中唯一具有日期/时间属性的节点是:ITEM_VERSION节点,因此让我们从这些节点开始。您需要在:ITEM_VERSION的from和until属性上建立索引以进行快速查找。
null值将成为您查找的问题,因为任何与null值的不等式都会返回null,并且大多数处理null值的解决方法(使用COALESCE()或几个ANDs/ORs用于null情况)似乎会防止使用索引查找,这就是我提出特定建议的原因。
我建议您用最小值和最大值替换from和until中的null值,这样应该可以利用索引查找节点:
MATCH (version:ITEM_VERSION)
WHERE version.from <= {date} <= version.until
MATCH (version)<-[:VERSION_OF]-(node:NODE)
...

那至少可以在开始时提供对更小的节点子集的快速访问,以继续查询。

1
感谢您的回复。您关于“ITEM_VERSION”优先方法的想法似乎很合理。您也是对的,索引已经完成了。fromuntil属性也是如此-它们分别使用09999123作为最小和最大值。这允许使用version.from <= {date} <= version.until进行范围扫描。日期通常以这种整数格式存储(而不是作为日期/时间字符串)。 - Stefan Gehrig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接