Neo4J Cypher:匹配多个属性并创建关系的性能

时间:2017-03-16 18:46:28

标签: performance neo4j cypher

一点背景:我正在尝试使用Neo4J(作为新手,但在其他数据库技术方面经验丰富)可用作我们身份智能业务中的主数据管理系统,特别是在构建图表时地点,身份属性(例如:电子邮件地址,电话号码,选民名单数据等),这些节点之间的关系表达了​​一些有意义的东西,例如在使用电子邮件地址或注册了电话号码的地方。

所需的系统属性:我希望这个系统具有一些对我们有价值的特定属性:

  • 从大量提供商(100+)快速提取信息,这排除了冗长(小时)的ETL流程,简短的提交就可以了!
  • 在任何时候都在线,这排除了批量导入器的使用,我们最有可能使用容错集群,分片会很好:)
  • 能够最终摄取〜30G记录/年(~1000 /秒)并保留它们,创建和保留~100G关系/年,现在我们正在摄取~1 / 10的负载。

我遇到困难:我一直在尝试使用Azure中的单个节点,32GB RAM,4个内核,使用非本地磁盘,运行Debian 8和Neo4J 3.1.1。通过使用LOAD CSV或自制Java和螺栓,这可以快速地将英国邮政地址文件(PAF)和大约29M记录一起提取并关联在一起。我还摄取了一组测试电子邮件地址数据,大约有20M记录,现在需要根据匹配的邮政编码,建筑物编号以及两个数据集之间可能的其他字段建立关系。这是使用Cypher时速度慢得多的地方,这是迄今为止我能够创建的最快的查询:

UNWIND {list} AS i
MATCH(e:DDSEMAIL) WHERE ID(e) = i WITH e
MATCH(s:SUBBNAME) USING INDEX s:SUBBNAME(SBNA)
    WHERE upper(e.Building) = s.SBNA WITH e,s
MATCH(m:MAINFILE)
    WHERE trim(split(e.Postcode,' ')[0]) = m.OUTC AND
    trim(split(e.Postcode,' ')[1]) = m.INCO AND
    right('0000'+e.HouseNo,4) = m.BNUM AND
    (m)-[:IS_SUBBNAME]->(s)
CREATE (e)-[r:USED_AT]->(m)
RETURN COUNT(r);

索引是:

ON :DDSEMAIL(HouseNo)  ONLINE  
ON :DDSEMAIL(Postcode) ONLINE  
ON :DDSEMAIL(Building) ONLINE  
ON :MAINFILE(OUTC)     ONLINE  
ON :MAINFILE(INCO)     ONLINE  
ON :MAINFILE(BNUM)     ONLINE  
ON :SUBBNAME(SBNA)     ONLINE  

请注意,{list}参数是从已经枚举了所有~20M DDSEMAIL节点的Java客户端通过bolt提供的,并且正在批量处理事务(通常一次只有1000个ID)。

这需要在每个ID 100-200毫秒之间,在157000个ID的测试运行中花费7.3小时,表明~760小时或> 1个月的完整执行时间。底层机器出现CPU限制(没有显着的IO等待时间)。

查看EXPLAIN这个查询,没有完整扫描,它是所有模式索引匹配(一旦我包含了显式索引语句),所以我不知道在哪里寻找更快的速度..

(已编辑以添加此PROFILE输出):

PROFILE part 1 PROFILE part 2

这表明与邮政编码的两个部分的匹配过滤了很多行(56k),重新排序这些字段以减少过滤器输入大小可能更好。 (编辑结束)

作为(非常不公平)的比较,我将CSV文件中的两组数据推送到用C#/ .NET编写的自定义Bloom过滤器中,该过滤器执行与上面类似的字段重新格式化,然后连接以生成文本键,并匹配这些键一起。这完成了在我的笔记本电脑的单核心上,在5分钟内完成所有28M PAF记录的所有20M电子邮件记录。它主要是IO绑定。

现在我正在考虑使用外部应用程序或用户程序来执行记录匹配,并且只是使用Cypher创建关系,但是避免使用编写良好的查询引擎应该能够做到这一点感觉是错误的,比它快得多。

请问我应该考虑提高绩效?

1 个答案:

答案 0 :(得分:0)

如果我没记错的话,当比较值(例如UPPER()或LOWER()或TRIM())发生转换时,如果它们来自另一个节点属性,则索引将无法正确使用。您可能需要先执行这些操作并对它们进行别名,然后进行匹配。

我认为,提供索引提示可以解决这个问题,所以你对s.SBNA的匹配应该正确使用索引,但是如果m:MAINFILE上的任何匹配属性都有索引,则可能没有使用索引。

测试这是否有所不同,将此查询与较小数据集上的旧查询进行比较:

UNWIND {list} AS i
MATCH(e:DDSEMAIL) WHERE ID(e) = i 
WITH e, upper(e.Building) as SBNA
MATCH(s:SUBBNAME)
WHERE s.SBNA = SBNA 
WITH e,s, trim(split(e.Postcode,' ')[0]) as OUTC,
          trim(split(e.Postcode,' ')[1]) as INCO,
          right('0000'+e.HouseNo,4) as BNUM
MATCH(m:MAINFILE)
WHERE OUTC = m.OUTC AND
      INCO = m.INCO AND
      BNUM = m.BNUM AND
      (m)-[:IS_SUBBNAME]->(s)
CREATE (e)-[r:USED_AT]->(m)
RETURN COUNT(r);

此外,如果您可以将查询的PROFILE或EXPLAIN的屏幕截图添加到您的描述中(在展开所有计划节点之后),这可能有助于查看可以改进的地方。

修改

正如您在描述中所提到的,批处理这些可能是一个好主意。 APOC程序有apoc.periodic.iterate(),这可能对此有所帮助。

让我们看看我们是否可以将其应用于您的查询。试试这个:

WITH {list} AS list
CALL apoc.periodic.iterate('
 UNWIND {list} as list
 RETURN list
', '
 WITH {list} as i
 MATCH(e:DDSEMAIL) WHERE ID(e) = i 
 WITH e, upper(e.Building) as SBNA
 MATCH(s:SUBBNAME)
 WHERE s.SBNA = SBNA 
 WITH e,s, trim(split(e.Postcode,' ')[0]) as OUTC,
           trim(split(e.Postcode,' ')[1]) as INCO,
           right('0000'+e.HouseNo,4) as BNUM
 MATCH(m:MAINFILE)
 WHERE OUTC = m.OUTC AND
       INCO = m.INCO AND
       BNUM = m.BNUM AND
       (m)-[:IS_SUBBNAME]->(s)
 MERGE (e)-[:USED_AT]->(m)
', {batchSize:1000, iterateList:true, params:{list:list}}) YIELD batches, total, committedOperations, failedOperations, failedBatches, errorMessages
RETURN batches, total, committedOperations, failedOperations, failedBatches, errorMessages

但是,我们必须牺牲返回创建的关系总数,因为我们无法从批处理查询中返回值。

相关问题