主流开源分布式图数据库Benchmark

1. 前言

近年来,深度学习和知识图谱技术发展迅速,相比于深度学习的“黑盒子”,知识图谱具有很强的可解释性,在搜索推荐、智能助理、金融风控等场景中有着广泛的应用。美团基于积累的海量业务数据,结合使用场景进行充分地挖掘关联,逐步建立起包括美食图谱、旅游图谱、商品图谱在内的近十个领域知识图谱,并在多业务场景落地,助力本地生活服务的智能化。

为了高效存储并检索图谱数据,相比传统关系型数据库,选择图数据库作为存储引擎,在多跳查询上具有明显的性能优势。当前业界知名的图数据库产品有数十款,选型一款能够满足美团实际业务需求的图数据库产品,是建设图存储和图学习平台的基础。我们结合业务现状,制定了选型的基本条件:

  • 开源项目,对商业应用友好
    • 拥有对源代码的控制力,才能保证数据安全和服务可用性。
  • 支持集群模式,具备存储和计算的横向扩展能力
    • 美团图谱业务数据量可以达到千亿以上点边总数,吞吐量可达到数万qps,单节点部署无法满足存储需求。
  • 能够服务OLTP场景,具备毫秒级多跳查询能力
    • 美团搜索场景下,为确保用户搜索体验,各链路的超时时间具有严格限制,不能接受秒级以上的查询响应时间。
  • 具备批量导入数据能力
    • 图谱数据一般存储在hive等数据仓库中。必须有快速将数据导入到图存储的手段,服务的时效性才能得到保证。

我们试用了DB-Engines网站上排名前30的图数据库产品,发现多数知名的图数据库开源版本只支持单节点,不能横向扩展存储,无法满足大规模图谱数据的存储需求,例如:Neo4j、ArangoDB、Virtuoso、TigerGraph、RedisGraph。经过调研比较,最终纳入评测范围的产品为:NebulaGraph(原阿里巴巴团队创业开发)、Dgraph(原Google团队创业开发)、HugeGraph(百度团队开发)。

2. 测试概要

2.1 硬件配置

  • 数据库实例:运行在不同物理机上的Docker容器。
  • 单实例资源:32核心,64GB内存,1TB SSD存储。【Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz】
  • 实例数量:3

2.2 部署方案

  • Nebula v1.0.1
    Metad负责管理集群元数据,Graphd负责执行查询,Storaged负责数据分片存储。存储后端采用RocksDB。

    实例1 实例2 实例3
    Metad Metad Metad
    Graphd Graphd Graphd
    Storaged[RocksDB] Storaged[RocksDB] Storaged[RocksDB]
  • Dgraph v20.07.0
    Zero负责管理集群元数据,Alpha负责执行查询和存储。存储后端为Dgraph自有实现。

    实例1 实例2 实例3
    Zero Zero Zero
    Alpha Alpha Alpha
  • HugeGraph v0.10.4
    HugeServer负责管理集群元数据和查询。HugeGraph虽然支持RocksDB后端,但不支持RocksDB后端的集群部署,因此存储后端采用HBase。

    实例1 实例2 实例3
    HugeServer[HBase] HugeServer[HBase] HugeServer[HBase]
    JournalNode JournalNode JournalNode
    DataNode DataNode DataNode
    NodeManager NodeManager NodeManager
    RegionServer RegionServer RegionServer
    ZooKeeper ZooKeeper ZooKeeper
    NameNode NameNode[Backup]
    ResourceManager ResourceManager[Backup]
    HBase Master HBase Master[Backup]

3. 评测数据集

  • 社交图谱数据集:Linked Data Benchmark Council · GitHub
    • 生成参数:branch=stable, version=0.3.3, scale=1000
    • 实体情况:4类实体,总数26亿
    • 关系情况:19类关系,总数177亿
    • 数据格式:csv
    • GZip压缩后大小:194G

4. 测试结果

4.1 批量数据导入

4.1.1 测试说明
批量导入的步骤为:Hive仓库底层csv文件 → 图数据库支持的中间文件 → 图数据库。各图数据库具体导入方式如下:

  • Nebula:执行Spark任务,从数仓生成RocksDB的底层存储sst文件,然后执行sst Ingest操作插入数据。
  • Dgraph:执行Spark任务,从数仓生成三元组rdf文件,然后执行bulk load操作直接生成各节点的持久化文件。
  • HugeGraph:支持直接从数仓的csv文件导入数据,因此不需要数仓-中间文件的步骤。通过loader批量插入数据。

