2

我正在寻找一些关于如何将应用程序代码重写为非幼稚的一般建议,或者是否放弃 neo4j 以使用另一种数据存储模型。这不仅是“主观的”,因为它与 Python 中 neo4j 驱动程序的具体、正确使用以及为什么它执行我的代码的方式密切相关。

背景:

我和我的团队一直在使用 neo4j 来存储最初存储在 Python 对象中的图形友好数据。最初,当地/内部专家建议我们使用 neo4j,因为它似乎符合我们的数据存储和操作/查询要求。数据始终是一组精心构建的本体的特定实例。例如(伪数据):

Superclass1 -contains-> SubclassA
Superclass1 -implements->SubclassB
Superclass1 -isAssociatedWith-> Superclass2
SubclassB -hasColor-> Color1
Color1 -hasLabel-> string::"Red"

...等等,以创建一些相当复杂和冗长的层次结构。

对于原型设计,我们使用 RDFLib 将这些数据存储为语法三元组(主语->动词/谓词->宾语)的序列,并使用 RDFLib 的图形生成器构建图形。

现在,由于这些信息只是一个复杂的层次结构,我们只需将其存储在一些自定义 Python 对象中。我们这样做也是为了向需要与我们的核心服务交互的其他开发人员提供一个简单的 API。我们给他们一个 Python 库,它是我们的 Object 模型,让他们用数据填充它,或者,我们填充它并将它交给他们以便于阅读,他们用它做他们想做的事。

为了永久存储这些对象,并希望加速这些数据的写入和读取(查询/过滤),我们构建了自定义对象映射代码,该代码利用官方 neo4j python 驱动程序以递归方式写入和读取这些 Python 对象,以/来自neo4j 数据库。

问题:

对于大型和复杂的数据集(例如 15k+ 节点和 15k+ 关系),我们代码的对象关系映射 (ORM) 部分太慢,并且扩展性很差。但我和我的同事都不是数据库或 neo4j 方面的专家。我认为我们对如何完成这个 ORM 太天真了。我们开始怀疑使用 neo4j 是否有意义,而更传统的 ORM(例如 SQL Alchemy)可能只是更好的选择。

例如,我们现在拥有的 ORM 提交算法是一个递归函数,它可以像这样提交一个对象(伪代码):

def commit(object):
    for childstr in object:             # For each child object
        child = getattr(object, childstr)   # Get the actual object

        if attribute is <our object base type): # Open transaction, make nodes and relationship
            with session.begin_transaction() as tx:
                <construct Cypher query with:
                MERGE object            (make object node)
                MERGE child             (make its child node)
                MERGE object-[]->child  (create relation)
                >
                tx.run(<All 3 merges>)

            commit(child)                   # Recursively write the child and its children to neo4j

这样做是不是很幼稚?像Py2neo 的 OGM这样的 OGM 库会更好,尽管我们是定制的吗?我已经看到推荐这个或那个 OGM 方法的这个和类似的问题,但是在这篇文章中,它说根本不要使用 OGM。

我们真的必须实现每一种方法和性能基准吗?似乎必须有一些最佳实践(除了使用批处理 IMPORT,这不适合我们的用例)。我们已经阅读了类似链接的文章,并看到了编写更好查询的各种技巧,但在尝试逐行优化代码之前,最好退后一步,更一般地检查案例。虽然很明显我们可以在一定程度上改进 ORM 算法。

使用像这样的递归策略向 Neo4j 写入和读取大而深的分层对象是否有意义?Cypher 中有什么东西,或者我们缺少的 neo4j 驱动程序吗?还是使用 Py2neo 的 OGM 之类的东西更好?最好完全放弃neo4j吗?neo4j 和 Cypher 的优势不容忽视,而且我们的数据似乎与图表非常吻合。谢谢。

4

2 回答 2

2

这里发生了很多事情,所以我会尝试在较小的问题中解决这个问题

像 Py2neo 的 OGM 这样的 OGM 库会更好吗

对于任何 ORM/OGM 库,现实情况是,您总是可以通过绕过它们并深入研究野兽的腹部来获得更好的性能。不过,这并不是 ORM 的全部工作。ORM 旨在通过简化相对高效的 DB 使用来节省您的时间和精力。

所以这取决于,如果你想要最好的性能,跳过 ORM,把你的时间花在尽可能低的水平上(*需要你正在使用的野兽的高级低级知识,以及你的大量时间)。否则,ORM 库通常是您最好的选择。

我们的代码太慢了,而且扩展性很差

数据库很复杂。如果可能的话,我建议让某人成为公司范围内的数据库管理员/专家。(当你还没有一个审查新员工实际上知道他们在说什么的时候,这就更难了)

