Janusgraph删除顶点并完成提交,但是下一个操作仍然可以看到顶点

时间:2019-06-17 23:22:56

标签: cassandra tinkerpop tinkerpop3 janusgraph

我有一段代码删除一个顶点并提交事务。 出于某种原因,下一个操作仍会看到顶点。 同样奇怪的是,它有时只能根据时间等来查看。 例如图形 服务-包含->路线

操作1:删除包含边,删除顶点并提交

操作2:get包含来自服务节点的边缘,并且仍然获得在操作1中删除的路由节点

这两个操作是一个接一个的,并且不是并行运行的,因此在第一次提交之前读取它没有问题。

如果第一次提交成功完成,那么我的理解是所有其他线程应立即查看更新。

在cassandra db中使用janusgraph api for Java

样本伪代码:

synchronized methodA:
 do some operations
 figure out route X need to be deleted from graph
 get all routes using contains edge from service node
 // service---contains--> route
 get route X from all routes 
 singlethreadExecutor.submitTask(DeleteRoute X)
 update some other DB with service without route X

Task DeleteRoute (route x)
 get  route X from graph DB
 delete route X vertex 
 commit

Operation1 calls into methodA:
 service with 4 routes R1,R2, R3, R4
 Expected to delete R3
 Works as expected
 R3 is deleted from graph as well as other DB

Operation2 calls into methodA:
 service expected routes in graph with R1, R2, R4
 however, method A still gets all 4 routes including R3 which is deleted in operation 1

请注意,方法A是同步的,因此操作1和2不会相互冲突。 操作1完成,然后开始操作2

这让我感到困惑,特别是当我的日志表明操作1的提交已完成并且操作2仍使用janusgraph api从图中获取路由节点R3时。

我们没有使用线程交易 我们没有使用新交易 我们依靠tinkerpop通过该线程的第一个操作来打开新事务。

日志摘要:

操作1:

2019-06-17 14:58:25,213 | deleteNode:路线:1560307936368:1683669533 2019-06-17 14:58:25,216 |承诺 2019-06-17 14:58:25,350 |提交所需的时间= 133

操作2:

2019-06-17 14:58:25,738 | updateNode 2019-06-17 14:58:25,739 | updateNode要更新的节点:route:1560307936368:1683669533 2019-06-17 14:58:25,740 | updateVertex:为以下键更新了顶点:route:1560307936368:1683669533 2019-06-17 14:58:25,741 | updateNode在updateNode = 3中花费的时间

如您所见,操作1删除路由节点并提交,而操作2从图形读取时,仍然获得相同的路由节点并能够更新它。 我们的更新api在更新顶点之前先检查其是否存在,如果不存在则抛出错误。

因此很明显,即使删除成功并且提交就在其之前完成,使用基于节点ID键的janusgraph getVertex api仍会从图中返回顶点。

如果将两个操作之间的时间差控制在几分钟以上,则相同的代码将按预期工作。

我们还配置了使用janushgraph缓存。

有了这一切,我真的很困惑这怎么发生。

我可以理解这2个操作是否以某种方式并行运行并相互促进,而竞争条件可以为我提供陈旧的数据,但这些操作是同步的并且彼此接连发生。

预期在删除并在第一操作中提交后在第二操作中不返回顶点,尤其是当两个操作都同步并且一个接一个地发生而没有任何失败/异常时。


用例1:

线程1 ----调用->同步方法1 --->获取edge / vertex,更新顶点,提交---- submits ---> singleThreadedExecutorTask --->删除edge / vertex ,提交---->调用->同步方法1(用于操作2) ---->在这里,get edge / vertex仍然会获得旧的edge / vertex

我可以理解用例2,其中事务范围是针对具有第一个操作的线程的,而其他线程中提交的任何内容在此事务范围中都不可见,因此,我必须在开始操作2之前先提交事务,以查看更改。 / p>

我在用例2中尝试了此方法,它按预期工作!!


用例2:

线程1 ----调用->同步方法1 --->获取edge / vertex,更新顶点,提交---- submits ---> singleThreadedExecutorTask --->删除edge / vertex ,提交---->线程1完成。

大约一分钟后:

线程2 ----调用->同步方法1 --->获取边缘/顶点,更新顶点,提交----提交---> singleThreadedExecutorTask --->删除边缘/顶点, 提交---->线程2完成。

对同步方法1的问题Thread-2调用仍然会获得旧的边/顶点,该旧边/顶点将在Thread-1进程中删除。

现在是这种情况。

线程1范围内的事务使用第一个图形操作打开,并且该事务在更新后立即关闭。 在单线程中运行了singleThreadedExecutor任务后,它将为第一次操作打开自己的新事务,并在任务完成时通过提交关闭事务。

线程2一分钟后启动时,将使用第一个图形操作打开其自己的线程范围的事务-在此新线程事务范围中的此get操作应能够获取正确的数据,而不会从线程1中删除边缘/顶点,特别是考虑ti将在1分钟后开始。 这甚至不是群集设置。 即使使用群集设置,我也认为必须满足法定人数,然后才能返回提交调用,其余复制可以独立发生(延迟)

这是我无法理解的部分,当然,如果我添加2个线程的手动干预,例如启动线程1可能会在2分钟后,则出于某些原因。

在这种情况下,

2分钟对于最终的一致性似乎真的很长。

那么应用程序可以处理此问题的选项是什么?

是否有任何方法可以强制图形操作等待最终一致性? 像线程2一样,我可以指定第一个get操作必须等待,除非它通过解决所有冲突等返回一致的数据。

我不认为在线程2中打开新事务或尝试执行某种全局提交来关闭先前打开的旧事务(如果有的话)是正确的方法,因为这仅仅是新线程的开始。


1 个答案:

答案 0 :(得分:2)

对Cassandra的更改不会立即显示

Cassandra是所谓的最终一致性数据库,这意味着写入该数据库的更改不能保证对所有使用者都立即可见。它尽了最大的努力来实现它,但这并不总是最终发生。关键是,您不应期望在写完Cassandra之后立即看到任何突变。

对Cassandra的写操作完成后,仍然需要将更改传播到集群的其余部分。完全有可能在突变获取一些过时的数据后立即进行读取。

JanusGraph的锁定和同步独立于Cassandra的一致性

JanusGraph确保每次仅触发一次对Cassandra的呼叫,但这并不能避免在Janus对Cassandra的呼叫完成后,Cassandra仍在传播一段时间的事实。突变如果Janus对Cassandra的下一次调用是在该突变完成之前,则数据将过时。

一般建议是进行应用程序端检查

使用最终一致的存储后端将导致此类问题。建议从JanusGraph Documentation on Eventually Consistent Backends开始使用的初始路径是在阅读时解决应用程序中的此类不一致问题。如果您可以的话,以不假定返回突变调用的方式来设计您的应用,这意味着该突变将是可见的。

在您的示例中,我将在您的两个事务之间插入一些内容,这些内容要么等待一段适当的时间(我说即使只有几秒钟也应该是足够的时间),或者检查删除是否已完成。

但是,如果您需要强大的数据一致性,那么Cassandra并不是一个很好的解决方案

我会注意到,尽管上一段是一种快速简便的检查方法,但是如果您发现您绝对需要确认每个upsert和delete操作,那么最好使用其他存储后端(例如HBase)或BerkeleyDB。 Here is the list of options for storage backends according to the JanusGraph Manual

但是,如果您总体上缺乏强大的一致性就可以,那么好处是Cassandra倾向于相当容易地水平扩展。最后,这完全取决于您的应用程序需求。