暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Nebula 性能问题排查和性能调优

1512


>>>>

前言

当我们使用 Nebula 图数据库执行查询语句,效率不如预期时候可能会有多种因素影响,可以分为两类,第一种是硬件资源受限,第二种是执行计划的问题(涉及到索引的选择、执行语句的优化等等),而本文着重讲述这两种情况下的性能问题排查。

>>>>

硬件检查

本文测试环境最低硬件要求

  • CPU 核数:4

  • 硬盘:100GB SSD

硬件检查大体分为内存检查、磁盘 IO 检查、网络带宽检查和 CPU 检查等四类检查。



内存检查

在执行查询语句时,在目标机上使用 top
命令查看 nebula-graph 进程和 nebula-storage 进程的内存使用情况

  • 物理内存(RES) 是实际占用的内存空间。

  • 交换区(SWAP) 是物理内存不足时,操作系统会把内存中不常用的页换出到磁盘空间,当被换出的页再次被访问时,再换入物理内存。

  • 虚拟内存(VIRT) 是物理内存 + 交换区,程序操作内存时操作的是虚拟内存中的地址,由操作系统再负责映射到物理内存或者交换区。

 free
命令可以查看内存的整体状态

  • 缓存 (buff/cache) 是 Linux 会尽量提高内存使用率:把磁盘上的内容缓存到内存中,用来加速。当内存不足时,Linux 会释放缓存部分,给真正需要的进程使用。

内存不足有两个主要的指标可以判断

  • 持续的内存换入换出

  • 较多的主缺页中断

    • 主缺页中断是指在内存中找不到目标页,需要到磁盘中找,所以较多的主缺页中断意味着较多的磁盘访问,可以通过 sar
      命令查看。

如上图所示,pgpgin/pgpgout
就是内存的换入换出;majflt
是主缺页中断。



磁盘IO检查

分析磁盘 IO 的工具很多,我们选择其中一个 iostat
命令, 可以同时监测 CPU 和磁盘 IO 的使用情况。

  • iowait 是 CPU 等待 IO 完成时间的百分比 ,若较高表明磁盘存在 IO 瓶颈

  • idle 是 CPU 空闲时间百分比,若较高则 CPU 比较空闲

  • aqu-sz 是平均请求队列长度,越短越好

  • util  是衡量 IO 的繁忙程度,值越大则 IO 请求较多,IO 压力较大,可以结合 idle 参数,若 idle < 70% 说明 IO 比较繁忙

  • w_awaitr_await 是衡量 IO 的读写响应速度,可以理解为 IO 响应时间

  • svctm 是平均每次 IO 服务时间,如果 svctm 和 await 很接近,表示几乎没有 IO 等待,磁盘性能很好,若 await 远高于 svctm,则 IO 队列等待太长,系统上运行的应用程序将变慢

通过上述指标可以判断瓶颈是否在 IO 排队上。



网络带宽检查

对数据密集型服务,网络带宽可能成为瓶颈。首先查看服务器的网卡带宽配置,使用 ethtool  <网卡名>。  (一般第一块网卡命名为 eth0,第二块为 eth1)

  • Speed: 1000Mb/s.  为千兆网卡

  • Speed: 10000Mb/s 为万兆网卡

然后使用 dstat 工具,dstat 可以实时监控 CPU、磁盘、网络、IO、内存的使用情况,dstat 中统计网络的单位是 byte,将网络统计的结果乘 8 和网卡带宽对比,如果比较接近,说明网络在满负荷运行,可能会产生瓶颈。


CPU检查

使用 top
命令查看 CPU 的运行指标。

  • load average:任务队列的平均长度,三个数值分别是 1 分钟、5 分钟、15 分钟前的平均值,值越大说明当前系统负荷越大,3 个值除以逻辑 CPU 个数,若结果均大于 1,表明 CPU 过载

  • us、sy:是用户空间和内核空间占用 CPU 百分比,若 us 大于 65%,sy 大于 35%,表明 CPU 有瓶颈

>>>>

执行计划

除了上述检查,还可以在执行查询语句后,通过 profile
查询,profile
 的使用参考文档:https://docs.nebula-graph.com.cn/2.6.1/3.ngql-guide/17.query-tuning-statements/1.explain-and-profile/#_2

profile
可以查看执行语句在 Nebula 中的详细执行计划,包括每个算子的执行时间。

其中和 storage
 交互的算子主要有 getNeightbor
getVertices
getEdge
indexScan
。下图为 getVertices
算子的详细情况

  • 其中红框中 totalTime
     是 getVertices
    算子从发送请求到接收到结果总的耗时。

  • 绿框中的 total
     是每一个 graph client
     发送请求到接收结果的时间 (这个时间等于 storage
    处理 + 数据序列化 + 网络传输)。

  • 绿框中的 exec
     是每一个 storage
     处理数据的时间。

在多分区场景下 :

  • 正常情况如果线程并发地执行,绿框中的每一个 total
    时间和红框中的 totalTime
     相差不大,像上图的情况表明线程在排队等待 CPU 执行,没有完全并发的跑,说明可能 CPU 有瓶颈。

同理,GetNeightbor
GetEdge
可以同样参考上述方式进行分析。



执行计划优化

近日在论坛中看到有用户发帖,为什么几乎相同的两条语句,一条 LIMIT
可以下推到 storage 中,另一条却无法下推到 storage 中,在此稍作解释。