4.1.2 测试结果
数据库|导入方式|导入耗时|导入前占用(gziped)|导入后
占用|存储
放大比|节点存储均衡
-|-|-|-|-|-|-|-
Nebula|Hive->sst file
sst file->DB|3h
0.4h|194G|518G|2.67倍|176G/171G/171G
均衡
Dgraph|Hive->rdf file
rdf file->DB | 4h
8.7hOOM中断 |4.2G
数据无法全部导入
仅统计用户关系|24G|5.71倍|1G/1G/22G
偏斜
HugeGraph|Hive->csv
csv->DB|0h
9.1h磁盘满中断|4.2G
数据无法全部导入
仅统计用户关系|41G|9.76倍|11G/5G/25G
偏斜
4.1.3 数据分析

  • Nebula:数据存储分布方式是主键哈希,各节点存储分布基本均衡。导入速度最快,存储放大比最优。
  • Dgraph:原始194G数据在内存392G的机器上执行导入命令,8.7h后OOM退出,无法导入全量数据。数据存储分布方式是三元组谓词,同一种关系只能保存在一个数据节点上,导致存储和计算严重偏斜。
  • HugeGraph:原始194G的数据执行导入命令,写满了一个节点1000G的磁盘,造成导入失败,无法导入全量数据。存储放大比最差,同时存在严重的数据偏斜。

4.2 实时数据写入

4.2.1 测试说明

  • 向图数据库插入点和边,测试实时写入和并发能力。
    • 响应时间:固定的50,000条数据,以固定qps发出写请求,全部发送完毕即结束。取客户端从发出请求到收到响应的Avg、p99、p999耗时。
    • 最大吞吐量:固定的1,000,000条数据,以递增qps发出写请求,Query循环使用。取1分钟内成功请求的峰值qps为最大吞吐量。
  • 插入点
    • Nebula
      INSERT VERTEX t_rich_node (creation_date, first_name, last_name, gender, birthday, location_ip, browser_used) VALUES ${mid}:('2012-07-18T01:16:17.119+0000', 'Rodrigo', 'Silva', 'female', '1984-10-11', '84.194.222.86', 'Firefox')
      
    • Dgraph
      {
          set {
              <${mid}> <creation_date> "2012-07-18T01:16:17.119+0000" .
              <${mid}> <first_name> "Rodrigo" .
              <${mid}> <last_name> "Silva" .
              <${mid}> <gender> "female" .
              <${mid}> <birthday> "1984-10-11" .
              <${mid}> <location_ip> "84.194.222.86" .
              <${mid}> <browser_used> "Firefox" .
          }
      }
      
    • HugeGraph
      g.addVertex(T.label, "t_rich_node", T.id, ${mid}, "creation_date", "2012-07-18T01:16:17.119+0000", "first_name", "Rodrigo", "last_name", "Silva", "gender", "female", "birthday", "1984-10-11", "location_ip", "84.194.222.86", "browser_used", "Firefox")
      
  • 插入边
    • Nebula
      INSERT EDGE t_edge () VALUES ${mid1}->${mid2}:();
      
    • Dgraph
      {
          set {
              <${mid1}> <link> <${mid2}> .
          }
      }
      
    • HugeGraph
      g.V(${mid1}).as('src').V(${mid2}).addE('t_edge').from('src')
      

4.2.2 测试结果

  • 实时写入

    实时写入
    响应时间(ms)
    插入点 qps=100
    avg / p99 / p999
    插入边 qps=100
    avg / p99 / p999
    Nebula 3 / 10 / 38 3 / 10 / 34
    DGraph 5 / 13 / 54 5 / 9 / 43
    HugeGraph 37 / 159 / 1590 28 / 93 / 1896
    实时写入
    最大吞吐量
    插入点
    qps
    插入边
    qps
    Nebula 84000 76000
    DGraph 10138 9600
    HugeGraph 410 457

