批量加载
有许多配置选项和工具可以提高将大量图数据摄取到 JanusGraph 的效率。这种摄取被称为批量加载,与默认的事务性加载(通过单个事务添加少量数据)形成对比。
将数据批量加载到 JanusGraph 有多种用例,包括:
-
在现有环境中引入 JanusGraph,并迁移或复制现有数据到新的 JanusGraph 集群。
-
将 JanusGraph 作为 ETL 过程的终点。
-
将现有或外部图数据集(例如,公开可用的 RDF 数据集)添加到正在运行的 JanusGraph 集群中。
-
使用图分析作业的结果更新 JanusGraph 图。
本页介绍了提高 JanusGraph 批量加载效率的配置选项和工具。在继续之前,请仔细注意每个选项的限制和假设,以避免数据丢失或数据损坏。
本文档侧重于 JanusGraph 特定的优化。此外,请考虑改进所选的存储后端和(可选的)索引后端以获得高写入性能。有关更多信息,请参阅相应后端的文档。
配置选项
批量加载
启用 storage.batch-loading 配置选项将对大多数应用程序的批量加载时间产生最大的积极影响。启用批量加载会在多个地方禁用 JanusGraph 的内部一致性检查。最重要的是,它禁用了锁定。换句话说,JanusGraph 假定要加载到 JanusGraph 中的数据与图是一致的,因此为了性能而禁用了其自身的检查。
在许多批量加载场景中,在加载数据之前确保数据一致性比在加载数据到数据库时确保数据一致性要便宜得多。storage.batch-loading 配置选项正是基于这一观察而存在的。
例如,考虑将现有用户配置文件批量加载到 JanusGraph 的用例。此外,假设用户名属性键上定义了一个唯一的复合索引,即用户名在整个图中必须是唯一的。如果用户配置文件是从另一个数据库导入的,则用户名唯一性可能已经得到保证。如果不是,则可以简单地按名称排序配置文件并过滤掉重复项,或者编写 Hadoop 作业来完成此类过滤。现在,我们可以启用 storage.batch-loading,这将显著减少批量加载时间,因为 JanusGraph 不必为每个添加的用户检查名称是否已存在于数据库中。
重要:启用 storage.batch-loading 要求用户确保加载的数据在内部一致,并与图中已有的任何数据一致。特别是,启用批量加载时,并发类型创建可能导致严重的数据完整性问题。因此,我们强烈建议通过在图配置中设置 schema.default = none 来禁用自动类型创建。
优化 ID 分配
ID 块大小
每个新添加的顶点或边都分配有一个唯一的 ID。JanusGraph 的 ID 池管理器为特定的 JanusGraph 实例以块的形式获取 ID。ID 块获取过程开销很大,因为它需要保证全局唯一的块分配。增加 ids.block-size 会减少获取次数,但可能会导致许多 ID 未分配,从而浪费。对于事务性工作负载,默认的块大小是合理的,但在批量加载期间,顶点和边添加得更频繁且连续。因此,通常建议将块大小增加 10 倍或更多,具体取决于每台机器要添加的顶点数量。
经验法则:将 ids.block-size 设置为预期每小时每个 JanusGraph 实例添加的顶点数量。
重要提示:所有 JanusGraph 实例必须配置相同的 ids.block-size 值以确保正确的 ID 分配。因此,在更改此值之前,请务必关闭所有 JanusGraph 实例。
ID 获取过程
当许多 JanusGraph 实例并行频繁分配 ID 块时,实例之间的分配冲突将不可避免地发生,并减慢分配过程。此外,由于批量加载导致的写入负载增加可能会进一步减慢该过程,甚至达到 JanusGraph 认为其失败并抛出异常的程度。有三个配置选项可以调整以避免这种情况。
1) ids.authority.wait-time 配置 ID 池管理器等待存储后端确认 ID 块应用程序的时间(以毫秒为单位)。此时间越短,应用程序在拥塞的存储集群上失败的可能性就越大。
经验法则:将其设置为在负载下测量的存储后端集群上的 95 百分位读写时间之和。重要:此值在所有 JanusGraph 实例中应相同。
2) ids.renew-timeout 配置 JanusGraph 的 ID 池管理器在尝试获取新 ID 块失败之前总共等待的毫秒数。
经验法则:将此值设置得尽可能大,以免因无法恢复的故障而等待太长时间。增加此值的唯一缺点是 JanusGraph 会在不可用的存储后端集群上尝试很长时间。
优化写入和读取
缓冲区大小
JanusGraph 缓冲写入并以小批量执行它们,以减少对存储后端的请求数量。这些批处理的大小由 storage.buffer-size 控制。在短时间内执行大量写入时,存储后端可能会因写入请求而过载。在这种情况下,增加 storage.buffer-size 可以通过增加每个请求的写入数量,从而降低请求数量来避免失败。
但是,增加缓冲区大小会增加写入请求的延迟及其失败的可能性。因此,不建议为事务性负载增加此设置,在批量加载期间应仔细试验此设置。
读写健壮性
在批量加载期间,集群上的负载通常会增加,导致读写操作失败的可能性更大(特别是如果如上所述增加了缓冲区大小)。storage.read-attempts 和 storage.write-attempts 配置 JanusGraph 在放弃之前尝试对存储后端执行读写操作的次数。如果预计在批量加载期间后端负载很高,通常建议增加这些配置选项。
storage.attempt-wait 指定 JanusGraph 在重新尝试失败的后端操作之前等待的毫秒数。更高的值可以确保操作重试不会进一步增加后端负载。
策略
并行加载
通过跨多台机器并行进行批量加载,如果 JanusGraph 的存储后端集群足够大以处理额外的请求,加载时间可以大大减少。这本质上是 带有 TinkerPop 的 Hadoop-Gremlin 的 JanusGraph 使用 MapReduce 将数据批量加载到 JanusGraph 所采用的方法。
如果不能使用 Hadoop 来并行化批量加载过程,这里有一些有效并行化加载过程的高级指南
-
在某些情况下,图数据可以分解为多个不连通的子图。这些子图可以跨多台机器独立并行加载(例如,使用上面描述的 BatchGraph)。
-
如果图无法分解,通常分多步加载会更有益,其中最后两步可以跨多台机器并行化
-
确保顶点和边数据集已去重且一致。
-
设置
batch-loading=true。可能会优化上面描述的其他配置设置。 -
将所有顶点及其属性添加到图中(但不添加边)。维护一个从顶点 ID(由加载的数据定义)到 JanusGraph 内部顶点 ID(即
vertex.getId(),它是一个 64 位长 ID)的(分布式)映射。 -
使用映射查找 JanusGraph 的顶点 ID 并使用该 ID 检索顶点,从而添加所有边。
-
问答
- 在批量加载期间,如何避免出现以下异常:
java.io.IOException: ID renewal thread on partition [X] did not complete in time.?此异常很可能是由于 ID 分配阶段重复超时,原因在于存储后端负载过高。请参阅上面ID 分配优化一节。