在亚马逊 Neptune 中探索 Apache TinkerPop 3.6.x 的新功能

作者: 斯蒂芬·马莱特 | 202

亚马逊 海王星版本1.2.1.0现在支持 A pache TinkerPop 3.6.x发行系列,该系列提供了许多主要的新功能和对现有功能的改进。新功能包括对 Gremlin 语言本身的新增功能,例如过滤器的 p.regex 谓词以及 merge V () 和 merGe e () 步骤,它们应该有助于简化类似 upser t 的复杂功能。

在这篇文章中,我们向您概述了与海王星有关的最关键和最有趣的更改,这将帮助您了解升级和使用此新版本的影响。

全新 Gremlin 语法

自从 TinkerPop 像在 3.6.x 中一样扩展 Gremlin 语言以来,已经有很长时间了。以下各节概述了这些新增内容。这些部分中显示的示例使用了受 航线数据集启发的小型人造数据集 。除非另有说明,否则所有示例均使用Groovy编写。

mergeV () 和 merGee ()

升级到 TinkerPop 3.6.x 的最有说服力的理由之一是引入了 mer geV () 和 merGee () 步骤 如前所述,这些步骤解开了 fold () .coalesce(unfold (),...)的复杂性 在进行图形突变时使用的模式,这些变更需要顶点和边缘的类似 upsert 的功能。由于 Fold-Coalesce-unfold 在 TinkerPop 中已经规定了多年,在帖子和示例中多次进行了记录,并且在许多方面进行了改进(批量加载、流式传输用例等),因此,如果你在代码中修改图形数据,你很可能会在某个地方使用这种模式。值得你花时间确定你在哪里使用它,并尽可能地重构这些新步骤。它将使你的代码更具可读性,并允许海王星在某些情况下更好地优化突变性能。

mergeV () 为例 ,考虑使用折叠合并展开模式来向上插入 Vertex:

gremlin> g.V().has('airport','code','ATL').
......1>   fold().
......2>   coalesce(unfold(),
......3>            addV('airport').property(id, '215').property('code','ATL')).
......4>   valueMap(true)
==>{code=[ATL], id=215, label=airport}

以在 3.6.x 中使用 mergeV () 的同一个例子 为例:

gremlin> g.mergeV([(T.label): 'airport', (T.id): '215', code: 'ATL']).
......1>   valueMap(true)
==>{code=[ATL], id=215, label=airport}

使用 Map 作为 mergeV () 的参数 很方便,因为它是应用程序使用的一种常见通用表单。可能不熟悉 Gremlin 的人不必识别步骤模式及其交互作用即可理解代码,而且所有人的可读性都大大提高。

merGee () 步骤在可读性方面提供了更为显著的改进。例如,假设除了之前添加的 ATL 顶点之外再增加 一个 机场 顶点:

gremlin> g.addV('airport').property(id, '203').property('code','AUS')
==>v[203]

使用 fold-Coalesce-unfold 向上插入从 ATL AUS 路线 边缘看起来像下面的代码:

gremlin> g.V().has('airport','code','ATL').as('v').
......1>   V().has('airport','code','AUS').
......2>   coalesce(inE('route').where(outV().as('v')),
......3>   addE('route').from('v').property(id, '219').property('dist',813)).
......4>   elementMap()
==>{id=219, label=route, IN={id=203, label=airport}, OUT={id=215, label=airport}, dist=813}

在 3.6.x 版本的 merGee () 中,前面的 Gremlin 简化为以下内容:

gremlin> g.V().has('airport','code','ATL').
......1>   mergeE([(T.label): 'route', (T.id): "219", (to): "203", dist: 813]).
......2>   elementMap()
==>{id=219, label=route, IN={id=203, label=airport}, OUT={id=215, label=airport}, dist=813}

请注意, mergeV () merGee () 旨在涵盖尽可能广泛 的 upsert 用例,而不会使步骤使用变得过于复杂,从而影响直观感。因此,你可能会发现这些步骤不能完美替代折叠合并展开的情况,因此可能需要保留旧模式。Fold-Coalesce-Unfold 仍然是一种有用的 Gremlin 模式,鉴于这些新步骤的出现,不应将其解释为值得避免使用的东西。

此处提供的示例很简单。 有关更多详细信息以及 mergeV () 和 merGee () 的具体参考文档,请参见 mergeV () 和 merGee () 随着这些步骤在现实应用程序中的使用量不断增加,看看它们将如何被使用以及会出现哪些新模式将很有趣。正如 fold-Coalesce-unfold 通过组合三个步骤的交互作用来满足常见用例将自己定义为一种模式一样,以 mergeV () 和 merGe e () 组合为特色的复杂新模式可能会出现在 G rem lin 工具箱中。

元素 ()

elem ent () 步骤允许您从属性遍历回其父元素( 顶点 、 边缘 或 vertex Prop er ty):

