【问题标题】:Is there a equivalent to commit in bulbs framework for neo4j在 neo4j 的灯泡框架中是否有等效的提交
【发布时间】:2013-05-21 12:13:18
【问题描述】:

我正在构建一个基于 neo4j 的数据密集型 Python 应用程序,出于性能原因,我需要在每个事务期间创建/恢复多个节点和关系。灯泡中是否有等效于 SQLAlchemy session.commit() 的语句?

编辑:

对于那些感兴趣的人,我们已经开发了一个 Bulb 的接口,它可以在本地实现该功能,否则功能非常类似于 SQLAlchemy: https://github.com/chefjerome/graphalchemy

【问题讨论】:

  • 您还可以查看 Neo4j REST-APIs batch-rest-operation 模式,它在一个事务中执行多个命令,或者新的事务性 http 端点,它允许跨多个 http 请求使用事务,请参阅 @987654322 @
  • 感谢您的回复,但它对我不起作用。我已经用原生的 neo4j python api 实现了一个相当不错的跨国批量插入(你可以看看它here)。问题是数据库很可能会增长到超过 32 个 10^9 节点(生物信息学),我希望构建一些可以稍后移植到 Titan GraphDB 的东西。

标签: python neo4j bulbs


【解决方案1】:

执行多部分事务的最高效方式是将事务封装在 Gremlin 脚本中,并将其作为单个请求执行。

这是一个如何做到这一点的示例——它来自我去年为 Neo4j Heroku Challenge 开发的一个示例应用程序。

项目名为Lightbulb:https://github.com/espeed/lightbulb

自述文件描述了它的作用......

什么是灯泡?

Lightbulb 是一个基于 Git 的、由 Neo4j 支持的 Heroku 博客引擎 用 Python 编写。

您可以在 Emacs(或您最喜欢的文本编辑器)中编写博客条目 并使用 Git 进行版本控制,而不放弃 动态应用。

在 ReStructuredText 中编写博客条目,并使用您的 网站的模板系统。

当您推送到 Heroku 时,条目元数据将自动 保存到 Neo4j,以及从 ReStructuredText 源文件将从磁盘提供。

但是,Neo4j 停止在其免费/测试 Heroku Add On 上提供 Gremlin,因此 Lightbulb 将不适用于 Neo4j/Heroku 新用户。

在明年之内——在TinkerPop book 发布之前——TinkerPop 将发布一个 Rexster Heroku Add On,它完全支持 Gremlin,这样人们就可以在阅读本书的同时在 Heroku 上运行他们的项目。

但现在,您无需担心运行应用程序——所有相关代码都包含在这两个文件中——Lightbulb 应用程序的模型文件及其 Gremlin 脚本文件:

https://github.com/espeed/lightbulb/blob/master/lightbulb/model.py https://github.com/espeed/lightbulb/blob/master/lightbulb/gremlin.groovy

model.py 提供了一个用于构建自定义灯泡模型和自定义灯泡Graph 类的示例。

gremlin.groovy 包含自定义 Entry 模型执行的自定义 Gremlin 脚本 -- 这个 Gremlin 脚本封装了整个多部分事务,以便它可以作为单个请求执行。

注意在上面的model.py 文件中,我通过重写create()update() 方法来自定义EntryProxy,而是定义一个单一的save() 方法来处理创建和更新。

要将自定义 EntryProxy 挂钩到 Entry 模型中,我只需重写 Entry 模型的 get_proxy_class 方法,以便它返回 EntryProxy 类而不是默认的 NodeProxy 类。

Entry 模型中的所有其他内容都是围绕为save_blog_entry Gremlin 脚本(在上面的 gremlin.groovy 文件中定义)构建数据而设计的。

请注意 gremlin.groovy 中的 save_blog_entry() 方法很长并且包含多个闭包。您可以将每个闭包定义为一个独立的方法并使用多个 Python 调用来执行它们,但是您会产生发出多个服务器请求的开销,并且由于请求是分开的,因此无法将它们全部包装在事务中。

通过使用单个 Gremlin 脚本,您可以将所有内容组合到单个事务请求中。这要快得多,而且是事务性的。

可以在 Gremlin 方法的最后一行看到整个脚本是如何执行的:

return transaction(save_blog_entry);

在这里,我只是在内部save_blog_entry 闭包中的所有命令周围包装了一个事务闭包。使事务闭包保持代码隔离,并且比将事务逻辑嵌入到其他闭包中要干净得多。

然后,如果您查看内部 save_blog_entry 闭包中的代码,它只是调用我在上面定义的其他闭包,使用我在 Entry 模型中调用脚本时从 Python 传入的参数:

def _save(self, _data, kwds):
    script = self._client.scripts.get('save_blog_entry')
    params = self._get_params(_data, kwds)
    result = self._client.gremlin(script, params).one() 

我传入的参数是在模型的自定义_get_parms()方法中建立起来的:

