跳至内容

批量加载

有许多配置选项和工具可以使将大量图数据导入JanusGraph更加高效。这种导入被称为批量加载,与默认的事务性加载形成对比,后者通过单个事务添加少量数据。

将数据批量加载到JanusGraph有多种使用场景,包括:

  • 将JanusGraph引入已有数据和环境的现有系统中,并将这些数据迁移或复制到新的JanusGraph集群中。

  • 使用JanusGraph作为 ETL 流程的终端。

  • 将现有或外部图形数据集(例如公开可用的RDF数据集)添加到正在运行的JanusGraph集群中。

  • 使用图分析作业的结果更新JanusGraph图。

本页介绍了在JanusGraph中使批量加载更高效的配置选项和工具。请在使用前仔细查看每个选项的限制和假设,以避免数据丢失或数据损坏。

本文档重点介绍JanusGraph特定的优化。此外,为提高写入性能,建议同时改进所选存储后端及(可选的)索引后端。更多信息请参阅相应后端的文档。

Configuration Options

批量加载

启用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-attemptsstorage.write-attempts 配置了JanusGraph在放弃之前会尝试对存储后端执行读写操作的次数。如果预期在批量加载期间后端负载较高,通常建议增加这些配置选项。

storage.attempt-wait 指定了JanusGraph在重新尝试失败的后端操作之前等待的毫秒数。较高的值可以确保操作重试不会进一步增加后端的负载。

策略

Parallelizing the Load

通过在多个机器上并行化批量加载,如果JanusGraph的存储后端集群足够大以处理额外的请求,加载时间可以大大减少。这本质上是JanusGraph with TinkerPop’s Hadoop-Gremlin采用的方法,使用MapReduce将数据批量加载到JanusGraph中。

如果无法使用Hadoop来并行化批量加载过程, 以下是一些高效并行化加载过程的高级指导原则:

  • 在某些情况下,图数据可以被分解为多个不连通的子图。这些子图可以跨多台机器并行独立加载(例如,使用如上所述的BatchGraph)。

  • 如果图无法分解,通常分多个步骤加载是有益的,其中最后两个步骤可以在多台机器上并行化:

    1. 确保顶点和边数据集已去重且保持一致。

    2. 设置 batch-loading=true。可能优化上述描述的其他配置设置。

    3. 将所有顶点及其属性添加到图中(但不包括边)。维护一个从顶点ID(由加载数据定义)到JanusGraph内部顶点ID(即vertex.getId())的(分布式)映射,该ID是一个64位长整型ID。

    4. 使用映射查找JanusGraph的顶点ID,并通过该ID检索顶点,添加所有边。

问答

  • 为避免批量加载期间出现以下异常,我应该怎么做: java.io.IOException: ID renewal thread on partition [X] did not complete in time.? 该异常很可能是由于存储后端压力过大,在ID分配阶段反复超时导致的。 请参考上文ID分配优化章节。