假设这不是一种选择,这里有一些事情需要考虑。

  • IO很贵。尤其是通过网络。最小化必须在任一方向发送的数据。(这就是页面返回结果的原因。只返回您实际需要的数据)
    • 需要注意的是,创建请求连接非常昂贵。尽量减少对数据库的调用。(享受平衡这两者的乐趣^_^)(注意:ORM 通常具有内置机制,只提交已更改的内容)
  • 快速获取您想要的数据。在数据库中创建索引以大大提高获取速度。id 越独特和一致越好。
    • 需要注意的是,索引必须在更改其中值的写入时更新。因此索引会降低写入速度并消耗更多内存来获得读取速度。最小化索引。
  • 事务是一种内存操作。提交事务是磁盘 IO 操作。这就是批处理作业效率更高的原因。
    • 警告,内存不是无限的。保持您的工作规模合理。

正如您可能知道的那样,将数据库操作扩展到生产级别并不有趣。在任何轴上过度优化都太容易烧伤自己,而这只是表面上的简化。

对于原型设计,我们将这些数据存储为语法三元组序列

少一个问题,多一个陈述,但不同类型的数据库有不同的优势和劣势。无方案的数据库更专门用于缓存存储;图数据库专门用于基于关系(边)的查询;关系数据库专门用于获取/更新记录(表);Triplestores 更专业于三元组 (RDF);(等等。还有更多类型)

我提到这一点是因为听起来您的数据可能主要是“一次写入,多次读取”。在这种情况下,您可能实际上应该使用 Triplestore。您可以将任何数据库类型用于任何事情,但选择最佳数据库需要您了解如何使用数据,以及这种使用可能如何演变。

我们真的必须实现每一种方法和性能基准吗?

嗯,这就是为什么存储过程如此重要的部分原因。ORM 有助于抽象这部分,并且拥有内部领域专家会非常有帮助。可能只是您正在突破 1 台机器所能做的极限。也许您只需要升级到集群;或者您可能有可怕的代码效率低下,当没有(或 1 个)值更改时,您在 1 次保存操作中触摸节点 10k 次。不过老实说,除非您知道自己在寻找什么,否则基准测试并没有多大作用。例如,通常 5 小时和 0.5 秒之间的差异可能就像创建 1 个索引一样简单。

(公平地说,虽然购买更大更好的数据库服务器/集群可能是低效的解决方案,但与 1 个数据库管理员的薪水相比,它有时是最具成本效益的。所以,再次取决于你的优先级。我敢肯定你的老板可能会优先考虑与你想要的不同)


TL;博士

您应该聘请领域专家来帮助您。

如果这不是一个选项,去书店(或谷歌)拿起 Databases 4 傻瓜(动手学习数据库在线教程课程),并自己成为领域专家。(你可以用它来提高你对公司的价值)

如果您没有时间,那么您唯一的节省可能就是升级您的硬件以使用蛮力解决问题。(*只要增长不是指数级的)

于 2018-07-03T21:33:05.010 回答
2

如果不查看所有代码并了解类层次结构,很难知道,但目前我冒险猜测您的代码在 OGM 位中很慢,因为每个关系都是在其自己的事务中创建的。因此,您正在为更大的图表执行大量事务,这会减慢速度。

我建议您在创建每个类/对象时进行初始导入,而不是仅仅添加一个新类/对象或编辑一个类的关系,而是使用类检查器来简单地创建数据的图形表示,并且然后使用 Cypher 在 Neo4J 中以更少的事务构建它。使用一些基本的拓扑图论,您也可以通过减少需要执行的查找次数来优化它。

您可以在 Python 代码中创建NetworkX MultiDiGraph来对类的结构进行建模。从那里开始,有几种不同的策略可以将数据放入 Neo4J - 我也刚刚发现了这个,但不知道它是否有效或效率如何。

查询以导入图表的最有效方式将取决于图表的拓扑结构,以及它是否是循环的。一些选项如下。

1. 在两组查询中创建图形

为每个节点标签运行一个查询以创建每个节点,然后另一个以在每个节点标签组合之间创建每条边(这样做的效率将取决于您使用的不同节点标签的数量)。

2. 从图中的拓扑最高点或最低点开始,将图创建为一系列路径

如果您有很多不同的边缘标签和节点标签,这可能涉及编写大量的密码逻辑组合UNWINDFOREACH (CASE r.label = 'SomeLabel' THEN [1] ELSE [] | CREATE (n:SomeLabel {node_unique_id: x})->,但如果图是非常分层的,您还可以使用 python 来跟踪哪些节点创建了所有较低的节点和关系然后使用该知识来限制在查询中发送到 Neo4J 的路径的大小。

3.使用APOC导入全图

另一种选择,可能适合也可能不适合您的用例,可能会或可能不会更高效,是使用 NetworkX 将图形导出到 GraphML,然后使用 APOC GraphML 导入工具

同样,如果不查看所有数据,很难提供精确的解决方案,但我希望这对引导正确方向有所帮助!很高兴根据更多数据帮助/回答任何其他问题。

于 2018-06-21T15:36:10.010 回答