4.2.3 数据分析

  • Nebula:如4.1.3节分析所述,Nebula的写入请求可以由多个存储节点分担,因此响应时间和吞吐量均大幅领先。
  • Dgraph:如4.1.3节分析所述,同一种关系只能保存在一个数据节点上,吞吐量较差。
  • HugeGraph:由于存储后端基于HBase,实时并发读写能力低于RocksDB(Nebula)和BadgerDB(Dgraph),因此性能最差。

4.3 数据查询

4.3.1 测试说明

  • 以常见的N跳查询返回ID,N跳查询返回属性,共同好友查询请求测试图数据库的读性能。
    • 响应时间:固定的50,000条查询,以固定qps发出读请求,全部发送完毕即结束。取客户端从发出请求到收到响应的Avg、p99、p999耗时。
      • 60s内未返回结果为超时。
    • 最大吞吐量:固定的1,000,000条查询,以递增qps发出读请求,Query循环使用。取1分钟内成功请求的峰值qps为最大吞吐量。
    • 缓存配置:参与测试的图数据库都具备读缓存机制,默认打开。每次测试前均重启服务清空缓存。
  • N跳查询返回ID
    • Nebula
      GO ${n} STEPS FROM ${mid} OVER person_knows_person
      
    • Dgraph
      {
       q(func:uid(${mid})) {
         uid
         person_knows_person { #${n}跳数 = 嵌套层数
           uid
         }
       }
      }
      
    • HugeGraph
      g.V(${mid}).out().id() #${n}跳数 = out()链长度
      
  • N跳查询返回属性
    • Nebula
      GO ${n} STEPS FROM ${mid} OVER person_knows_person YIELDperson_knows_person.creation_date, $$.person.first_name, $$.person.last_name, $$.person.gender, $$.person.birthday, $$.person.location_ip, $$.person.browser_used
      
    • Dgraph
      {
        q(func:uid(${mid})) {
          uid first_name last_name gender birthday location_ip browser_used
          person_knows_person { #${n}跳数 = 嵌套层数
            uid first_name last_name gender birthday location_ip browser_used
          }
        }
      }
      
    • HugeGraph
      g.V(${mid}).out()  #${n}跳数 = out()链长度
      
  • 共同好友查询语句
    • Nebula
      GO FROM ${mid1} OVER person_knows_person INTERSECT GO FROM ${mid2} OVER person_knows_person
      
    • Dgraph
      {
        var(func: uid(${mid1})) {
          person_knows_person {
            M1 as uid
          }
        }
        var(func: uid(${mid2})) {
          person_knows_person {
            M2 as uid
          }
        }
        in_common(func: uid(M1)) @filter(uid(M2)){
          uid
        }
      }
      
    • HugeGraph
      g.V(${mid1}).out().id().aggregate('x').V(${mid2}).out().id().where(within('x')).dedup()
      

4.3.2 测试结果

  • N跳查询返回ID

    N 跳查询 返回ID
    响应时间(ms)
    n = 1, qps=100
    avg / p99 / p999
    n = 2, qps=100
    avg / p99 / p999
    n = 3, qps=10
    avg / p99 / p999
    Nebula 4 / 13 / 45 24 / 160 / 268 1908 / 11304 / 11304
    Dgraph 4 / 12 / 30 93 / 524 / 781 60s内未返回
    HugeGraph 28 / 274 / 1710 60s内未返回 60s内未返回
    N 跳查询 返回ID
    最大吞吐量(qps)
    n = 1
    平均返回邻居数=62
    n = 2
    平均返回邻居数=3844
    n = 3
    平均返回邻居数=238328
    Nebula 80830 6950 32
    Dgraph 8558 100 60s内未返回
    HugeGraph 804 60s内未返回 60s内未返回
  • N跳查询返回属性
    单个返回节点的属性平均大小为200 Bytes。

    N 跳查询 返回属性
    响应时间(ms)
    n = 1, qps=100
    avg / p99 / p999
    n = 2, qps=100
    avg / p99 / p999
    n = 3, qps=10
    avg / p99 / p999
    Nebula 5 / 20 / 49 99 / 475 / 1645 48052 / >60s / >60s
    Dgraph 50 / 210 / 274 60s内未返回 60s内未返回
    HugeGraph 29 / 219 / 1555 60s内未返回 60s内未返回
    N 跳查询 返回属性
    最大吞吐量(qps)
    n = 1
    平均返回邻居数=62
    n = 2
    平均返回邻居数=3844
    n = 3
    平均返回邻居数=238328
    Nebula 36400 730 2
    Dgraph 80 60s内未返回 60s内未返回
    HugeGraph 802 60s内未返回 60s内未返回
  • 共同好友
    本项未测试最大吞吐量。

    共同好友
    响应时间(ms) | qps = 100
    avg / p99 / p999
    -|-|-
    Nebula|4 / 20 / 42
    Dgraph|4 / 13 / 36
    HugeGraph | 39 / 366 / 1630

