首页 > 解决方案 > Janusgraph 删除顶点并提交完成,但下一个操作仍然看到顶点

问题描述

我有一段代码删除一个顶点并提交事务。由于某种原因,下一个操作仍然会看到顶点。它也很奇怪,它有时只看到它可能是基于时间等。例如图形服务--包含-->路线

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

操作2:从服务节点获取包含边,它仍然获取在操作1中删除的路由节点

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

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

在 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 不会相互冲突。operation1 完成,然后开始 operation 2

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

我们没有使用线程事务我们没有使用新事务我们依赖 tinkerpop 打开新事务,第一次操作线程。

日志片段:

操作一:

2019-06-17 14:58:25,213 | 删除节点:路线: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 | 更新节点 2019-06-17 14:58:25,739 | updateNode 待更新节点:route:1560307936368:1683669533 2019-06-17 14:58:25,740 | updateVertex:为键更新顶点:路由:1560307936368:1683669533 2019-06-17 14:58:25,741 | updateNode 在 updateNode 中花费的时间 = 3

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

所以很明显,即使删除成功并且提交在它之前完成,仍然使用基于节点 id 键的 janusgraph getVertex api 从图形返回顶点。

如果两个操作之间的时间差被操纵为超过几分钟,则相同的代码将按预期工作。

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

有了所有这些,我真的很困惑这是怎么发生的。

我可以理解这 2 个操作是否以某种方式并行运行并相互踩踏,竞争条件会给我带来陈旧的数据,但这些操作是同步的并且一个接一个地发生。

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


用例 1:

线程 1 ----调用 ---> 同步方法 1 ---> 获取边/顶点,更新顶点,提交 ----提交 ---> singleThreadedExecutorTask ---> 删除边/顶点,提交 - ---> 调用 --> 同步方法 1(用于操作 2)----> 此处获取边/顶点仍然获取旧边/顶点

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

我为用例 2 尝试了这个,它按预期工作!


用例 2:

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

大约一分钟后:

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

问题 Thread-2 调用同步方法 1 仍会获取作为 Thread-1 进程的一部分被删除的旧边/顶点。

现在在这种情况下。

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

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

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

在这种情况下,对于最终的一致性来说,2 分钟似乎真的很长。

那么应用程序处理这个问题的选项是什么?

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

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


标签: cassandratinkerpoptinkerpop3janusgraph

解决方案


Cassandra 的突变不会立即出现

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

完成对 Cassandra 的写入后,它仍需要将更改传播到集群的其余部分。在突变获取一些陈旧数据之后立即发生读取是完全可能的。

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

JanusGraph 确保它一次只触发一次对 Cassandra 的调用,但这并不能解决在 Janus 对 Cassandra 的调用完成后,Cassandra 仍然有一段时间传播突变的事实;如果 Janus 对 Cassandra 的下一次调用是在该突变完成之前,则数据将是陈旧的。

一般建议是做应用端检查

使用最终一致的存储后端会导致这些问题;最终一致后端的 JanusGraph 文档中推荐的初始路径是在阅读时解决应用程序中的此类不一致问题。如果可以的话,以不假设返回的突变调用意味着突变将是可见的方式来构建您的应用程序。

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

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

我要注意,虽然上一段是检查这一点的快速简便的方法,但如果您发现绝对需要确认每个 upsert 和 delete 操作,您最好使用不同的存储后端,例如 HBase 或 BerkeleyDB。这是根据 JanusGraph 手册的存储后端选项列表

但是,如果您通常对缺乏强一致性感到满意,那么好处是 Cassandra 倾向于相当容易地水平扩展。最后,这一切都取决于您的应用程序的需求。


推荐阅读