View a markdown version of this page

标注属性图的池模型 - AWS 规范性指导

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

标注属性图的池模型

在 Amazon Neptune LPGs 上使用池模型有三种不同的方法:

  • 属性策略 − 当您需要优先使用已建立的库结构(例如 Apache TinkerPop Gremlin 语言的)而不是性能时,请选择属性策略。PartitionStrategy

  • Prefix-Label 策略 − 我们根据性能和限制噪音邻域效应推荐大多数场景使用前缀标签策略。

  • 多标签策略 − 多标签策略的性能与前缀标签策略相比有所提高。它还支持运行跨集群上所有租户的查询(例如,用于报告或监控所有租户的 ISV 查询)。

房地产策略

使用 LPGs,用户可以向节点、顶点和边添加键值对属性。为了实现逻辑分离,大多数客户直观地将其建模为每个节点和边缘的唯一属性,并使用公共租户属性密钥。租户属性密钥表示拥有该节点的所有租户。租户标识符是用于标识单个租户的唯一值。

下图显示了此模型。两个断开连接的子图具有不同的标记节点和边,租户属性键用TId表示。一个子图中的每个节点和边的TId1值为。在另一个子图中,每个节点和边的TId2值都为。

节点及其关系。

在带标签的属性图中,有两种方法可以对此进行管理。Gremlin 查询语言提供了PartitionStrategy遍历库来帮助管理数据的数据分区。以下示例中的代码期望每个节点和边都有一个名为的属性TId

strategy1 = new PartitionStrategy(partitionKey: "TId", writePartition: "1", readPartitions: ["1"]) strategy2 = new PartitionStrategy(partitionKey: "TId", writePartition: "2", readPartitions: ["2"])

写入新节点或边时,根据是否选择了"1""2",将该属性"TId"添加strategy2strategy1或的值。对于带有 of "TId" 的客户"1",您可以使用strategy1。以下示例显示了为该客户写入数据:

g.withStrategies(strategy1).addV("Label1").property("Value", "123456").property(id, "Item_1")

对于读取查询,分别使用"TId == '1'"strategy1或将过滤"TId == '2'"器添加到每个节点或边缘遍历strategy2中。这些分区策略可以简化您的代码,但它们不是必需的。使用该策略的好处是,它可以在授权级别注入,然后传递给构成查询的较低级别的代码。这将确定客户标识符 (TId) 的代码与查询逻辑分开。

以下示例代码显示了用于读取数据的 Gremlin 查询:

g.withStrategies(strategy1).V().hasLabel("Label1")

前面的代码等同于以下示例:

g.V().hasLabel("Label1").has("TId", "1")

同样,在使用 Gremlin 写入数据时,可以使用以下查询:

g.withStrategies(strategy1).addV("Label1").property("Value").property(id, "Item_1")

前面的代码等同于以下示例,后者不使用分区策略,因此需要显式写入"TId"属性:

g.addV("Label1").property("TId", "1").property("Value").property(id, "Item_1")

在 OpenCypher 中,这些库并不存在。您负责编写和修改查询,以将租户标识符作为属性添加到节点和边缘。例如:

CREATE (n:Item {`~id`: 'Item_1', Value: '123456', TId: '1'}) CREATE (n:Item {`~id`: 'Item_2', Value: '123456', TId: '2'})

请注意没有分区策略的 Gremlin 代码之间的相似之处。然后,您可以使用以下代码读取从第一条CREATE语句中写入的节点:

MATCH (n:Item {TId: '1'}) RETURN n --or MATCH (n:Item) WHERE n.TId == '1' RETURN n

当你想使用原生 TinkerPop Gremlin 构造时,你可以选择属性策略,例如。 PartitionStrategy但是,与前缀标签策略相比,该模型在Amazon Neptune上存在性能缺陷。有关这些性能缺点的讨论,请参阅液化石油气模型的性能影响部分。

如果满足以下条件,请考虑仅在节点上对属性策略进行建模,而不在边上建模:

  • 您的图表的边缘比标签多得多。

  • 每个租户都是一个断开连接的图表。

  • 您只能通过使用节点作为起点而不是标签来访问图表。

前缀标签策略

如果绩效是头等大事,我们强烈建议考虑使用前缀标签策略,而不是房地产策略。

在前缀标签策略中,您可以使用租户标识符和节点标签的组合来标记每个节点。例如,如果租户的标识符为,"1"而节点标签为"Label1",则将节点标签指定为"1-Label1"。下图显示了使用此模型的两个断开连接的子图。

带有包含前缀和节点关系的标签的节点。

在 Gremlin 中写入数据时,可以在任何节点的标签上添加标识号:

g.addV("1-Label1") g.addV("2-Label6")

查询此图表时,您可以检查节点上是否存在此前缀:

g.V().hasLabel("1-Label1")

在 OpenCypher 中,你可以使用以下语句写入数据:CREATE

CREATE (n:`1-Label1` {`~id`: 'Item_1', Value: 'XYZ123456'})

要查询你在 OpenCypher 中写入的数据,请使用以下代码:

MATCH n= (:`1-Label1`) RETURN n

前缀标签策略假设所有节点都分配给一个或多个租户,并且不在边缘范围内分配权限。避免在边缘标签上使用此策略,因为这会导致大量谓词,并会对 Neptune 性能产生负面影响。

前缀标签方法有两个主要缺点。首先,很难运行跨租户的任何查询。例如,对给定标签的所有节点进行计数以进行报告或监控的查询。如果这是您的用例,请考虑将此策略与多标签策略相结合。有关组合策略的更多信息,请参阅混合模型部分。

