第一期 Happy Office Hour 线上交流会因为时间的关系,有 3 家公司参与了本次交流。因为企业信息敏感的缘故,本文经过脱敏后,现将 NebulaGraph 使用技能分享给大家一起研究探讨下,同这 3 家企业交流过程中遇到的产品需求,可移步至本文文末查看。如果你觉得这个需求同你的业务需求相似,不妨给它投上一票,人工提高它的开发优先级。
本文目录
- 多场景、百亿数据的企业 H1
- 合理的部署方式
- flink-connector 导入的性能
- Partition 的规划
- 一般的性能瓶颈
- TTL 的删除速率
- 数据血缘的企业 H2
- 模糊检索
- 权限控制
- 主备集群
- 特征存储的企业 H3
- 存储的数据类型性能
- 存储取数
- 问题定位和调试
- meta 性能
- 内存控制
- 端口切换
这里用 H1、H2、H3 作为代号指代这三家公司,Muyi 指代 NebulaGraph 产品总监方扬,Yee 指代 NebulaGraph 资深研发伊兴路、Critical 为资深研发王玉珏,亚军为 NebulaGraph 实施陈亚军缩写;
多场景、百亿数据的企业 H1
H1:科技公司,目前业务信息:
- 状态:已在上线上使用 NebulaGraph
- 数据量:200 TB
- 单机器标准配置:CPU 104c,内存 700 GB,磁盘 4 块 2.9 TB 的 SSD
- 业务场景:
- 传统的知识图谱,主要解决风控问题。系统存储用户的相关数据,以离线数据为主。数据量在百亿级别,为 T+1 型数据;
- 特征工厂,采用图数据库来存储特征。由于用户的特征较大,单个点的属性值可长达 200k。此外,这块的写并发较高,每秒 15w 左右点数据写入;
- 广告推荐,主要为图谱召回,进行 u2i 和 u2u 相关数据检索;
- 集群:10+ 套集群
- 版本:v3.2.0 和 v3.4.0;
合理的部署方式
H1:目前,NebulaGraph 开源版本有过实践的集群规模最大是多少节点,及其是什么配置、什么数据量级,meta、graph、storage 如何部署?
Muyi:据我所知,NebulaGraph 客户最大的集群规模是百台 graphd + storaged。这个客户采用的部署方式是容器化部署,但其实理论上如果你的集群部署使用物理机和虚拟机的话,性能可能会更佳。
flink-connector 导入的性能
H1:目前,我们的配置就是上面罗列的那样:裸金属,104c,700 GB,单机 4 块 SSD 共计 10 TB 的配置。而我们进行资源评估时,如果是 3 副本,单磁盘(存储)方面,按点和边来算的话,在特征工厂业务场景下,数据的窗口期是 2 小时,在此期间会有 100 TB、十几亿的数据流动,需要 70~80 台上面所罗列的单机配置。
我们之前用 24 台标准配置,partition 设 100,发现数据导入的 IO 和磁盘容量并没有达到瓶颈,但 CPU 已经达到上限,graphd 内存会打满。而导入速率这块,24 台单副本、50k 一条数据的话,导入速度是 11w/s,远低于我们的目标是 78w/s。在测试过程中,我们发现 24 台机器的情况下,单台机器的 IO 能达到 200+ MB/s;而缩小集群为 3 台的话,最高速度能到 300 MB/s。
基于测试的这种情况,我们根据业务规模,估算了下大概要 70~80 台机器。这是按照存储来估算的,如果按照吞吐来算的话,机器台数会更多。
所以,在这种情况下,我们如何优化写入性能?提高吞吐呢?
亚军:首先看下导入速率的瓶颈在哪里。
H1:瓶颈在 graph 这边,我们用上了所有的 graph 服务,并发起来之后,graph 这边的 CPU 消耗能到 99%。在一些场景下,像 800 个 Flink task 的情况下,将并发从 800 提升到 2,000,基本额上没有导入的吞吐增长。800 并发的导入性能是 11 w/s,拉到 2,000 并发,还是 11 w/s,这时候,CPU 消耗非常高。三台机器规模的情况下,通过火焰图观测到 graph 解析 nGQL 耗时中
nebula:GraphScanner:~GraphScanner/yyFlexer:yy_get_previous_state
消耗了 75% 的时间,剩下的 25% 花在了 NET IO 那边。这是使用 Flink 3.3 的情况下,如果切换到 Flink 3.5 的话,这个函数消耗能达到 90%,这时候 NET IO 在 10% 左右。
亚军:根据过往的实施经验,并发并非越大导入性能越好。在你这种情况下,800 并发可能设定稍大。你这边单条 INSERT 语句的长度是多长?batch size
设置多大?
H1:batch size 设定是 24w,数据是 100 ms 写入一次。我们看过统计,在单条记录 50k 的情况下,每次可能只能写入几十条。这边数据导入的模式是 kv 形式的,vid 就是 key,value 的话是一个长字符串。value 中存储的是特征,因为没有做属性的划分,所以一股脑地将特征存储到字符串,交由上层来处理。
Yee:这个 case 比较特殊,相当于一个字段承载所有的属性。从上面的描述来讲,是 Parser 解析的问题,Flex/Bison 解析初始的语句的字符串比较耗时。
H1:目前,我们这边能统计到的 IO,如果是 24 台机器的集群,大概是 200 MB/s;而论坛之前有用户分享过导入性能,吞吐差不多是 100 MB/s。这里想问下,一块 SSD 是 100 MB 的话,是不是说明 Flink 和 NebulaGraph 结合导数的话,差不多极限就是这个速度?
Muyi:如果说这个数据没法细分做切分,所有数据都加载过来的话,应该只能通过添加 graphd 的机器来提速了。
H1:如果是添加 graphd 的话,对应的连接数会变高,这时候会不会导致大量的 NET IO 消耗,最后占大量的 CPU 资源?
Yee:在我们进行数据导入的时候,一般都是通过连接池配置好特定的线程数去和 graphd 进行连接,而线程内部会进行负载均衡(load balance),不需要每个 graphd 都创建 800 个连接。像 nebula-importer 的话,是支持配置一个 graphd 的列表,内部会轮询,发送数据。正如亚军所说的,导入性能要高的话,并不要求客户端并发设置很高,才能提高吞吐和速度。 Muyi:建议可以增加 graphd 的数量,再调小 batchSize,因为你们的 value 比较大。此外,可以建议你换 nebula-importer 进行下压测,因为它是本地读 csv 文件,再导入到 NebulaGraph 中。在 nebula-importer 侧做些配置,可能会有个不错的性能。瓶颈在哪块,就增加对应的配置。
亚军:之前我单副本、3 台物理机,用 LDBC SF100 的数据集、万兆网络,单副本的情况下最高的速率是 600 w/s。建议你可以用标准的数据集测试下你们机器的导入数据,看下情况。关于 LDBC-SNB 数据集,具体请参考以下文档:
- LDBC-SNB Specification:https://ldbcouncil.org/ldbc_snb_docs/ldbc-snb-specification.pdf
- LDBC-SNB Docs:https://github.com/ldbc/ldbc_snb_docs
- LDBC-SNB 测试数据集生产工具:https://github.com/ldbc/ldbc_snb_datagen_spark
Partition 的规划
H1:文档上建议说集群的总磁盘 x 20 这个数量级,这个是比较通用的么?
Yee:考虑到阔缩容的需求,一般建议是磁盘数的 10 倍以上。
H1:我们的实际测试中,如果 24 台机器、100 个 Space 的情况下,Partition 设定 1,000,整个集群容易 Offline、各种 SHOW 语句会明显地卡顿,以及 Storage Offline 的问题遇到的概率也会增大。
Yee:Partition 不一定是越大越好,里面会涉及到很多的资源分配。你设置 Partition 多了的话,是会比较占资源,里面的线程间也会有并发的竞争。
Critical:你举的这个例子,Space 数量已经不小了,这个时候仍然设置为 1,000 个 Partition,是有可能出现资源不够的问题的。
亚军:我们之前有个客户,数据量也挺大的,机器比较多,Partition 配置在 810, 集群运行了很久非常稳定。
H1:此外,我们的 Heartbeat 的值设置在了 30s,两个心跳时间之后响应服务,是不是有点慢?
亚军:设置成默认值(10s)就够。
一般的性能瓶颈
H1:一般来说,性能瓶颈会出现在哪块呢?是 graph 还是 CPU 内存,这块有没有什么分享的经验?
亚军:大部分我们这边碰到的瓶颈是出现在 Compaction,比较少见到你们这种瓶颈在 graph 的情况。所以之前你说 graph 被打爆了,第一反应就是 800 的并发太大了,其实用不到那么大的并发。一般来说,实施这边常遇到的是,用户的 VID 长度设置得太大、Compaction Job 没调,Partition 数量设置的太小等这些都是比较影响性能的。
TTL 的删除速率
H1:前面我们说过特性场景下,我们的数据窗口期是 2 个小时,没有办法在一边写入 400 MB/s 数据落盘的情况下,打开 Compaction,又借助 TTL 删掉 400 MB/s 的数据呢?目前,我们观察到的现状是虽然 TTL 也在删数据,但是删除的速率是远低于导入的数据的。
Critical:你可以看下这个 pr #5622,修改 min_level_for_custom_filter
这个参数。这个参数的值越小,删除 TTL 的速度会越快。如果还达不到预期,可以不断手动进行 Compaction。
数据血缘的企业 H2
H2:科技公司,目前业务信息:
- 状态:已在上线上使用 NebulaGraph
- 数据量:10 万
- 单机器标准配置:8c、16 GB
- 业务场景:
- 数据血缘和数据分析,在提供给上层业务同学进行找数据服务的过程中,业务同学会配置一些数据源、指标表等内容,但这些指标和数据源之间或存在调用、上下游关系,目前是借助图数据库来实现这块的数据治理。
- 版本:v3.6.0
模糊检索
H2:我们这块有功能模块是需要用到模糊检索的,用户在这个模块配置他所需的关键字信息之后,可以匹配出相关的一些列表、视图等等字段信息。目前,我们使用 NebulaGraph 的全文索引的 3.6.0 版本。我们全文索引的构建方式是单独创建一个 Node(tag),里面存储其他 Node(tag) 的关键信息,基于这个单独的 Node(tag) 再创建全文索引。
Muyi:这个做法不是特别的通用,一般来说的话,可以基于多个 tag 属性创建索引,然后全文索引查询的时候对这些属性进行统一查询。比如说,tag1 有 name 属性,tag2、tag3 也是,你可以创建这 3 个 tag name 属性的全文索引,做相关查询的时候对这 3 个全文索引进行检索,再最后做个 union 合并下。
亚军:其实 H2 目前的用法,在这个业务场景下更合适,相当于利用多 tag 的特性,为所有其他的 tag 创建一个公共的 tag 来存放用来搜索的属性,基于这个属性创建一个全文索引,根据这个全文索引再找到对应 VID 匹配到对应的点。新的 tag 相当于就是为了全文索引专用。
H2:我们这边还有一个额外的关联性需求,就是关联性高的话,结果排序更靠前。
Muyi:在 v3.6.0 开始,ES 那边会有一个得分项,这个得分相当于是原始 ID 的分值。当你做一度查询时,将其乘以一个系数;在做二度查询时,再将其乘以一个系数,这样全文的关联性系数就能全部获得,最后你再做一个整体的得分排序,就行了。
H2:所以这个排序主要是在 ES 端实现的?
Muyi:不是的,ES 其实只会返回你一次全文索引的分数,你可以基于这个分数在做 MATCH 之类的查询时,做一度、二度的扩展,再去计算每个邻居的分数。
Yee:其实在这里有一个歧义点时,这个关联性是文本的关联度呢?还是点边关系的关联性。如果是文本的关联性的话,是在 ES 实现的;而后者则是在图数据库中实现的。NebulaGraph 全文索引这块的话,返回查询结果时,不只是返回结果,还会把 ES 当中的打分(即:score)返回,可以基于它的打分来判断关联性。
H2:这个打分的功能时在什么版本上支持的?
Yee:最新的 v3.6.0 中对全文索引进行了重构,支持了 score 功能。
权限控制
H2:因为这块是数据血缘场景,涉及到一些表、数据源的权限管控。不知道 NebulaGraph 这块是如何数据管控?
Muyi:目前点、边颗粒度的权限,是在企业版上支持的。如果是你们这边的话,可以在应用层进行权限管控。
Yee:内核这边权限的配置是存在元数据中,每次查询时元数据会查阅数据中该用户对应的 tag、edge 的操作是否有访问权限,比如读权限还是写权限。在查询层(语言层)就能进行一些权限验证,而这些验证遵循 RBAC 的访问控制,看给哪些用户配置了哪些表的读权限和写权限。
H2:看样子不是基于应用层的权限来做的权限管控?
Yee:应用层业务的话,一般来说都有一套账号体系对应到图数据库这边,应用那边有一个 user,对应到图数据库这边也会有一个 user,至于他能访问哪些点边,就要参考应用层那边的原始设定。
主备集群
H2:这里再问一个数据迁移的问题。之前大促时,一个 8c、16g 集群突然 CPU 被打满了,临时紧急申请了一个 32c、64g 的三台机器去替换。想问下 NebulaGraph 这边有没有类似的主从机制,这样我们可以申请两套机器,一个线上用,另一个做备用。遇到故障的话,可以快速做一个切换。
Muyi:这种主备场景的话,目前是在企业版上做的支持。社区有一篇文章[《架构设计——基于 raft-listener 实现的 nebula 实时同步的高效双集群主备环境》(架构设计——基于 raft-listener 实现的 nebula 实时同步的高效双集群主备环境) 这里提供了一个实现思路,需要进行一些定制化开发工作。不过,回到你这个例子,其实你 CPU 被打爆的话,应该不是主备集群能搞定的事情,而是你本身的业务压力有点大了,你可以走分布式路线,扩充你的 storage 和 graph。
特征存储的企业 H3
H3:科技公司,目前业务信息:
- 状态:业务开发适配中
- 数据量:100 亿
- 集群配置:9台(32c、64 GB 内存、4T 硬盘)
- 业务场景:
- 特征数据存储:从用户的行为历史数据提取出特性,再存储到 NebulaGraph 中做下一步的数据处理;
- 动态实时计算:对 KV 抽取的性能要求高;
存储的数据类型性能
H3:因为特征场景,可能我们需要处理一些 Embedding 和 Binary 的数据类型。虽然 NebulaGraph 这两个类型不支持,我们内部进行了数据结构的扩展,将其序列化成一个二进制,存储到 NebulaGraph 中。目前来看,这种实现方式存成二进制和 String 性能上来看,差不多。因为像 SQL 语句存成 Binary 的话,要通过 Base64 进行转化,和纯 String 相比,除了底层上会有 25% 的数据膨胀的减少之外,其他的效果并不是很明显。同样的 Embedding 类型因为 Java 客户端不是很好适配,所以在 Java 客户端进行数据抽取时,要进行转换,将 Embedding 转化成 List 类型,这个性能开销也比较大。这块性能优化,有什么建议么?个人觉得,Embedding 这块的耗时是在序列化和反序列化。
Yee:之前 NebulaGraph 规划过 List 数据类型的支持,尤其是同构的数据类型,像是 List Double。不过这种结构可能对解决一维数据比较有用,而你的数据如果是二维的话,便是一个矩阵,并且可能不是一个规范矩阵:第一个 List 里面是三维数据、第二个 List 是多维数据…
H3:这边的话数据的话都是规范矩阵,不只是支持一维、二维,还要支持高维数据。但是它还是非常规范的矩阵,比如说它是二维的话,第二维就会都是三维或者是四维,不会出现第二维度不一致的情况。数据类型也是规整的,都是整型或者都是浮点型。数据结构这块我们已经内部实现了,但是最大的问题是查询性能不是很好。
Yee:查询的话,主要是基于什么场景下的查询?
H3:主要是 GO 语句,从一个点出发,去查询另外一个点,找到存在这个点上面的 Embedding 属性,而这个 Embedding 属性大概在 200 位左右,Value 比较大。
Yee:这边查询性能不好的话,其实要具体看下哪块有问题。是因为数据量太多,它要扫磁盘,导致时延比较高,还是因为它往外拓展找关联关系的时候,查询耗时比较高。
H3:耗时的话,其实有一个原因是我们的客户端对新的数据类型没有做适配,取数的时候需要做一层转译。Embedding 数据从 Storage 存储取出来之后,到 graph 那边时,需要反序列化成我自定义的 Embedding 结构之后,Java 客户端因为没有适配,需要转成 List 这种我们现在支持的数据类型,上层才能读到数据。我觉得这个是比较大的瓶颈。
Yee:这个没关系的,因为数据返回的时候,Server 端不是有一个 latency 的统计么?Server 端的统计是不包含网络传输和客户端的反序列化耗时的。
H3:所以我理解,要想性能好的话,客户端这边的话就不做类型转换,不访问它的数据结构。其实我之前有想过直接支持 value 类型,直接将序列化的内容存储到 Storage 中,然后取数的话也是直接把序列化的内容传给 graph,graph 再把序列化的内容填充到 RPC 的 Protocol 协议中。
Yee:value 的话,它的 Overhead 会比较高。空间占用也会比较大,像一个 bool 类型,存 1 个 value 的话,它本身字节就 24 个字节,这块的空间就会很浪费。如果是 List 类型的话,这块的空间占用问题就不会那么显著。但是你如果存储一些基础类型的话,就会比较明显。不过一旦存储那边读出来数据,差异就不大。因为内核现在查询和存储的传输,也是用的 value。
存储取数
H3:目前我们这边有一个需求:单独弄一个接口用来做 KV 接口。这个接口主要是屏蔽掉查询这边的一些执行计划的流程,直接 Fetch 取数不通过执行计划,调用 Storage 的接口去取数。这样性能会提升不少。
Yee:其实 NebulaGraph 有 storage client,它的作用和你说的这个是类似的。你可以直接调用 storage 客户端来满足你这个业务需求。本质上就是压 RocksDB 的性能。
问题定位和调试
H3:这边日常会遇到一个定位的问题,不知道 NebulaGraph 这边有什么调试的方法。我这边如果要定位一些问题的话,因为线上的 Space 比较多可能是几个百,每个 Space 又有上百个 Partition,这时候如果打开日志的话,日志级别打到一级或者三级,日志刷刷刷地写得飞快,要在那么多信息中找到想要的日志信息,是挺难的。不知道这边有没有一个功能,说针对某个 Partition 打开对应的日志,方便做后续的调试或者定位。
Yee:日志这块我们是用的 glog 实现,glog 有一个 vmodule,可以基于它过滤一些日志。
Muyi:如果你的实时性不高的话,可以将日志读取到 ES 中,进行读取和分析。
H3:其实我还想问,像 NebulaGraph 日常定位的话,是只通过日志,还是有什么其他手段在定位问题上会比较快速一点?
Yee:如果是线上定位的话,依赖日志会比较多点。如果说是研发定位的话,手段就相对多了。你可以做断言、Chaos 测试、覆盖率测试还有调试等等。
meta 性能
H3:我这边会遇到一些 meta 相关的问题,以后我们的 meta 会有高频的发心跳操作,因为我们的 Space 和 Partition 数量比较多(上百个 Space、每个 Space 上百的 Partition),如果 meta 发心跳去更新 schema 的话,会发送很多请求,然后这个请求一次可能就好几百条,这是如果是单节点的 meta 的话,它可能会有性能压力,不知道有什么好的建议没?
Yee:meta 这块的话,目前会有个 slow query 写入的问题,meta 会记录一些比较慢的 query,标记成 slow query,这块有可能是个性能瓶颈。
H3:目前来说 meta 都是单 part 的,有考虑说做成多 part 么?
Yee:因为元数据不同于真实的数据,不需要很高的并发、数据量大了要做切片,其实元数据没有那么大的需求。
H3:多 part 的话就是可以分散请求压力。
Yee:其实这个和 meta 的设计有关系,合理的话 meta 是不应该有很高的负载,不管出于什么原因,meta 的负载不应该是很高的。
Critical:尽量减少 Session 的相关操作,绝大多数 meta 的压力其实是来自与 Session。另外更新 schema 的频率不应该太高。
内存控制
H3:v3.4 版本好像有上了一些内存控制的功能,我们这边使用的话,也会到了一些内存上涨的问题。不知道 NebulaGraph 这边有什么内存调参和 RocksDB 的优化建议?
Yee:内存这块涉及到两块,一个是 RocksDB 存储这块的,另外一个是查询层面的内存使用。大概是 3.4 之后,主要做了两点优化:第一点是减少内存使用,像相同的点边 value 它要去共享这个点边,这样减少内存使用;第二点就是控制内存使用,就是为了保证进程还能对外服务,可能会优先杀掉一些 query,会牺牲部分的 query 来换取服务的稳定。主要是 memory tracker 和 kill slow query 之类的功能,像结束慢查询的话,一般优先杀掉最占内存的 query。后面有规划,让用户可配置每个 query 可以分配多大的内存,如果超过了配置就继续数据落盘。
H3:我们这边主要是 Storage 的内存占用,应该是写缓存占用比较大。多 Space 同时写的时候,写缓存就比较大。我现在的写缓存主要是 RocksDB 那边的 read-buffer,它的写缓存好像是 raft-buffer x 块数就是 Space 的大小。
Yee:nebula-storage 本身有一些 buffer,它会缓存一些部分来不及处理的数据和日志,还有 RocksDB 的数据缓存,这个对应的参数设置项我们会后找下给你发过去。
Critical:raft buffer 的大小由这个控制 max_log_buffer_size
。
端口切换
H3:我们整个集群进行迁移时,我看到 NebulaGraph 这边有个 replace 接口可以换 IP,但是有时候我也想更换端口。如果在配置里换成新的端口的话,meta 那边不能识别到新的 Storage 端口。
Muyi:Storage 的话,可以 Add Hosts 新的机器端口之后,进行 data balance remove,再 drop 掉对应的老的 Storage 端口。
Critical:新的版本好像可以支持换 ip/port。
Happy Office Hour
Happy Office Hour 活动暂定为每月一期,每期来自 NebulaGraph 的内核开发同学以及产品同学,将在活动约定的 2 个小时内,通过线上腾讯会议的形式 ,和大家交流遇到的使用问题。
每期 NebulaGraph 会从报名表中,选择 2、3 名报名用户加入私密的腾讯会议。在该次线上会议,同来自这 2、3 家公司的社区小伙伴交流使用心得。
想要和 NebulaGraph 核心研发来一场线上的面对面交流么,记得报名Happy Office Hour:https://wj.qq.com/s2/13521700/1698/ (同二维码)
Tips:下一场交流会将在 01.26 下午 15:00 正式开启~~
对图数据库 NebulaGraph 感兴趣?欢迎前往 GitHub 查看源码:https://github.com/vesoft-inc/nebula;
想要一起提高文档的可读性么?一起来给『文档 nGQL 示例添加注释』吧~请瞄准那条让人费解的 nGQL 语句,留下你的讲解 (///▽///)
点击下图了解活动详情