在不同事务中插入相同类型的顶点时出现 Neptune ConcurrentModificationException

问题描述 投票:0回答:2

我们正在使用 AWS Neptune 数据库,目前当两个用户同时将某些内容保存到数据库时,我们遇到问题。

在这张图片中,您可以以简化的方式看到我们的模型。我们有带有标签“A”的根顶点,它可以有几个“B”顶点,这些顶点又可以有几个“C”顶点,然后是一堆连接的顶点see graph model here

我们所做的是启动一个事务,添加C、D、E、F、G顶点和边,提交事务。

当两个用户同时针对完全独立的“A”顶点点击“保存”时,就会出现问题。 用户1想要保存顶点A(ID:4711)的新子图,用户2想要保存顶点A(ID:4712)的新子图。这些子图是完全独立的,它们之间没有任何联系。我的第一个假设是,在顶点 A(ID:4711)下添加子图对在不同顶点 A(ID:4712)下添加子图没有影响。 添加操作可能需要几秒钟。现在,如果两个事务尝试同时添加子图,我们会得到这个异常:

Caused by: java.util.concurrent.CompletionException: org.apache.tinkerpop.gremlin.driver.exception.ResponseException: {"detailedMessage":"Failed to complete Insert operation for a Vertex due to conflicting concurrent operations. Please retry. 0 transactions are currently rolling back.","requestId":"64be5fdb-4208-4729-bc68-dccfecbfc87f","code":"ConcurrentModificationException"}

Caused by: org.apache.tinkerpop.gremlin.driver.exception.ResponseException: {"detailedMessage":"Failed to complete Insert operation for a Vertex due to conflicting concurrent operations. Please retry. 0 transactions are currently rolling back.","requestId":"64be5fdb-4208-4729-bc68-dccfecbfc87f","code":"ConcurrentModificationException"}

我在事务中的调试器中停止,并尝试在 Jupyter Notebook 上添加顶点。当调试器停止时,我无法添加顶点“A”。但插入之前图中未使用过的新顶点“XYZ”是没有问题的。

我假设添加顶点会阻止其他事务的索引。这就是我在文档中找到的:

我想说我们使用 SERIALIZABLE 作为隔离级别: https://docs.aws.amazon.com/neptune/latest/userguide/transactions-isolation-levels.html: READ UNCOMMITTED – 允许所有三种交互(即脏读、不可重复读和幻读)。 READ COMMITTED – 脏读是不可能的,但不可重复读和幻读是可能的。 可重复读 – 脏读和不可重复读都不可能,但幻读仍然可能。 **可串行化 **– 这三种类型的交互现象都不会发生。

https://docs.aws.amazon.com/neptune/latest/userguide/access-graph-gremlin-transactions.html: 无会话只读查询在 SNAPSHOT 隔离下执行,但在显式事务中运行的只读查询在 SERIALIZABLE 隔离下执行。与在 SNAPSHOT 隔离下运行的查询不同,在 SERIALIZABLE 隔离下执行的只读查询会产生更高的开销,并且可能会阻塞或被并发写入阻塞。

查询如下所示:

g.V("f6e9a3ed-5b2e-400a-83f4-074af0fee71f").fold().coalesce(unfold(),addV("C").property(single,"status",'"abc"').property(T.id,"f6e9a3ed-5b2e-400a-83f4-074af0fee71f")).id().fold()

我只是认为合并可能是这篇文章中的问题:ConcurrentModificationException in amazon neptune using gremlin javascript languagevariant 但我试图在事务中停止调试器,然后执行 g.addV("C") 并遇到同样的问题。

是否可以同时为不同的图添加子图?

经过更深入的调查,我发现事务中的第一个查询阻塞了整个数据库。你知道为什么这个查询会阻止一切吗?

g.V().hasLabel("A").has("v",4711).outE("s").inV()
.hasLabel("B").hasId("c4...a","a0...f","ac...3")
.outE("h").inV()
.bothE("sv","ev")
.bothV().not(hasLabel("C")).simplePath()
.barrier()
.repeat(outE().not(hasLabel("sv","ev")).simplePath().inV())
.until(or(outE().count().is(0),hasLabel("C"),hasLabel("AP","AC").bothE("sv","ev").count().is(P.gt(0))))
.path().unfold().dedup().or(hasLabel("sp").has("ev"),hasLabel("ev")).barrier()
.drop()
exception gremlin amazon-neptune concurrentmodification subgraph
2个回答
0
投票

我相信您所看到的是间隙锁的效果[1]。如果您使用基于会话的事务,一旦您写入给定的顶点、边或属性,存储这些值的关联索引中的间隙将被锁定。因此,任何其他尝试写入同一间隙的操作都会遇到死锁情况。在 Neptune 中,死锁被视为“胜者优先”,因此任何后续在死锁情况下写入的请求都会引发 ConcurrentModificationException。

另一个值得注意的事项是写入索引的值不是您在查询中写入的值。 Neptune 使用字典并将给定值的字典 ID 存储在索引中。该字典值是不确定的,无法提前预测。这使得试图避免 CME 变得有点困难。

CME 的当前指导是在您的应用程序中实施重试和指数退避策略 [2]。大多数情况下,立即重试就会成功。如果您有长时间运行的事务,您可能需要调整等待/重试周期,或者为每次尝试的重试时间设置更陡峭的指数曲线。

[1] https://docs.aws.amazon.com/neptune/latest/userguide/transactions-neptune.html#transactions-neptune-false-conflicts

[2] https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html


0
投票

最终我们做了这些事情来让它发挥作用:

  • 使用没有事务的读取查询,因此我们使用快照隔离来读取
  • 将删除查询从“获取并删除”更改为在一个事务中以及在我们没有事务读取所需的子图之前通过 ID 显式删除边和顶点。

目前正在运行,我们需要确保数据保持一致,因为不使用事务读取。

© www.soinside.com 2019 - 2024. All rights reserved.