在 Spark 数据导入中的一些实践细节

497 阅读6分钟

best-practices-import-data-spark-nebula-graph

本文由合合信息大数据团队柳佳浩撰写

1.前言

图谱业务随着时间的推移愈发的复杂化,逐渐体现出了性能上的瓶颈:单机不足以支持更大的图谱。然而,从性能上来看,Neo4j 的原生图存储有着不可替代的性能优势,这一点是之前调研的 JanusGraph、Dgraph 等都难以逾越的鸿沟。即使 JanusGraph 在 OLAP 上面非常出色,对 OLTP 也有一定的支持,但是 GraphFrame 等也足以支撑其 OLAP 需求,更何况在 Spark 3.0 会提供 Cypher 支持的情况下,图谱的 OLAP 需求相比 OLTP 有更多途径可以解决。这个时候,Nebula Graph 的“横空出世”无疑是对分布式 OLTP 效率低下现状的一种突破。

之前在各类调研、部署后,特别是从 JanusGraph 的 OLTP 效率最终测试发现无法满足线上需求之后,我们不再对同一图谱可以同时进行 OLAP 和 OLTP 进行强制性要求,而 Nebula Graph 的架构刚好符合图谱方面的需要:

  1. 分布式——shared-nothing 分布式架构
  2. 高速 OLTP(性能需要和 Neo4j 相近)——Nebula Graph 的存储层架构查询直接映射物理地址,实际上可以算是原生图存储
  3. 服务的高可用(即在非人为情况下,图谱可以稳定提供服务)——局部失败服务可用、有快照机制
  4. 保证可扩展性——支持线性扩容,由于开源、支持二次开发

综上所述,Nebula Graph 架构上符合实际生产需求,因此对 Nebula Graph 进行了调研、部署、测试。关于部署、性能测试(美团 NLP 团队性能测试腾讯云安全团队性能测试)的部分无论是官网还是其他同学在博客中都有比较详尽的数据,本文主要从 Spark 导入出发,算是对 Nebula Graph 对 Spark 的支持进行粗浅的理解。

2.测试环境

  1. Nebula Graph 集群
    1. 3 台 32 c(实际限制了16 c)
    2. 400 G 内存(实际配置了 100 G)
    3. SSD
    4. 版本信息:Nebula Graph 版本 1.0.0(当时测试比较早)。
  2. 网络环境:万兆。
  3. 图谱大小:十亿级别节点(属性较少),百亿级别边(有向,无属性或带权值)。
  4. Spark 集群
    1. 版本信息:Spark 2.1.0

实际上 Nebula Graph 的使用资源合计 2T 左右 memory (3 * 30 executor + 1 driver) * 25G。

3.Spark 批量导入

3.1 基础流程

  1. 打包 sst.generator(Spark 生成 sst 所需要的包)。
  2. 配置 Nebula Graph 集群,Nebula Graph 集群正常启动,创建图谱。
  3. Spark 配置文件 config.conf(可以参考文档《Spark 导入工具》)进行配置。
  4. 排查 Spark 集群是否存在冲突的包。
  5. Spark 启动时使用配置文件和 sst.generator 快乐地导入。
  6. 数据校验。

3.2 一些细节

  1. 批量导入前推荐先建立索引

这里推荐先建立索引的原因是:批量导入仅在非线上图谱进行,虽然建立索引可以选择是否在提供服务的同时进行,但是为了防止后续 REBUILD 出现问题,这边可以优先建好索引。带来的问题就是在批量导入结点时相对较慢。

  1. 推荐用 int 型节点 ID(可以使用 Snowflake算法 等),如果节点的 ID 不是 int 型,这里可以通过在节点/边中加入 policy: "uuid" 来设置自动生成 uuid。

  2. 如果使用的是单独的 Spark 集群可能不会出现 Spark 集群有冲突包的问题,该问题主要是 sst.generator 中存在可能和 Spark 环境内的其他包产生冲突,解决方法是 shade 掉这些冲突的包,或者改名。

  3. Spark 调优方面:可以根据实际情况调整参数,尽量降低 memory 以节约资源,相对的可以适当提高并行度加速。

3.3 导入结果

十亿级别节点(属性较少),百亿级别边(有向,无属性或带权值),提前建好索引的情况下大约消耗 20 小时左右导入全图。

3.4 关于 PR

因为在较早的版本使用了 Spark 导入,自然也有一些不太完善的地方,这边也提出了一些拙见,对 SparkClientGenerator.scala 略作了修改。

  1. 最早在使用 Spark Writer(现:Exchange) 写入 Nebula Graph 时,发现错列的问题。

通过看源码发现 SparkClientGenerator.scala 存在 BUG,读取的是配置文件的位置而非 parquet/json 文件的位置,修复后提了我第一个 PR#2187,有幸通过

  1. 后续发现使用 SparkClientGenerator 自动生成 uuid/hash 功能时,存在会出现重复的双引号的问题,导致无法导入。

这块可以说是由于解决问题的想法不同,提交了好多次。重复引号的问题归根结底是对类型转化的时候添加了一次双引号,我这边发现有个 extraIndexValue 的方法可以把用户自填的非 string 类型的转成 string 类型,我这边想着可能会有用户想把非 string 型的 index 转成 uuid/hash(比如 array),所以修改的比较多。

但是和官方 @darionyaphet 沟通后,发现我这种做法其实是对数据源进行了修改,用户传 array 等不支持的类型时,应该报错而不是转换类型(这个确实,一开始只考虑到了逻辑上跑通以及自己这边业务的使用,没考虑通用性)。重新修改,提交 PR #2258,通过。经过这次 PR 我也学到了很多。

  1. 之后发现 nebula-python 也有和官方 thrift 冲突的问题,本来想 shade 后提 PR,但是觉得这个改动太大了,所以直接提给官方,近期也修复了。

Nebula Graph 旁白:欢迎社区小伙伴来 GitHub 给我们提 PR,GitHub 传送门:github.com/vesoft-inc/…

4.总结 & 展望

因为之前调研过 JanusGraph,Nebula Graph 给我的第一印象就是:暗坑相对较少、社区反馈非常及时。在测试后 Nebula Graph 又用她的效率证明了自己,成为了分布式图谱的首选项。

Nebula Graph 社区、群组、PR 官方反馈非常及时,这是图谱迅速、茁壮成长的不可替代的重要因素,也希望可以后续可以继续见证 Nebula Graph 的成长,继续为 Nebula Graph 生态的完善添砖加瓦!

喜欢这篇文章?来来来,给我们的 GitHub 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]

Nebula Graph Meetup 深圳场报名中:www.huodongxing.com/event/45723…,期待你来现场交流技术 😊

best-practices-import-data-spark-nebula-graph