其次,前缀标签策略要求控制措施强制在每个查询中正确应用适当的前缀,以防止数据泄露。但是,对于需要低延迟查询的工作负载,这种策略是最有效的选择,我们强烈推荐使用这种策略。液化石油气模型的性能影响部分提供了一些示例,说明为什么这是最有效的策略。

多标签策略

第三种选择是使用多标签策略。对于这种方法,您可以为图表上的每个节点添加额外的标签。例如,如果您需要筛选给定租户的所有数据,请添加租户 ID 标签。 如果您需要筛选给定标签的所有数据,而不考虑租户,请添加该标签。下图显示了通过为每个节点使用三个标签所应用的多标签策略。

现在,您可以使用三种不同的模式来访问图表:

节点及其关系,其中每个节点都有 LabelX、X、x-labelX。
  • 筛选Label1以返回所有租户中的Label1所有节点。

  • 筛选1以返回租户 1 的所有节点。

  • 筛选后仅1-Label1返回带有标签的租户 1 的所有节点Label1

因为 LPGs,有两种方法可以实现这一点。

在 Gremlin 中,您可以使用名为的遍历策略SubgraphStrategy将所有查询的范围限制为仅具有特定标签的顶点,例如:"Label1"

g.withStrategies( new SubgraphStrategy( vertices=hasLabel("Label1") ) )

不同的是 PartitionStrategy,只 SubgraphStrategy 影响读取数据,不影响写入数据。要写入数据,请在每个查询中手动分配标签:

g.addV("Label1").property("Value","XYZ123456") .addV("Label2").property("Value","XYZ123456")

读取数据时,您可以使用以下 SubgraphStrategy 命令查询所有节点"Label1"

g.withStrategies( new SubgraphStrategy(vertices=.hasLabel("Label1")) ). V().has("Value","XYZ123456")

Neptune 仅返回第一条记录"Label1",其值为。"XYZ123456"它等同于以下查询,但该查询不使用 SubgraphStrategy:

g.V().hasLabel("Label1").hasValue("XYZ123456")

在这个基本查询中,使用 SubgraphStrategy 起来似乎更复杂。请记住,您的库可以提供已定义策略g的实例。开发人员不必确保应用了正确的过滤器:

def getGraphTraversal(): return g.withStrategies(new SubgraphStrategy(vertices=.hasLabel("Label1")) getGraphTraversal().has("Value","XYZ123456")

OpenCypher 库没有这些结构,因此您必须为每个节点创建多个标签:

CREATE (n:`1`:`Label1`:`1-Label1` {`~id`: 'Item_1', Value: '12345'})

当您使用这些标签筛选子图时,可以返回具有您正在寻找的客户标签的节点,或者返回与具有该标签的另一个节点共享关系的节点:

MATCH n=(:Label1:`1`) // or MATCH n=(:`1-Label1`)

多标签策略为您提供了最大的灵活性,可以按类型 (Label1) 或租户 (1) 查询节点,或者在性能最重要时使用更有效的前缀标签策略 ()。1-Label1

这种策略的主要缺点是,每个标签都是存储在图表中的额外对象。对象是节点或边上的节点、边或属性 LPGs。摄取速度由每秒对象数来衡量和约束,存储成本取决于消耗的千兆字节数。这意味着额外的物体可能会在大规模上产生可衡量的影响。

对液化石油气模型的性能影响

AWS 技能生成器课程 Amazon Neptune 的数据建模深入描述了 Neptune 数据模型的内部结构和建模含义,但我们将在这里总结这些设计的重要注意事项。考虑在单个 Neptune 集群上有三个租户(T1、T2、T3)。这些租户具有以下属性:

  • 租户 1 (T1) 总共有 1 亿个节点,其中 1000 万个节点属于 Item 类型。

  • 租户 2 (T2) 总共有 1000 万个节点,其中 100 万个节点属于 Item 类型。

  • 租户 3 (T3) 总共有 1 亿个节点,其中 100 万个节点属于 Item 类型。

运行查询,使用属性策略为租户 3 检索物品。Neptune 会检查两个索引调用的统计信息:

  • 哪里tenant property key=T3有 1 亿个结果

  • 哪里label = Item有 1200 万个结果(来自 T1 的 1000 万个 + 来自 T2 的 100 万个 + 来自 T3 的 100 万个)

Neptune 查询优化器确定最好先应用后一个查询(1200 万个结果),然后检查每个项目。tenant property key=T3您检索了 1200 万个项目以查找 100 万个结果。

请注意此查询对邻居的噪音影响。如果每个租户有 1 亿个 Item 节点,则第一个查询将有 3 亿个结果,而不是 1200 万个结果(出于说明目的,这过于简化。 Neptune 优化器可能应用了不同的操作顺序)。

接下来,考虑前缀标签策略。调用单个索引 wherelabel=T3-Item,这将返回 100 万个结果。这实现了与财产策略相同的结果,但它检索的记录减少了1100万条。此外,由于索引中的标签不重叠,因此您不再有噪音邻居顾虑。

与属性策略相比,多标签策略并不能直接提高查询性能。当搜索空间也具有可比性时,按属性值筛选与按标签值筛选相当。相反,多标签策略支持更大的灵活性。 多标签策略提供的性能等同于或标签的前缀标签策略。label=T3 T3-Item多标签策略提供的性能与属性策略相同。label=Item好处是支持各种访问模式。