gremlin> g.V().properties('code').as('p').
......1>   element().out().as('v').
......2>   select('p','v')
==>{p=vp[code->AUS], v=v[2]}
==>{p=vp[code->AUS], v=v[5]}
==>{p=vp[code->DFW], v=v[3]}
==>{p=vp[code->DFW], v=v[4]}
==>{p=vp[code->LAX], v=v[1]}
==>{p=vp[code->LAX], v=v[2]}
==>{p=vp[code->LAX], v=v[4]}
==>{p=vp[code->ATL], v=v[2]}
==>{p=vp[code->ATL], v=v[4]}

此步骤提供了很大的便利,因为它使您在需要遍历属性时不必跟踪父元素,从而获得更具可读性的 Gremlin 查询。

失败 ()

fai l () 步骤提供了一种在 Gremlin 的特定分支运行时停止遍历的方法。如果你有一个用例,你正在检测一个使用 常量 () 运行的分支 ,这可能最好以异常结尾并停止遍历,那么你可能希望重构该分支以改用 fai l () 。 参见以下代码:

gremlin> g.V().choose(has('code',startingWith('A')), values('code'), constant('Not Starting with A'))
==>AUS
==>Not Starting with A
==>Not Starting with A
==>Not Starting with A
==>ATL
gremlin> g.V().choose(has('code',startingWith('A')), values('code'), fail('Not Starting with A'))
{"requestId":"2de62d83-2fc4-407b-b524-c0d71ba28309","code":"InternalFailureException","detailedMessage":"Exception processing a script on request [RequestMessage{, requestId=2de62d83-2fc4-407b-b524-c0d71ba28309, op='eval', processor='', args={gremlin=g.V().choose(has('code',startingWith('A')), values('code'), fail('Not Starting with A')), userAgent=Gremlin Console/3.5.4, batchSize=64}}]."}
Type ':help' or ':h' for help.
Display stack trace? [yN]n

textp.regex ()

te xTP 上的 regex () 选项 允许构造基于正则表达式构建的谓词,这为筛选字符串值提供了极大的灵活性:

gremlin> g.V().values('code')
==>AUS
==>DFW
==>LAX
==>JFK
==>ATL
gremlin> g.V().has('code', regex('A')).values('code')
==>AUS
==>LAX
==>ATL
gremlin> g.V().has('code', regex('^A')).values('code')
==>AUS
==>ATL
gremlin> g.V().has('code', regex('^A|J')).values('code')
==>AUS
==>JFK
==>ATL

财产(地图)

使用 property () 步骤向元素添加多个 属性 包括将它们一个接一个地链接起来,为每个添加的键值对调用一次:

gremlin> g.addV('airport').property(id, '203').property('code','AUS')
==>v[203]

当然,来自应用程序的数据通常以 Map 的形式出现 ,这意味着你必须在代码中循环 或在 Gremlin 本身中展开 地图 。TinkerPop 3.6.x 为 属性 () 步骤提供了一个新的重载,它将直接生成 地图 ,从而为你节省这个添加的步骤:

gremlin> g.addV('airport').property([(T.id): '203', code: 'AUS'])
==>v[203]

by () 的行为

在 Gremlin 中,b y () 调制器与 Gremlin 中的各种不同步骤一起使用,以帮助配置其他选项。在这些步骤中, 由 () 触发的行为差异很大,当由于给出的参数而引发异常时,通常会引起相当大的混乱。在 3.6.x 中,by () 触发的行为具有 更好的一致性。如果 by () 生成结果,则该结果将在父步骤中使用。如果情况恰恰相反,则对其应用的遍历器进行过滤。

很难说升级时你的 Gremlin 查询需要进行哪些更改,因为你的查询语义可能利用了旧的行为,也可能没有利用过时的行为。如果你的应用程序以某种方式依赖异常行为 来实现 failed by () ,那么在升级时,这可能是你首先要考虑的代码区域。考虑以下 Java 代码中的示例,其中某些顶点可能没有 out () 边:

List l;
try {
   l = g.V().aggregate("a").by(out()).cap("a").toList();
} catch (Exception ex) {
   // The provided traverser does not map to a value: v[2]->[VertexStep(OUT,vertex)]
   l = g.V().aggregate("a").by(in()).cap("a").toList();
}

如果你的代码看起来像前面的示例,这意味着你依赖于 b y () 引发的异常,而 该异常并不能在给定的所有情况下生成结果,则需要在升级后进行一些调整,因为该异常将不再被抛出。当然,这样的案例很少而且相差甚远,因为在开发过程中通常会遇到这种异常。通常的做法是解释查询本身 中存在 的 by () 问题,而不是使用异常作为修改查询行为流程的开关。无论如何,值得仔细检查您的代码和查询结果,以确保升级后一切都按预期运行。

