【问题标题】:Janusgraph delete vertex and commit completes but next operation still sees the vertexJanusgraph 删除顶点并提交完成,但下一个操作仍然看到顶点
【发布时间】: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


    【解决方案1】:

    Cassandra 的突变不会立即出现

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

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

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

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

    一般建议是做应用端检查

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

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

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

    我要注意,虽然上一段是一种快速简便的检查方法,但如果您发现绝对需要确认每个 upsert 和删除操作,您最好使用不同的存储后端,例如 HBase或伯克利数据库。 Here is the list of options for storage backends according to the JanusGraph Manual.

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

    【讨论】:

    • 谢谢丽贝卡,感谢您的回复和详细信息。我知道 Cassandra 最终的一致性可能需要几秒钟,但我希望 Janusgraph 缓存能够处理这种情况,尤其是读取。在本例中,service----contains--->route edge 被删除,route vertex 也被删除。但可能删除是一种特殊情况,其中边缘和顶点从缓存中删除/标记为过期,下一次读取强制它转到可能不一致的 Cassandra。
    • 另外我认为提交应该只在所有集群/仲裁都满足写入时返回。现在即使 Cassandra 突变可能需要几秒钟,最终某些层必须考虑写入并相应地返回正确的数据。基于 jansugraph 缓存或 Cassandra 缓存。不确定默认情况下 cassandra 是否支持内存缓存以支持此类延迟写入/突变延迟等。但我知道根据 Janusgraph 文档和应用程序需要了解并根据需要相应地处理它会出现临时不一致
    • 同一示例的另一个有趣的用例失败:线程 1 ---> 进入同步方法 1 ---> 图形获取边/顶点,更新,提交 ---> 调用 singleThreadExecutor -- -> 图形删除边/顶点,提交 ---> 返回线程 1 --> 线程 1 完成。现在在 1 分钟后线程 2 启动并经历与线程 1 完全相同的过程,并且第一个获取边/顶点在这里失败并出现同样的问题,它仍然获得通过线程 1 操作删除的顶点/边。在线程 1 完成 1 分钟后,线程 1 提交了所有内容,但仍然是相同的问题,添加了两个用例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-03
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多