提高移除 TinkerGraph 顶点的性能
Improve performance removing TinkerGraph vertices
我有一个图 g
,有 600k
个顶点和 950k
个边。经过一些处理后,我需要使用此查询清理大约 350k+
个顶点:
g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).drop().iterate();
即使我排除了没有“依赖”边的顶点,它们仍然与其他边相连。
使用 Java、tinkerpop/tinkergraph 3.4.6.
目前删除所有这些顶点大约需要 45 分钟。
我做了一个 java 分析,结果显示 73% 的时间花在 TinkerVertex.remove
方法上,其余时间花在 ExpandableStepIterator.next
有没有类似“批量投放”的东西? JanusGraph 或其他图形提供程序会更快吗?
不太可能有比 TinkerGraph 快得多的图形,因为 TinkerGraph 是纯内存实现。您可能会发现一个使用该内存更有效的方法,例如 OverflowDB,它最初是从 TinkerGraph 分叉出来的,但我不知道它会使这个特定的查询运行得更快。
TinkerGraph 和我所知道的任何图表都具有过滤的“批量删除”操作。
此处的“非”样式全局查询非常昂贵,因为您必须触及图表的大部分。当然,我有点惊讶 TinkerGraph 为边数少于 100 万的图花费了这么长时间。你没有提到你在做你的个人资料时是否遇到了很多 GC。也许这是一个问题?如果是这样,我会尝试调整您的 JVM 内存配置 - 也许您只需要更大的 -Xmx
值或类似的简单值。
从查询的角度来看,您可以尝试反转遍历的 not()
部分,以明确找到要删除的内容。这可能会导致阅读的查询不那么简洁,但可能会加快速度,但另一方面,您仍在尝试删除 50% 的数据,因此成本可能不仅仅是找到要删除的顶点。
另一个想法是尝试并行化 drop()
。您可能会遇到并发错误,因此您可能需要重试策略,但您可以考虑采用 g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND))
的 Iterator
,然后将对每个(或成批)Vertex.remove()
的调用委托给单独的工作线程.
根据已接受的答案,简单的并行化改进足以使此操作不再是时间上最关键的操作
为了将来参考,这个:
g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).drop().iterate();
现在是这样的:
ExecutorService executor = Executors.newFixedThreadPool(4);
int iterator = 0;
final int batchsize = 10000;
Long count = g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).count().next();
List<Callable<Object>> callableList = new ArrayList<Callable<Object>>();
// splitting current set into tasks to be executed in para
while (iterator * batchsize < count) {
final Set<Object> vSet = g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).skip(iterator * batchsize).limit(batchsize).id().toSet();
callableList.add(() -> g.V(vSet).drop().iterate());
iterator++;
}
List<Future<Object>> results = executor.invokeAll(callableList);
经过一些测试,我决定将迭代保留在单个线程中。这样分布式任务就真正相互独立了(例如:一个任务完成不会影响其他任务查询)。
请记住,实际删除仍然是单线程的,因为顶点节点映射修改是在并发访问锁之后。
效果是增加线程不会得到更好的效果(个人试过8个)。根据一些线程转储,即使是 4 个也可能太多(总是有 1 个或多个线程处于等待状态)- 尽管我确实得到了一个包含 3 个线程的转储 运行!
我有一个图 g
,有 600k
个顶点和 950k
个边。经过一些处理后,我需要使用此查询清理大约 350k+
个顶点:
g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).drop().iterate();
即使我排除了没有“依赖”边的顶点,它们仍然与其他边相连。
使用 Java、tinkerpop/tinkergraph 3.4.6.
目前删除所有这些顶点大约需要 45 分钟。
我做了一个 java 分析,结果显示 73% 的时间花在 TinkerVertex.remove
方法上,其余时间花在 ExpandableStepIterator.next
有没有类似“批量投放”的东西? JanusGraph 或其他图形提供程序会更快吗?
不太可能有比 TinkerGraph 快得多的图形,因为 TinkerGraph 是纯内存实现。您可能会发现一个使用该内存更有效的方法,例如 OverflowDB,它最初是从 TinkerGraph 分叉出来的,但我不知道它会使这个特定的查询运行得更快。
TinkerGraph 和我所知道的任何图表都具有过滤的“批量删除”操作。
此处的“非”样式全局查询非常昂贵,因为您必须触及图表的大部分。当然,我有点惊讶 TinkerGraph 为边数少于 100 万的图花费了这么长时间。你没有提到你在做你的个人资料时是否遇到了很多 GC。也许这是一个问题?如果是这样,我会尝试调整您的 JVM 内存配置 - 也许您只需要更大的 -Xmx
值或类似的简单值。
从查询的角度来看,您可以尝试反转遍历的 not()
部分,以明确找到要删除的内容。这可能会导致阅读的查询不那么简洁,但可能会加快速度,但另一方面,您仍在尝试删除 50% 的数据,因此成本可能不仅仅是找到要删除的顶点。
另一个想法是尝试并行化 drop()
。您可能会遇到并发错误,因此您可能需要重试策略,但您可以考虑采用 g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND))
的 Iterator
,然后将对每个(或成批)Vertex.remove()
的调用委托给单独的工作线程.
根据已接受的答案,简单的并行化改进足以使此操作不再是时间上最关键的操作
为了将来参考,这个:
g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).drop().iterate();
现在是这样的:
ExecutorService executor = Executors.newFixedThreadPool(4);
int iterator = 0;
final int batchsize = 10000;
Long count = g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).count().next();
List<Callable<Object>> callableList = new ArrayList<Callable<Object>>();
// splitting current set into tasks to be executed in para
while (iterator * batchsize < count) {
final Set<Object> vSet = g.V().hasLabel(LABEL_INTERMEDIATE_COLUMN).not(inE(EDGE_DEPEND)).skip(iterator * batchsize).limit(batchsize).id().toSet();
callableList.add(() -> g.V(vSet).drop().iterate());
iterator++;
}
List<Future<Object>> results = executor.invokeAll(callableList);
经过一些测试,我决定将迭代保留在单个线程中。这样分布式任务就真正相互独立了(例如:一个任务完成不会影响其他任务查询)。
请记住,实际删除仍然是单线程的,因为顶点节点映射修改是在并发访问锁之后。
效果是增加线程不会得到更好的效果(个人试过8个)。根据一些线程转储,即使是 4 个也可能太多(总是有 1 个或多个线程处于等待状态)- 尽管我确实得到了一个包含 3 个线程的转储 运行!