共计 8958 个字符,预计需要花费 23 分钟才能阅读完成。
本文首发于 Nebula Graph 公众号 NebulaGraphCommunity,Follow 看大厂图数据库技术实际。
1 背景
Nebula 2.0 中曾经反对了基于内部全文搜索引擎的文本查问性能。在介绍这个性能前,咱们先简略回顾一下 Nebula Graph 的架构设计和存储模型,更易于下边章节的形容。
1.1 Nebula Graph 架构简介
如图所示,Storage Service 共有三层,最底层是 Store Engine,它是一个单机版 local store engine,提供了对本地数据的 get
/put
/scan
/delete
操作,相干的接口放在 KVStore / KVEngine.h 文件外面,用户齐全能够依据本人的需要定制开发相干 local store plugin,目前 Nebula 提供了基于 RocksDB 实现的 Store Engine。
在 local store engine 之上,便是咱们的 Consensus 层,实现了 Multi Group Raft,每一个 Partition 都对应了一组 Raft Group,这里的 Partition 便是咱们的数据分片。目前 Nebula 的分片策略采纳了 动态 Hash
的形式,具体依照什么形式进行 Hash,在下一个章节 schema 里会提及。用户在创立 SPACE 时需指定 Partition 数,Partition 数量一旦设置便不可更改,一般来讲,Partition 数目要能满足业务未来的扩容需要。
在 Consensus 层下面也就是 Storage Service 的最上层,便是咱们的 Storage Interfaces,这一层定义了一系列和图相干的 API。这些 API 申请会在这一层被翻译成一组针对相应 Partition 的 KV 操作。正是这一层的存在,使得咱们的存储服务变成了真正的图存储,否则,Storage Service 只是一个 KV 存储罢了。而 Nebula 没把 KV 作为一个服务独自提出,其最次要的起因便是图查问过程中会波及到大量计算,这些计算往往须要应用图的 Schema,而 KV 层是没有数据 Schema 概念,这样设计会比拟容易实现计算下推。
1.2 Nebula Graph 存储介绍
Nebula Graph 在 2.0 中,对存储构造进行了改良,其蕴含点、边和索引的存储构造,接下来咱们将简略回顾一下 2.0 的存储构造。通过存储构造的解释,大家根本也能够简略理解 Nebula Graph 的数据和索引扫描原理。
1.2.1 Nebula 数据存储构造
Nebula 数据的存储蕴含“点”和“边”的存储,“点”和“边”的存储均是基于 KV 模型存储,这里咱们次要介绍其 Key 的存储构造,其构造如下所示
Type
: 1 个字节,用来示意 key 的类型,以后的类型有 vertex、edge、index、system 等。PartID
: 3 个字节,用来示意数据分片 partition,此字段次要用于 partition 从新散布(balance)时不便依据前缀扫描整个 partition 数据VertexID
: n 个字节,出边外面用来示意源点的 ID,入边外面示意指标点的 ID。Edge Type
: 4 个字节, 用来示意这条边的类型,如果大于 0 示意出边,小于 0 示意入边。Rank
: 8 个字节,用来解决同一种类型的边存在多条的状况。用户能够依据本人的需要进行设置,这个字段可 寄存交易工夫 、 交易流水号 、或 某个排序权重。PlaceHolder
: 1 个字节,对用户不可见,将来实现分布式做事务的时候应用。TagID
:4 个字节,用来示意 tag 的类型。
1.2.1.1 点的存储构造
Type (1 byte) | PartID (3 bytes) | VertexID (n bytes) | TagID (4 bytes) |
---|
1.2.1.2 边的存储构造
Type (1 byte) | PartID (3 bytes) | VertexID (n bytes) | EdgeType (4 bytes) | Rank (8 bytes) | VertexID (n bytes) | PlaceHolder (1 byte) |
---|
1.2.2 Nebula 索引存储构造
- props binary(n bytes):tag 或 edge 中的 props 属性值。如果属性为 NULL,则会填充 0xFF。
- nullable bitset(2 bytes):标识 prop 属性值是否为 NULL,共有 2 bytes(16 bit),由此可知,一个 index 最多能够蕴含 16 个字段。
1.2.2.1 tag index 存储构造
Type (1 byte) | PartID (3 bytes) | IndexID (4 bytes) | props binary (n bytes) | nullable bitset (2 bytes) | VertexID (n bytes) |
---|
1.2.2.2 edge index 存储构造
Type (1 byte) | PartID (3 bytes) | IndexID (4 bytes) | props binary (n bytes) | nullable bitset (2 bytes) | VertexID (n bytes) | Rank (8 bytes) | VertexID (n bytes) |
---|
1.3 借用第三方全文搜索引擎的起因
由以上的存储构造推理能够看出,如果咱们想要对某个 prop 字段进行文本的含糊查问,都须要进行一个 full table scan
或 full index scan
,而后逐行过滤,由此看来,查问性能将会大幅降落,数据量大的状况下,很有可能还没扫描结束就呈现内存溢出的状况。另外,如果将 Nebula 索引的存储模型设计为适宜文本搜寻的倒排索引模型,那将背离 Nebula 索引初始的设计准则。通过一番调研和探讨,所谓术业有专攻,文本搜寻的工作还是交给内部的第三方全文搜索引擎来做,在保障查问性能的根底上,同时也升高了 Nebula 内核的开发成本。
2 指标
2.1 性能
2.0 版本咱们只对 LOOKUP
反对了文本搜寻性能。也就是说基于 Nebula 的外部索引,借助第三方全文搜索引擎来实现 LOOKUP
的文本搜寻性能。对于第三方全文引擎来说,目前只应用了一些根本的数据导入、查问等性能。如果是要做一些简单的、纯文本的查问计算的话,Nebula 目前的性能还有待欠缺和改良,期待宽广的社区用户提出贵重的倡议。目前所反对的文本搜寻表达式如下:
- 含糊查问
- 前缀查问
- 通配符查问
- 正则表达式查问
2.2 性能
这里所说的性能,指数据同步性能和查问性能。
- 数据同步性能:既然咱们应用了第三方的全文搜索引擎,那不可避免的是须要在第三方全文搜索引擎中也保留一份数据。通过验证,第三方全文搜索引擎的导入性能要低于 Nebula 本身的数据导入性能,为了不影响 Nebula 本身的数据导入性能,咱们通过 异步数据同步 的计划来进行第三方全文搜索引擎的数据导入工作。具体的数据同步逻辑咱们将在以下章节中具体介绍。
- 数据查问性能:刚刚咱们提到了,如果不借助第三方全文搜索引擎,Nebula 的文本搜寻将是一场噩梦。目前
LOOKUP
中通过第三方全文引擎反对了文本搜寻,不可避免的性能会慢于 Nebula 原生的索引扫描,有时甚至第三方全文引擎本身的查问都会很慢,此时咱们须要有一个 时效机制 来保障查问性能。即LIMIT
和TIMEOUT
,将在下列章节中具体介绍。
3 名词解释
名称 | 阐明 |
---|---|
Tag | 用于点上的属性构造,一个 vertex 能够附加多个 tag,以 tagId 标示。 |
Edge | 相似于 tag,edge 是用于边上的属性构造,以 edgetype 标示。 |
Property | tag 或 edge 上的属性值,其数据类型由 tag 或 edge 的构造确定。 |
Partition | Nebula Graph 的最小逻辑存储单元,一个 Storage Engine 可蕴含多个 partition。Partition 分为 leader 和 follower 的角色,raftex 保障了 leader 和 follower 之间的数据一致性。 |
Graph space | 每个 graph space 是一个独立的业务 graph 单元,每个 graph space 有其独立的 tag 和 edge 汇合。一个 Nebula Graph 集群中可蕴含多个 graph space。 |
Index | 下文中呈现的 index 指 Nebula Graph 中点和边上的属性索引。其数据类型依赖于 tag 或 edge。 |
TagIndex | 基于 tag 创立的索引,一个 tag 能够创立多个索引。因暂不反对复合索引,因而一个索引只能够基于一个 tag。 |
EdgeIndex | 基于 edge 创立的索引。同样,一个 edge 能够创立多个索引,但一个索引只能够基于一个 edge。 |
Scan Policy | index 的扫描策略,往往一条查问语句能够有多种索引的扫描形式,但具体应用哪种扫描形式须要 scan policy 来决定。 |
Optimizer | 对查问条件进行优化,例如对 WHERE 子句的表达式树进行子表达式节点的排序、决裂、合并等。其目标是获取更高的查问效率。 |
4 实现逻辑
目前咱们兼容的第三方全文搜索引擎是 ElasticSearch,此章节中次要围绕 ElasticSearch 来进行形容。
4.1 存储构造
4.1.1 DocID
partId(10 bytes) | schemaId(10 bytes) | encoded_columnName(32 bytes) | encoded_val(max 344 bytes) |
---|
- partId:对应于 Nebula 的 partition ID,以后的 2.0 版本中还没有用到,次要用于今后的查问下推和 es routing 机制。
- schemaId:对应于 Nebula 的 tagId 或 edgetype。
- encoded_columnName:对应于 tag 或 edge 中的 column name,此处做了一个 md5 的编码,用以防止 ES DocID 中不兼容的字符。
- encoded_val 之所以最大为 344 个 byte,是因为 prop value 做了一个 base64 的编码,用于解决 prop 中存在某些 docId 不反对的可见字符的问题。理论的 val 大小被限度在 256 byte。这里为什么会将长度限度在 256?设计之初,次要的目标是实现 LOOKUP 中的文本搜寻性能。基于 Nebula 本身的 index,其长度也有限度,相似传统关系数据库 MySQL 一样,其索引的字段长度倡议在 256 个字符之内。因而将第三次搜索引擎的长度也限度在 256 之内。此处并没有反对长文本的全文搜寻。
- ES 的 docId 最长为 512 byte,目前有大概 100 个 byte 的保留字节。
4.1.2 Doc Fields
- schema_id:对应于 Nebula 的 tagId 或 edgetype。
- column_id:nebula tag 或 edge 中 column 的编码。
- value:对应于 Nebula 原生索引中的属性值。
4.2 数据同步逻辑
Leader & Listener
上边的章节中简略介绍了数据异步同步的逻辑,此逻辑将在本章节中具体介绍。介绍之前,先让咱们认识一下 Nebula 的 Leader 和 Listener。
- Leader:Nebula 自身是一个可程度扩大的分布式系统,其分布式协定是 raft。一个分区(Partition)在分布式系统中能够有多种角色,例如 Leader、Follower、Learner 等。当有新数据写入时,会由 Leader 发动 WAL 的同步事件,将 WAL 同步给 Follower 和 Learner。当有网络异样、磁盘异样等状况产生时,其 partition 角色也会随之扭转。由此保障了分布式数据库的数据安全。无论是 Leader、Follower,还是 Learner,都是在 nebula-storaged 过程中管制,其零碎参数由配置参数
nebula-stoage.conf
决定。 - Listener:不同于 Leader、Follower 和 Learner,Listener 由一个独自的过程管制,其配置参数由
nebula-stoage-listener.conf
决定。Listener 作为一个监听者,会被动的接管来自于 Leader 的 WAL,并定时的将 WAL 进行解析,并调用第三方全文引擎的数据插入 API 将数据同步到第三方全文搜索引擎中。对于 ElasticSearch,Nebula 反对PUT
和BULK
接口。
接下来咱们介绍一下数据同步逻辑:
- 通过 Client 或 Console 插入 vertex 或 edge
- graph 层通过 Vertex ID 计算出相干 partition
- graph 层通过 storageClient 将
INSERT
申请发送到相干 Partition 的 Leader - Leader 解析
INSERT
申请,并将 WAL 同步到 Listener 中 - Listener 会定时解决新同步来的 WAL,并解析 WAL,获取 tag 或 edge 中字段类型为 string 的属性值。
- 将 tag 或 edge 的元数据和属性值组装成 ElasticSearch 兼容的数据结构
- 通过 ElasticSearch 的
PUT
或BULK
接口写入到 ElasticSearch 中。 - 如果写入失败,则回到第 5 步,持续重试失败的 WAL,直到写入胜利。
- 写入胜利后,记录胜利的 Log ID 和 Term ID,做为下次 WAL 同步的起始值。
- 回到第 5 步的定时器,解决新的 WAL。
在以上步骤中,如果因为 ElasticSearch 集群挂掉,或 Listener 过程挂掉,则进行 WAL 同步。当零碎复原后,会接着上次胜利的 Log ID 持续进行数据同步。在这里有一个倡议,须要 DBA 通过内部监控工具实时监控 ES 的运行状态,如果 ES 长期处于有效状态,会导致 Listener 的 log 日志暴涨,并且无奈做失常的查问操作。
4.3 查问逻辑
由上图可知,其文本搜寻的关键步骤是“Send Fulltext Scan Request”→ “Fulltext Cluster” → “Collect Constant Values” → “IndexScan Optimizer”。
- Send Fulltext Scan Request: 依据查问条件、schema ID、Column ID 生成全文索引的查问申请(即封装成 ES 的 CURL 命令)
- Fulltext Cluster:发送查问申请到 ES,并获取 ES 的查问后果。
- Collect Constant Values:将返回的查问后果作为常量值,生成 Nebula 外部的查问表达式。例如原始的查问申请是查问 C1 字段中以“A”结尾的属性值,如果返回的后果中蕴含“A1”和 “A2″ 两条后果,那么在这一步,将会解析为 neubla 的表达式
C1 == "A1" OR C1 == "A2"
。 - IndexScan Optimizer:依据新生成的表达式,基于 RBO 找出最优的 Nebula 外部 Index,并生成最优的执行打算。
- 在 ”Fulltext Cluster” 这一步中,可能会有查问性能慢,或海量数据返回的状况,这里咱们提供了
LIMIT
和TIMEOUT
机制,实时中断 ES 端的查问。
5 演示
5.1 部署内部 ES 集群
对于 ES 集群的部署,这里不再具体介绍,置信大家都很相熟了。这里须要阐明的是,当 ES 集群启动胜利后,咱们须要对 ES 集群创立一个通用的 template,其构造如下:
{
"template": "nebula*",
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 1
}
},
"mappings": {
"properties" : {"tag_id" : { "type" : "long"},
"column_id" : {"type" : "text"},
"value" :{"type" : "keyword"}
}
}
}
5.2 部署 Nebula Listener
- 依据理论环境,批改配置参数
nebula-storaged-listener.conf
- 启动 Listener:
./bin/nebula-storaged --flagfile ${listener_config_path}/nebula-storaged-listener.conf
5.3 注册 ElasticSearch 的客户端连贯信息
nebula> SIGN IN TEXT SERVICE (127.0.0.1:9200);
nebula> SHOW TEXT SEARCH CLIENTS;
+-------------+------+
| Host | Port |
+-------------+------+
| "127.0.0.1" | 9200 |
+-------------+------+
| "127.0.0.1" | 9200 |
+-------------+------+
| "127.0.0.1" | 9200 |
+-------------+------+
5.4 创立 Nebula Space
CREATE SPACE basketballplayer (partition_num=3,replica_factor=1, vid_type=fixed_string(30));
USE basketballplayer;
5.5 增加 Listener
nebula> ADD LISTENER ELASTICSEARCH 192.168.8.5:46780,192.168.8.6:46780;
nebula> SHOW LISTENER;
+--------+-----------------+-----------------------+----------+
| PartId | Type | Host | Status |
+--------+-----------------+-----------------------+----------+
| 1 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" |
+--------+-----------------+-----------------------+----------+
| 2 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" |
+--------+-----------------+-----------------------+----------+
| 3 | "ELASTICSEARCH" | "[192.168.8.5:46780]" | "ONLINE" |
+--------+-----------------+-----------------------+----------+
5.6 创立 Tag、Edge、Nebula Index
此时倡议字段“name”的长度应该小于 256,如果业务容许,倡议 player 中字段 name 的类型定义为 fixed_string 类型,其长度小于 256。
nebula> CREATE TAG player(name string, age int);
nebula> CREATE TAG INDEX name ON player(name(20));
5.7 插入数据
nebula> INSERT VERTEX player(name, age) VALUES \
"Russell Westbrook": ("Russell Westbrook", 30), \
"Chris Paul": ("Chris Paul", 33),\
"Boris Diaw": ("Boris Diaw", 36),\
"David West": ("David West", 38),\
"Danny Green": ("Danny Green", 31),\
"Tim Duncan": ("Tim Duncan", 42),\
"James Harden": ("James Harden", 29),\
"Tony Parker": ("Tony Parker", 36),\
"Aron Baynes": ("Aron Baynes", 32),\
"Ben Simmons": ("Ben Simmons", 22),\
"Blake Griffin": ("Blake Griffin", 30);
5.8 查问
nebula> LOOKUP ON player WHERE PREFIX(player.name, "B");
+-----------------+
| _vid |
+-----------------+
| "Boris Diaw" |
+-----------------+
| "Ben Simmons" |
+-----------------+
| "Blake Griffin" |
+-----------------+
6 问题跟踪与解决技巧
对于零碎环境的搭建过程中,可能某个步骤谬误导致性能无奈失常运行,在之前的用户反馈中,我总结了三类可能产生的谬误,对剖析和解决问题的技巧详情如下
-
Listener 无奈启动,或启动后不能失常工作
- 查看 Listener 配置文件,确保 Listener 的
IP:Port
不和已有的 nebula-storaged 抵触 - 查看 Listener 配置文件,确保 Meta 的
IP:Port
正确,这个要和 nebula-storaged 中保持一致 - 查看 Listener 配置文件,确保 pids 目录和 logs 目录独立,不要和 nebula-storaged 抵触
- 当启动胜利后,因为配置谬误,批改了配置,再重启后依然无奈失常工作,此时须要清理 meta 的相干元数据。对此提供了操作命令,请参考 nebula 的帮忙手册:文档链接。
- 查看 Listener 配置文件,确保 Listener 的
-
数据无奈同步到 ES 集群
- 查看 Listener 是否从 Leader 端承受到了 WAL,能够查看
nebula-storaged-listener.conf
配置文件中–listener_path
的目录下是否有文件。 - 关上 vlog(
UPDATE CONFIGS storage:v=3
),并关注 log 中 CURL 命令是否执行胜利,如果有谬误,可能是 ES 配置或 ES 版本兼容性谬误
- 查看 Listener 是否从 Leader 端承受到了 WAL,能够查看
-
ES 集群中有数据,然而无奈查问出正确的后果
- 同样关上 vlog(
UPDATE CONFIGS graph:v=3
),关注 graph 的 log,查看 CURL 命令是什么起因执行失败 - 查问时,只能辨认小写字符,不能辨认大写字符。可能是 ES 的 template 创立谬误。请对照 nebula 帮忙手册进行创立:文档链接。
- 同样关上 vlog(
7 TODO
- 针对特定的 tag 或 edge 建设全文索引
- 全文索引的重构(REBUILD)
交换图数据库技术?退出 Nebula 交换群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~
想要和其余大厂交换图数据库技术吗?NUC 2021 大会等你来交换:NUC 2021 报名传送门