考虑到使用 b y () 的步骤数量 ,在这篇文章中全部研究它们有点过分了。有关所有受影响步骤的示例,请参阅 Consid ent by () 行为

gremlin-驱动程序和主机可用性

在使用 Java gremlin-driver 连接海王星时 ,你可能在某个时候遇到 nohostaVailableException。 这是一个例外,本质上意味着驱动程序中配置的所有可用主机都无法访问,并且在驱动程序检测到工作主机之前,进一步的请求将导致同样的异常。 在开发3.5.5和3.6.2期间,在研究 NohostaVailableEx ception的性质运作时,注意到该驱动程序在评估主机运行状况时有点悲观,因此,驱动程序陷入了本可以发送请求但却生成了NohostavailableException的状态。

对于 3.6.2 和 3.5.5,对驱动程序进行了修改,以提供更乐观的策略来确定主机可用性。现在,驱动程序更加怀疑地看待从池中借用连接时出现的错误,并在放弃主机之前提供更即时、更可靠的重试。对服务器保持在线状态更加乐观有助于使驱动程序更能抵御临时故障。还值得注意的是,驱动程序的日志记录得到了极大的改进,这应该有助于在出现问题时更轻松地将其隔离。要阅读有关更改确切性质的更多详细信息,请参阅 gremlin- driver 主机可用性。

使用 Java 驱动程序的 Neptune 用户一定要根据你正在使用的 Neptune 版本寻找机会升级到这两个版本 中的 任何一个。

编译和依赖关系

如果从 TinkerPop 的早期版本升级,3.6.x 中有许多更改可能会在迁移到此版本时造成编译问题。幸运的是,这些问题应该很容易解决。

  • Java Gremlin DSL 依赖关系 — 如果你已经用 J ava 开发了 Gremlin DSL ,请注意,支持此功能的库已不存在于 gremlin-core 模块中 它们已被提取到 gremlin -annotations 模块中。在此重构中保留了包名和类名,因此只需添加 gremlin-annotations 依赖关系即可解决任何编译问题
  • 移除 Groovy 依赖关系 — 在 3.5.x 版本中声明的 gremlin-dri ver 中对 Groovy 的依赖关系已完全删除 ,对 JsonBuilder 序列化的支持已 在 3.5.2 中被弃用。
  • 移除对 Gryo 的支持 — 所有 Gryo MessageSerializer 实现在 3.4.3 版被弃用后都已被删除。现在,您应该更喜欢 GraphBinary 来满足网络序列化需求。除非你是从非常旧的 gremlin-dri ver 版本升级 或者有 Gryo 的特定配置,否则在与 Neptune 通信时,你可能已经在使用 Gr aph Binary 了。在 Gryo 配置为 与 Neptune 1.2.1.0 通信的情况下,你将无法使用旧版本的 g remlin-driv er。尝试这样做会导致客户端出现错误。
  • 默认设置 GraphBinar y — GraphBinary 序列化现在是所有官方支持的编程语言的默认序列化选项。仍然支持 GraphSon 3.0 序列化,但不建议使用。
  • GroovyTranslator — GroovyTranslator ,一个旨在将 Gremlin 遍历转换为 字符串 形式的类,现已从 gremlin-groovy 模块中移 出。 如果你使用这个类,你应该放弃对 gremlin-g roovy 的依赖 (假设你没有其他用途),只需要依靠 gremlin-core 这个类现在可以在 org.apache.tinkerpop.gremlin.pro cess.translator 包中找到。
  • Python 中的步骤命名 — 以下步骤使用标准下划线后缀进行了重命名,以避免与某些 Python 关键字发生冲突: filter_ () 、 id_ () 、 max_ () 、m in_ () 、 range_ () 和 sum_ ()。 在升级期间引用这些步骤需要对这些步骤进行重命名或以其他方式设置别名。 另请注意,gremlinpython 的驼峰式步长现在有更多的 Pythonic 等效项(例如,valueMap ( ) 现在也有了更友好的 value_map () 命名)。 尽管旧的驼峰命名仍然存在,但建议你更喜欢更像Pythonic的方法,因为将来可能会删除驼峰大小写的命名。

结论

TinkerPop 3.6.x 发行系列具有许多功能,海王星用户会很高兴拥有这些功能。与往常一样,请参阅 TinkerPop 升级文档 及其 变更日志 ,了解最新版本 3.6.2 之前所有更改的完整列表。请升级到 Neptune 1.2.1.0 以使用这些重要的新功能。


作者简介

Stephen Mallett e 是 亚马逊云科技 亚马逊 Neptune 团队的一员。他多年来一直开发图形数据库和图形处理技术。他为 Apache TinkerPop 项目做了长达十年的贡献,该项目是 Gremlin 图表查询语言的发源地。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。