4.3.3 数据分析

  • 在1跳查询返回ID「响应时间」实验中,Nebula和DGraph都只需要进行一次出边搜索。由于DGraph的存储特性,相同关系存储在单个节点,1跳查询不需要网络通信。而Nebula的实体分布在多个节点中,因此在实验中DGraph响应时间表现略优于Nebula。
  • 在1跳查询返回ID「最大吞吐量」实验中,DGraph集群节点的CPU负载主要落在存储关系的单节点上,造成集群CPU利用率低下,因此最大吞吐量仅有Nebula的11%。
  • 在2跳查询返回ID「响应时间」实验中,由于上述原因,DGraph在qps=100时已经接近了集群负载能力上限,因此响应时间大幅变慢,是Nebula的3.9倍。
  • 在1跳查询返回属性实验中,Nebula由于将实体的所有属性作为一个数据结构存储在单节点上,因此只需要进行【出边总数Y】次搜索。而DGraph将实体的所有属性也视为出边,并且分布在不同节点上,需要进行【属性数量X * 出边总数Y】次出边搜索,因此查询性能比Nebula差。多跳查询同理。
  • 在共同好友实验中,由于此实验基本等价于2次1跳查询返回ID,因此测试结果接近,不再详述。
  • 由于HugeGraph存储后端基于HBase,实时并发读写能力低于RocksDB(Nebula)和BadgerDB(Dgraph),因此在多项实验中性能表现均落后于Nebula和DGraph。

5. 结论

参与测试的图数据库中,Nebula的批量导入可用性、导入速度、实时数据写入性能、数据多跳查询性能均优于竞品,因此我们最终选择了Nebula作为图存储引擎。

6. 参考资料

本次性能测试系美团NLP团队高辰、赵登昌撰写

9 个赞

:clap: :clap:

这个可以分享下怎么做的吗?

用论坛右上角的搜索功能

:+1:

:+1:

请教下这里的小并发和大并发指的是QPS多少,是QPS100,n=1吗

已修改表述。
“小并发1跳查询返回ID实验中” => “在1跳查询返回ID「响应时间」实验中”
“在大并发1跳查询返回ID实验中” => “在1跳查询返回ID「最大吞吐量」实验中”

3 个赞

感谢分享,我们做过的测试中,Dgraph性能和blog中吻合,在实际使用过程中,也确实越来越发现更多的弊端。期望nebula会有更好的表现

7 个赞

看到关于 HugeGraph 的一个评测 ,咨询下,这里HBase 表有做预分区吗

应该没有。。。我用比这差的配置,spark单task顶点都有几千的qps

测试时HBase没做预分区, 整体写入性能确实要差一点。 上面测试报告里有评测使用的数据集跟机器配置,对HugeGraph比较熟悉的同学可以帮忙重新跑下测试场景、补充测试数据~

3 个赞

你好, 能分享下这个数据生成器的jar包吗?

你好, 关于这篇"主流开源分布式图数据库Benchmark", 你们有测过吗?
能分享这个数据生成器的jar包吗?

这个不是我们测试的 我目前也没有这个jar 可以问下本文作者

看起来是来自 GitHub - ldbc/ldbc_snb_datagen: Synthetic data generator for the LDBC Social Network Benchmark

从项目的 README 看起来是 tools/build.sh 就能够 build出来,我测试了一下(没玩过maven/java, 防止节外生枝用docker来跑它),下边是我尝试的步骤,是可行的。

$ git clone https://github.com/ldbc/ldbc_snb_datagen.git && cd ldbc_snb_datagen

$ docker run -it --rm --name my-maven-project -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 mvn verify

