列存引擎诞生的背景
在运维监控领域的某些场景中,可能存在某些标签的基数非常高的场景,如下是网络流量监控的一个样例数据:
其中,ip 地址、时间列就是典型的高基数列。
openGemini 现有的时序存储引擎在存储数据时,首先将数据按照时间线(即tag value的组合)进行聚簇,在时间线内再按照时间对数据进行排序,同时构建了 tag key/value 到时间线的倒排索引,这种存储方式在时间线数量相对有限的情形下,可以提供极致的写入与查询性能,但是在处理上述高基数场景时,时间线数量巨大,由于倒排索引与时间线数量相关,可能导致内存膨胀、读写性能下降等问题:
它如何解决高基数问题
openGemini 列存引擎解决高基数问题的核心思路是调整数据排序与索引方式,去掉与基数的关系。
假设业务数据如下:
| Time | ispIp | direction | ISP | … | bps |
|---|---|---|---|---|---|
| 7:05:40 | 192.69.3.132 | out | China Mobile | … | 56 |
| 7:05:40 | 192.69.3.177 | out | China Telecom | … | 66 |
| 7:05:41 | 192.69.3.132 | out | China Mobile | … | 76 |
| … | … | … | … | … |
指定排序键为 ISP, direction, Time 等,那么数据按照排序键排序之后变成:
| ISP | direction | Time | ispIp | … | bps |
|---|---|---|---|---|---|
| China Mobile | out | 7:05:40 | 192.69.3.132 | … | 56 |
| China Mobile | out | 7:05:41 | 192.69.3.132 | … | 76 |
| China Telecom | out | 7:05:40 | 192.69.3.177 | … | 66 |
| … | … | … | … | … | … |
排序完成之后,将数据按列存储,存储时以若干行(如 8192 行)为一个 Block 进行数据压缩并序列化,在此基础上,选取每个 Block 的第一条记录构建稀疏的聚簇索引:
| (稀疏)聚簇索引 |
|---|
| China Mobile, out, 7:05:40 |
| … |
同时与时序引擎一样,使用 LSM-Tree 结构在后台对数据进行 Compaction,保持数据的整体有序性。
引入列存引擎后,openGemini 的整体架构如下:
除了本文介绍的列存引擎以及后台 Compaction 以外,对查询引擎、接口协议也做了相应的适配,以达成更好的写入与查询性能,后续也会进一步分享相关技术。
列存引擎的使用
引入列存引擎之后,需要在创表的时候显式指定引擎类型,默认引擎类型为原有的时序引擎。创表的 DDL 命令如下:
CREATE MEASUREMENT $mst_name ($columnlists)
WITH ENGINETYPE = COLUMNSTORE
[SHARDKEY $shardkeylist]
[TYPE HASH|RANGE]
[PRIMARYKEY $primarykeylist]
[SORTKEY $sortkeylist]
其中,关键字意义如下(上述出现的大写单词均为关键字,使用时不区分大小写) :
-
$mst_name 为创建的表名,实际使用时用具体名称替换。不支持包含
,:;/等特殊字符,如需包含其他特殊字符,需要使用双引号包含 mst_nameCREATE MEASUREMENT ":mst0" (tag1 TAG, field1 INT64 FIELD, field2 BOOL, field3 STRING, field4 FLOAT64) -
$columnlists 用于定义 Schema,包含在括号内,需要显式指定,暂不支持对 Schema 进行变更
(tag1 TAG, field1 INT64 FIELD, field2 BOOL, field3 STRING, field4 FLOAT64)columnlists 指定了每一列的列名、数据类型以及列属性,其中
-
- TAG不需要指定数据类型,默认为
STRING - 列属性可选值为
TAG或者FIELD,未指定列属性时,默认为FIELD - 数据类型仅支持
FLOAT64,INT64,BOOL,STRING - 默认带
time列,不需要包含在 columnlists 中
- TAG不需要指定数据类型,默认为
-
ENGINETYPE 表示引擎类型,时序引擎为 TSSTORE,列存引擎为 COLUMNSTORE
-
SHARDKEY 关键字指定存储引擎按给定的一个或多个字段进行数据分区打散,默认按全部 TAG KEYS 进行分区打散
-
TYPE 关键字表示打散方式,分为 HASH 和 RANGE 两种。默认为 HASH
-
PRIMARYKEY 关键字指定索引列,可以是一个或者多个字段,意味着存储引擎会在这些字段之上创建索引。
-
SORTKEY 指定存储引擎内部的数据排序方式。PRIMARYKEY 和 SORTKEY 二者关系是,
PRIMARYKEY需为SORTKEY的左前缀,否则报错,如果只配置了其中一个,则二者保持一致。
性能提升一览
列存引擎在高基数场景下可以带来显著的性能提升,以下对比了 InfluxDB、ClickHouse 的写入与查询性能。
其中,查询场景为:
- 场景一:查询15分钟实时流量数据(时间范围查询)
- 场景二:指定条件查询15分钟内实时流量数据
- 场景三:查看低基标签列表(show tag values)
- 场景四:查看高基标签列表(show tag values)
- 场景五:全量数据统计查询(count *)
总结
openGemini 在现有时序引擎的基础上,通过调整数据排序与索引方式,推出了列存引擎,以解决高基数带来的问题,后续在此基础上,会提供更多丰富的索引类型,以加速不同场景的查询性能。
openGemini官网:www.openGemini.org
openGemini开源地址:github.com/openGemini
openGemini公众号:
欢迎关注~ 诚邀你加入 openGemini 社区,共建、共治、共享未来!