Nebula Graph与DGraph的两款数据库对比
本文仅限自己本地使用时的两款数据库对比,对于数据库整体性能并没有达到最佳。两款数据库均基于SSD进行优化。目前测试环境存储均为HDD,因此数据库的写入、查询均存在一定影响。
NebulaGraph官方又大量优质的此数据库与其他图数据库产品的对比文章。
1、安装
Dgraph:下载源码后解压在生产环境下即可启动图数据库
Nebula:下载deb包后安装,默认安装路径为 /user/local/nebula/。
启动数据库服务为 /usr/local/nebula/scripts/nebula.service start all
连接数据库需要使用nebula-console,进入后按照sql的基本逻辑使用
2、配置
Dgraph:
依次启动zero、alpha
--badger.vlog disk //设置存储为磁盘存储
--abort_older_than 1m
--pending_proposals 1000
--lru_mb 3072 //查询的内存限制目前设置为3G,但实际使用会超出
--query_edge_limit 500000000 //设置查询边的上限
set_schema结构
task_id: [string] @index(term) .
ip_str: string @index(term) .
ip_num: string @index(term) .
scan_to: [uid] @reverse . //reverse为可进行逆向查询
device_type: string @index(term,fulltext) @lang .
has_pid: string @index(term) .
virtual_node: [string] @index(term) .
Nebula
在默认安装路径 /usr/local/nebula/scripts内启动
/user/local/nebula/scripts start all 即可启动数据库、存储服务
设置数据库建联执行一系列的excute,完成数据库建立
1、CREATE SPACE my_space_2(partition_num=5); 设置库名、同时设置分片数量
# 此处对比DGraph有以下几点说明
a、Dgraph同样可以设置分片,但是设置分片对内存消耗同比较大(并不准确、需要后期验证)
b、Dgraph不支持多个数据库的建立,即所有的数据存储在同一表中,当同一项目中存在需要两个不同的数据结构时,DGraph无法满足该场景
2、CREATE TAG node(ip_str string, has_pid bool default true, device_type string);
node为节点数据类名
3、CREATE EDGE next(virtual bool default false);
next为边数据类名,类似DGraph中的scan_to
3、两个图数据库数据结构的不同对数据库设计的影响
首先,两个图数据库的数据结构存在较大的区别。两者数据库数据结构对后续的CURD操作各有利弊。
DGraph的数据结构大致为(测试环境中的数据结构)
{
uid: "0x1231", //数据库生成的唯一主键,不可自定义
scan_to: [uid1, uid2, uid3], //边关系的存储字段,将uid之间建联,uid的list只增不减
"" : "" 其他节点属性参数
}
存储后的数据结构内容类似单个的json
{
uid:"uid1",
scan_to: [
{
uid: 'uid2',
scan_to: [...],
virtual_node: []
},
{
uid: "uid3"
scan_to: [...],
virtual_node: []
}
]
}
Nebula的数据结构大致为
节点类型:
[("100000" :node{device_type: "路由交换设备", has_pid: false, ip_str: "172.18.6.100000", task_id: "12"})]
边类型:
[:next "100000"->"100001" @0 {virtual: true}]
其中 1000000、100001均为节点的vid数据,类似于DGraph中的uid,为当前节点的唯一主键。
但是vid可以为自定义的string。node内的数据为当前的所有节点属性。
边类型中: vid1 -> vid2 为一条边的关系建联。 virtual为当前边的属性信息。
根据数据库存储的数据结构。两个数据库在存储数据时所遇到的问题
//设置一条即将入库的信息
[
{
'start': {
'task_id': '1',
'ip_str': '172.18.0.1',
},
'end': {
'task_id': '1',
'ip_str': '172.18.0.10',
}
},{...}
]
所有的插入都建立在已经完成了图数据库结构设计的前提下
即Dgraph完成了schema的创建。nebula完成了space、tag、edge的创建
Dgraph
1、由于Dgraph不支持自定义的uid,因此在存储时需要做到一下几点,才能将一条数据完整的存储在数据库中
1.检查即将入库的ip是否在图数据库中已存在(图数据库指挥根据uid判断是否存在相同数据,即使存储的内同完全一致)
2.对未入库的数据进行入库操作(同时增加IP、device_type等一系列不包含关系的数据)
3.对节点间的关系数据进行入库操作
3.1 查询start和end的ip在数据库中对应的uid信息
3.2 获取当前的scan_to字段,判断end_uid是否存在于start对应的scan_to字段内。不存在对当前的start的scan_to增加end_uid
3.3 判断start节点的 virtual_node 数据是否为0。不为0时则表示为跳跃节点,需要更改start节点信息的virtual_node字段。
2、在存储virtual_node数据时。存在节点连接状态一直变化的状态,在当前的virtual_node中就必须不断的进行增加、删除操作。但是uid的list无法进行删除操作。即当连接线为虚线后无法更改状态
3、DGraph使用事务进行图数据库的mutation,但是事务不支持锁。当不同的线程同时操作一个节点时就会造成事务冲突问题。具体可查看以下链接内容
https://discuss.dgraph.io/t/transaction-locks-and-conflicts/1982
4、DGraph存在较大概率出现OOM情况。在静置状态下。DGraph消耗的内存为
当前的数据库中不存在数据。即当前数据库启动后的内存占用。当开始写入数据时,内存信息最大会维持在8G(单进程写入),但当同时查询并写入数据时,整体的内存消耗会超过10G,在测试过程中存在内存占用范围为15~20G的情况。查询较多数据量的全部数据时大概率会产生OOM。目前推测的整体占用内存较大为入库内过多的查询导致的。在每次的入库中基本过程为(查询->入库->查询->入库),因此需要大量内存。
Nebula-Graph
1、NebulaGraph的一整条插入为
1. 生成一条节点插入语句 INSERT VERTEX node(ip_str, has_pid, device_type, task_id) VALUES
2. 生成一条边插入语句 INSERT EDGE next(virtual) VALUES
3. 将将所有的插入语句一次性的插入到space内
// 由于可以自定义vid的内容,在插入边与插入节点时均不用提前查询唯一主键的值,后续的节点信息,在相同主键的前提下会做覆盖操作
2、有关virtual_node的添加问题
在nebula数据库中由于不支持list的数据类型,仅支持基本的数据类型。即,使用相同的task_id存储方式无法满足当前的数据库。但是同时由于task_id类别多样化的问题,可以将任务信息存储为节点并对数据进行关联,即存储的结构为。
整个数据库存在三个数据集
边数据集合、节点数据集合、任务信息数据集合
任务信息数据集 边信息数据集 任务信息数据集
|| || ||
节点信息数据集 ==========》 节点信息数据集
3、数据库的插入语法
相比较DGraph的json格式数据插入。Nebula将数据写入到数据库语法构建过于复杂。Nebula数据库的数据插入必须依托excute()函数才能实现,即函数内为完整的NGsql语句 需要依托字符拼接完成。
4、Nebula对于内存消耗的部分信息
目标场景:在数据库中存储着60W条数据,一次查询所有的数据并返回。内存使用情况基本维持在如图所示
其中nebula-console为数据库操作进程,内存使用可以忽略,生产环境不会影响。
查询完成后的静息内存使用信息为
数据库的查询问题
两款数据库对于查询的覆盖面基本相同,但是目前Nebula对于图数据库查询的算法覆盖不及DGraph,但目前Nebula的查询场景基本可以涵盖项目需求。以后也可以对数据库版本进行更新。
Dgraph查询结果依托特有的数据结构,本身就为标准的JSON格式,对数据的解析友好度较高。
但是Nebula查询的结果为一个ResultSet类型数据
目前需要循环遍历解析为Node类型,并单独获取单个node内的数据信息,相比Dgraph解析复杂度较大。具体流程为
1、查询一条链路消息
resp = client.execute("""GET SUBGRAPH 5 STEPS FROM '100000'""")
2、分别获取边、节点信息
node = resp.column_values('_vertices')
edge = resp.column_values('_edges')
3、遍历节点,获取节点内的全部信息
for info in node:
new_info = info.as_list()
if new_info:
new_data = new_info[0].as_node()
node_obj = new_data.propertys('node')
node_obj['vid'] = new_data.get_id().as_string()
print(node_obj)
4、遍历边,获取边上的全部信息
for info in edge:
new_info = info.as_list()
if new_info:
new_data = new_info[0].as_relationship()
start_id = new_data.start_vertex_id()
end_id = new_data.end_vertex_id()
edge_obj = new_data.propertys()
edge_obj['source'] = start_id
edge_obj['target'] = end_id
nebula同时提供了数据转换的函数,可以根据给出的示例,总结自己的转换函数
https://github.com/vesoft-inc/nebula-python/blob/master/example/FormatResp.py
目前对两者的数据库都只是初步使用阶段,可能存在较多不足,如在Nebula中可以将节点的其他类型实例为一种点结构数据,在建联时只需要将两种点连接就为目标点增加了一个属性,只不过属性信息存在其他点,通过边建联
不过两款图数据库都只是停留在初步阶段,正确使用仍需要以官方文档为准。