Unable to find image 'maven:3.3-jdk-8' locally
3.3-jdk-8: Pulling from library/maven
6d827a3ef358: Pull complete
2726297beaf1: Pull complete
7d27bd3d7fec: Pull complete
e61641c845ed: Pull complete
cce4cca5b76b: Pull complete
6826227500b0: Pull complete
c03b117ffd91: Pull complete
821a1547b435: Pull complete
2bd47f6b1b42: Pull complete
e4cf3e9f705c: Pull complete
3733107c5c01: Pull complete
Digest: sha256:18e8bd367c73c93e29d62571ee235e106b18bf6718aeb235c7a07840328bba71
Status: Downloaded newer image for maven:3.3-jdk-8
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building ldbc_snb_datagen 0.4.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: https://repo1.maven.org/maven2/net/alchim31/maven/scala-maven-plugin/4.3.1/scala-maven-plugin-4.3.1.pom
...
model contains 234 documentable templates
[INFO] Building jar: /usr/src/mymaven/target/ldbc_snb_datagen-0.4.0-SNAPSHOT-javadoc.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 06:32 min
[INFO] Finished at: 2021-05-06T11:24:25+00:00
[INFO] Final Memory: 100M/732M
[INFO] ------------------------------------------------------------------------


$ docker run -it --rm --name my-maven-project -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 tools/build.sh
...
Downloaded: https://repo1.maven.org/maven2/com/fasterxml/jackson/jackson-parent/2.8/jackson-parent-2.8.pom (8 KB at 25.9 KB/sec)
Downloading: https://repo1.maven.org/maven2/com/fasterxml/oss-parent/27/oss-parent-27.pom
Downloaded: https://repo1.maven.org/maven2/com/fasterxml/oss-parent/27/oss-parent-27.pom (20 KB at 68.7 KB/sec)
[INFO] Building jar: /usr/src/mymaven/target/ldbc_snb_datagen-0.4.0-SNAPSHOT-jar-with-dependencies.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 06:31 min
[INFO] Finished at: 2021-05-06T11:31:24+00:00
[INFO] Final Memory: 80M/1065M
[INFO] ------------------------------------------------------------------------

[wei.gu@wey ldbc_snb_datagen]$ ls -l target
total 125404
drwxr-xr-x. 2 root root       41 May  6 11:30 analysis
drwxr-xr-x. 2 root root        6 May  6 11:31 archive-tmp
drwxr-xr-x. 7 root root      153 May  6 11:30 classes
-rw-r--r--. 1 root root 49662367 May  6 11:30 ldbc_snb_datagen-0.4.0-SNAPSHOT.jar
-rw-r--r--. 1 root root 49445147 May  6 11:31 ldbc_snb_datagen-0.4.0-SNAPSHOT-jar-with-dependencies.jar
-rw-r--r--. 1 root root  1058729 May  6 11:24 ldbc_snb_datagen-0.4.0-SNAPSHOT-javadoc.jar
drwxr-xr-x. 2 root root       28 May  6 11:23 maven-archiver
-rw-r--r--. 1 root root 28237618 May  6 11:30 original-ldbc_snb_datagen-0.4.0-SNAPSHOT.jar
drwxr-xr-x. 3 root root       23 May  6 11:24 site
drwxr-xr-x. 2 root root     4096 May  6 11:23 surefire-reports
drwxr-xr-x. 4 root root       34 May  6 11:23 test-classes

你好, wey
首先感谢你哈, docker确实应该方便一些, 我在公司是内网, 连不通外网, 所有我首先考虑用java的方式打包, 可还是有些jar包, 我们公司的内部仓库没有, 那我周末回去用自己电脑试试
谢谢

1 个赞

如果你需要可以微信联系我把生成的jar文件传给你,我的微信id如下

echo c2l2dmVpCg== | base64 -d

你好, wey
感谢, 感谢, 我们先用我们数据集测, 后续如果有需要找你 :+1: :+1: :+1:

1 个赞

你好, wey
咨询你下有关nebula-import 的"concurrency"和batchSize 这两个值有没有什么参考值, 相同的数据, 第一次我导入用了5个小时(并发是10, batchSize=10), 第二次都调成了50就只用了1个小时, 那我就搞不懂它的极限速度到底在哪里?, 如何根据导入数据的大小合适设置这2个值?
image
谢谢!