def _get_params(self, _data, kwds):
    params = dict()

    # Get the property data, regardless of how it was entered
    data = build_data(_data, kwds)

    # Author
    author = data.pop('author')
    params['author_id'] = cache.get("username:%s" % author)

    # Topic Tags
    tags = (tag.strip() for tag in data.pop('tags').split(','))
    topic_bundles = []
    for topic_name in tags:
        #slug = slugify(topic_name)
        bundle = Topic(self._client).get_bundle(name=topic_name)
        topic_bundles.append(bundle)
    params['topic_bundles'] = topic_bundles


    # Entry
    # clean off any extra kwds that aren't defined as an Entry Property
    desired_keys = self.get_property_keys()
    data = extract(desired_keys, data)
    params['entry_bundle'] = self.get_bundle(data)

    return params

这是_get_params() 在做什么...

buld_data(_data, kwds)bulbs.element中定义的函数: https://github.com/espeed/bulbs/blob/master/bulbs/element.py#L959

它只是合并参数,以防用户输入一些作为位置参数和一些作为关键字参数。

我传入_get_params()的第一个参数是author,也就是作者的用户名,但是我没有把用户名传给Gremlin脚本,我传的是author_idauthor_id 已缓存,因此我使用用户名查找 author_id 并将其设置为参数,稍后我将其传递给 Gremlin save_blog_entry 脚本。

然后我为设置的每个博客标签创建TopicModel 对象,并在每个对象上调用get_bundle(),并将它们保存为参数中的topic_bundles 列表。

get_bundle() 方法在bulbs.model 中定义: https://github.com/espeed/bulbs/blob/master/bulbs/model.py#L363

它只返回一个元组,其中包含模型实例的dataindex_name 和索引keys

def get_bundle(self, _data=None, **kwds):
    """
    Returns a tuple containing the property data, index name, and index keys.

    :param _data: Data that was passed in via a dict.
    :type _data: dict

    :param kwds: Data that was passed in via name/value pairs.
    :type kwds: dict

    :rtype: tuple

    """
    self._set_property_defaults()   
    self._set_keyword_attributes(_data, kwds)
    data = self._get_property_data()
    index_name = self.get_index_name(self._client.config)
    keys = self.get_index_keys()
    return data, index_name, keys

我向 Bulbs 添加了 get_bundle() 方法,以提供一种将参数捆绑在一起的漂亮而整洁的方式,这样您的 Gremlin 脚本就不会在其签名中被大量 args 所淹没。

最后,对于Entry,我只需创建一个entry_bundle 并将其存储为参数。

请注意,_get_params() 返回三个参数的 dictauthor_idtopic_bundleentry_bundle

这个params dict 直接传递给Gremlin 脚本:

def _save(self, _data, kwds):
    script = self._client.scripts.get('save_blog_entry')
    params = self._get_params(_data, kwds)
    result = self._client.gremlin(script, params).one()        
    self._initialize(result)

Gremlin 脚本与params 传入的参数名称相同:

def save_blog_entry(entry_bundle, author_id, topic_bundles) {

   // Gremlin code omitted for brevity 

}

然后根据需要在 Gremlin 脚本中简单地使用这些参数 - 没什么特别的。

现在我已经创建了我的自定义模型和 Gremlin 脚本,我构建了一个自定义 Graph 对象,它封装了所有代理和相应的模型:

class Graph(Neo4jGraph):

    def __init__(self, config=None):
        super(Graph, self).__init__(config)

        # Node Proxies
        self.people = self.build_proxy(Person)
        self.entries = self.build_proxy(Entry)
        self.topics = self.build_proxy(Topic)

        # Relationship Proxies
        self.tagged = self.build_proxy(Tagged)
        self.author = self.build_proxy(Author)

        # Add our custom Gremlin-Groovy scripts
        scripts_file = get_file_path(__file__, "gremlin.groovy")
        self.scripts.update(scripts_file)

您现在可以直接从应用的model.py 导入Graph 并像往常一样实例化Graph 对象。

>> from lightbulb.model import Graph  
>> g = Graph()
>> data = dict(username='espeed',tags=['gremlin','bulbs'],docid='42',title="Test")
>> g.entries.save(data)         # execute transaction via Gremlin script

这有帮助吗?

【讨论】:

  • 感谢您的回复,这实际上很酷,即使我认为我可以暂时避开 Gremlin。我将尝试实现与您类似的方法,这实际上对我来说可能是最简单的事情。
  • Gremlin 非常酷,而且不需要那么长时间就能掌握它。上面的 Gremlin 示例在 Gremlin-Groovy(原版)中,这是大多数人使用的。而 Gremlin 是 Titan 使用的,所以如果你最终要像上面指出的那样迁移到它,那么从 Gremlin 开始会让事情变得简单。
  • James,您介意再解释一下您在 Gremlin 脚本中所做的工作吗?我很难理解为什么在“create_or_update_vertex”和“get_and_create_vertex”中需要“index_key”参数
  • 嗨 Andrei - 在 Neo4j 中,属性值不能保证在所有顶点中都是唯一的。 Bulbs 为每个模型创建一个单独的索引,默认情况下所有属性都被索引。 create_or_update_vertexget_and_create_vertex 闭包在模型索引中查找属性名称以查看它是否存在。属性名称 (index_key) 被用作模型索引的唯一主键。如果 index_key/value 对存在顶点,这两个闭包会更新或返回顶点,否则它们会创建顶点。请注意,我将所有这些都包装在事务关闭中。
猜你喜欢
  • 2013-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-11
  • 2010-09-30
相关资源
最近更新 更多