关于elasticsearch:Nebula-基于-ElasticSearch-的全文搜索引擎的文本搜索

38次阅读

共计 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 scanfull 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 反对 PUTBULK 接口。

接下来咱们介绍一下数据同步逻辑:

  1. 通过 Client 或 Console 插入 vertex 或 edge
  2. graph 层通过 Vertex ID 计算出相干 partition
  3. graph 层通过 storageClient 将 INSERT 申请发送到相干 Partition 的 Leader
  4. Leader 解析 INSERT 申请,并将 WAL 同步到 Listener 中
  5. Listener 会定时解决新同步来的 WAL,并解析 WAL,获取 tag 或 edge 中字段类型为 string 的属性值。
  6. 将 tag 或 edge 的元数据和属性值组装成 ElasticSearch 兼容的数据结构
  7. 通过 ElasticSearch 的 PUTBULK 接口写入到 ElasticSearch 中。
  8. 如果写入失败,则回到第 5 步,持续重试失败的 WAL,直到写入胜利。
  9. 写入胜利后,记录胜利的 Log ID 和 Term ID,做为下次 WAL 同步的起始值。
  10. 回到第 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” 这一步中,可能会有查问性能慢,或海量数据返回的状况,这里咱们提供了 LIMITTIMEOUT 机制,实时中断 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 的帮忙手册:文档链接。
  • 数据无奈同步到 ES 集群

    • 查看 Listener 是否从 Leader 端承受到了 WAL,能够查看 nebula-storaged-listener.conf 配置文件中 –listener_path 的目录下是否有文件。
    • 关上 vlog(UPDATE CONFIGS storage:v=3),并关注 log 中 CURL 命令是否执行胜利,如果有谬误,可能是 ES 配置或 ES 版本兼容性谬误
  • ES 集群中有数据,然而无奈查问出正确的后果

    • 同样关上 vlog(UPDATE CONFIGS graph:v=3),关注 graph 的 log,查看 CURL 命令是什么起因执行失败
    • 查问时,只能辨认小写字符,不能辨认大写字符。可能是 ES 的 template 创立谬误。请对照 nebula 帮忙手册进行创立:文档链接。

7 TODO

  • 针对特定的 tag 或 edge 建设全文索引
  • 全文索引的重构(REBUILD)

交换图数据库技术?退出 Nebula 交换群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~

想要和其余大厂交换图数据库技术吗?NUC 2021 大会等你来交换:NUC 2021 报名传送门

正文完
 0