目前 Nebula 中使用的是基于 RBO 的优化 https://nebula-graph.com.cn/posts/nebula-graph-source-code-reading-04/,优化阶段是基于已经写好的优化规则进行优化的,优化器并不能看到整体的执行计划,而是从根节点开始递归的遍历到叶子节点,然后再从叶子节点一个个和已经写好的规则进行匹配。如果当前遍历到的节点及其依赖节点和某一个规则匹配,则执行转换;否则继续,直到递归结束。

目前已有的规则参考源码:https://github.com/vesoft-inc/nebula/tree/master/src/graph/optimizer/rule

这里举一个例子方便理解,首先把 nebula-graph.conf
enable_optimizer
设置为 false,这时候执行计划是不经过优化器的。下面对比

  • GO FROM "tim" OVER like YIELD $^.person.name, like._dst | LIMIT 3

  • GO FROM "tim" OVER like YIELD $$.person.name, like._dst | LIMIT 3

这两条语句最原始的执行计划

GO FROM "tim" OVER like YIELD $^.person.name, like._dst | LIMIT 3
  的执行计划如下

在优化器中从根节点 DataCollect
 出发遍历到 Start
点,然后一个个匹配,当遍历到 LIMIT
节点时,匹配到 project→ limit
规则,执行转换将 LIMIT
放到 project 前面变为 limit→project
,然后继续 LIMIT
节点匹配到 getNeighbors→limit
规则,将 LIMIT
下推到 getNeighbor
中,完成 LIMIT
下推。

GO FROM "tim" OVER like YIELD $$.person.name, like._dst | LIMIT 3
  的执行计划如下

在优化器中从根节点 DataCollect
出发遍历到 Start
节点,然后一个个匹配,当遍历到 LIMIT
节点时,匹配到 project→ limit
规则,执行转换将 LIMIT
放到 project
前面变为 limit→project
,但是不存在 leftJoin→limit
规则,因此无法将 LIMIT
继续下推

因此虽然两条语句的 YIELD
子句只相差了一点,一个是取起始点属性,一个是取目的点属性,但是原始的执行计划却相差很大,造成优化后一个 LIMIT
下推,另一个却没有,造成性能问题。


suoy

索引的选择

索引是否命中对查询性能的影响也非常大,Nebula 的索引介绍请参考文章:https://nebula-graph.com.cn/posts/how-indexing-works-in-nebula-graph/

Nebula 中的索引分为唯一索引(索引列的值必须唯一)和联合索引(多个字段上建立的索引,能够加速复合查询条件的检索)。其中联合索引遵循最左前缀原则,即在检索数据时从联合索引的最左边开始匹配,目前 Nebula 不支持跨 tag 或者 edge 创建复合索引。

不能使用索引的条件有

1、负向条件查询, 例如 WHERE
条件中有 !=、not in

2、模糊查询, 例如:like '%XX'

validate 阶段会将 MATCH
 语句中的 WHERE
条件和点和边属性过滤提取出来,然后在 optimizer
阶段会对提取出的条件进行分析:

若过滤条件时候会在选择一个和当前 Schema 相同的索引中属性列最少的索引进行全表扫描,否则进入到 2

对当前条件进行分析, 目前仅支持单一的逻辑表达式,例如 where c1 > 1 and c2 < 2 and c3 == 1
  或者 where c1 == 1 or c2 == 1 or c3 == 1
,不支持复合的逻辑表达式,例如 where c1 > 1 and c2 > 1 or c3 > 1
或者 where c1 > 1 and c2 < 2 or c2 > 1
等等,当满足单一逻辑表达式后,进入到 3

索引选取的优先级时等值查找 > 范围查找,其中索引中的列命中条件中的列数越多,优先级越高;

建议:

1、建联合索引的时候,区分度最高的字段在最左边

2、存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置

>>>>

Nebula 配置和使用指南

讲完硬件和执行计划,再来讲述下常见的 Nebula 使用性能瓶颈点,它们主要集中在配置以及使用操作方面。


suoy

Nebula 配置

storage.conf 配置

  • rocksdb_block_cache:数据在内存缓存大小,默认是 4MB,可以设置到当前内存的一半,这样在第二次查询的时候,可以优先在 RocksDB 的 cache 中查找数据;

  • query_concurrently:true 时 storage 会并发地捞数据,false 时 storage 是单线程捞数据;

  • rocksdb_column_family_options:在刚开始导入大量数据时可以将 disable_auto_compaction
    选项设置为 true,提升写入的性能;

graphd.conf 配置

  • system_memory_high_watermark_ratio:设置内存使用量超过多少时停止计算,以 1 作为单位一般设置为 0.8~1.0 之间


suoy

执行语句前的操作

Compaction

当导入完数据之后需要执行。

  • 打开 nebula-console 连接到图空间,然后执行 SUBMIT JOB COMPACT
    ;

  • 通过 SHOW JOBS
     查看 compact 是否执完毕。

compaction 可以消除重复的数据,去掉无用的数据,将 sst 文件合并,进而减少 sst 占用的磁盘空间,提高读的性能。

Balance leader

  • 打开 nebula-console 连接到图空间,然后执行 BALANCE LEADER
    ;

  • 通过 S
    HOW HOSTS
     查看 leader 是否均匀分布。

Balance 可以让 storage 服务实现负载均衡,避免出现热点现象。

以上为本次分享。

要来交流图据库技吗?关注公众号后发送“加群”,Nebula 迷人小姐姐拉你进群~~

🙋‍♂️ 喜欢本文的话,、👍 在看

谢谢哟^^

文章转载自Nebula Graph Community,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论