【发布时间】:2019-06-17 23:22:56
【问题描述】:
我有一段代码删除一个顶点并提交事务。 由于某种原因,下一个操作仍然会看到顶点。 也很奇怪,它有时只看到它可能是基于时间等。 例如图形 服务--包含-->路由
操作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 通过线程的第一个操作打开新事务。
记录sn-ps:
操作1:
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 在更新之前检查顶点是否存在,如果不存在则抛出错误。
所以很明显,即使删除成功并且提交在它之前完成,使用 janusgraph getVertex api 基于节点 id 键从图中返回顶点。
如果将 2 个操作之间的时间差设置为超过几分钟,则相同的代码将按预期工作。
我们还配置了使用 janushgraph 缓存。
有了所有这些,我真的很困惑这是怎么发生的。
我可以理解这 2 个操作是否以某种方式并行运行并相互踩踏,而竞争条件可能会给我带来陈旧的数据,但这些操作是同步的并且一个接一个地发生。
在第一次操作中删除并提交后,预计不会在第二次操作中返回顶点,尤其是当两个操作同步并且一个接一个地发生而没有任何失败/异常时。
用例 1:
Thread-1 ----calls---> 同步方法 1---> 获取边/顶点,更新顶点,提交 ----submits ---> singleThreadedExecutorTask ---> 删除边/顶点, 提交 ----> 调用 --> 同步方法 1(用于操作 2)----> 此处获取边/顶点仍然获取旧边/顶点
我可以理解用例 2,其中事务范围适用于具有第一个操作的线程,并且在此事务范围内不可见其他线程中提交的任何内容,因此我必须在开始操作 2 之前提交事务以查看更改。
我在用例 2 中尝试过这个,它按预期工作!!
用例 2:
Thread-1 ----calls---> 同步方法 1---> 获取边/顶点,更新顶点,提交 ----submits ---> singleThreadedExecutorTask ---> 删除边/顶点, 提交 ----> Thread-1 完成。
大约一分钟后:
Thread-2 ----calls---> 同步方法 1---> 获取边/顶点,更新顶点,提交 ----submits ---> singleThreadedExecutorTask ---> 删除边/顶点, commit ----> Thread-2 完成。
问题线程 2 调用同步方法 1 仍会获取旧边/顶点,该边/顶点作为线程 1 进程的一部分被删除。
现在在这种情况下。
Thread-1 范围的事务通过第一个图形操作打开,并且该事务在更新后立即关闭。 在该 singleThreadedExecutor 任务在单独的线程中运行之后,它会为第一个操作打开自己的新事务,并在任务完成时关闭事务并提交。
线程 2 在一分钟后启动时使用第一个图形操作打开自己的线程范围事务 - 在这个新线程事务范围中的这个 get 操作应该能够在不从线程 1 删除边/顶点的情况下获取正确的数据,特别是考虑到ti 几乎在 1 分钟后开始。 这甚至不是集群设置。 即使使用集群设置 - 我认为必须在提交调用返回之前满足法定人数,并且其余的复制可以独立发生(延迟)
这是我无法理解的部分,当然,如果我添加 2 个线程的手动干预,比如启动线程 1 可能在 2 分钟后,它会出于某种原因工作。
在这种情况下,对于最终的一致性而言,2 分钟似乎真的很长。
那么应用程序有什么选择来处理这个问题?
有没有办法强制图形操作等待最终一致性? 像 thread-2 一样,我可以指定第一个 get 操作必须等待,除非它通过解决所有冲突等返回一致的数据。
我不认为在线程 2 中打开新事务或尝试执行某种全局提交来关闭先前打开的陈旧事务(如果有的话)是正确的做法,因为这只是新线程的开始。
【问题讨论】:
标签: cassandra tinkerpop tinkerpop3 janusgraph