openGemini大解析:预聚合

原创
2023/10/27 17:49
阅读数 23

在openGemini中,在数据持久化到文件时,以时间线为维度,计算好了min,max,sum,count 并记录在文件元数据中,我们称为预聚合。

这些预聚合的数据,可以对部分查询场景起到加速的作用(减少磁盘读取和数据解码的开销)。

数据布局

在介绍预聚合之前,先简单说一下openGemini的数据布局。

数据在文件中是按时间线维度进行了划分的,每个时间线有一个独立的数据块(ChunkData),使用 ChunkMeta 来描述。

如下图所示,水平方向,每个 ChunkData 包含多列;垂直方向,每列按1000行为单位切割为 Segment 。

ChunkMeta

ChunkMeta 相关数据结构如下,本次我们重点关注timeRange 和 ColumnMeta.preAgg

type ChunkMeta struct {
    sid         uint64
    offset      int64
    size        uint32
    columnCount uint32
    segCount    uint32
    timeRange   []SegmentRange // 存放每个 segment 的时间范围
    colMeta     []ColumnMeta // 每列的元数据信息
}

type SegmentRange [2]int64 // min/max

type ColumnMeta struct {
    name    string
    ty      byte
    preAgg  []byte  // 序列化后的预聚合数据
    entries []Segment  // 存放 segment 的偏移量
}

预聚合数据

  • openGemini在数据存储时,按时间线维度,提前计算了每列的 min,max,sum,count 的结果,存放在 ColumnMeta 中

  • boolean 和 string 类型的数据仅记录count, 本次我们以 float,int 类型数据为例

 

查询加速

如果一个查询仅通过预聚合数据就能得到结果,那么可以减少磁盘读取, 数据解压等开销,极大的提升查询性能。

写入样例数据

  • 写入如下样例数据并刷盘,此时数据库中仅1个文件,2000行数据,1条时间线,1列,2个 segment

  • insert foo,host=127.0.0.1 val=1 1694654000000000001
    insert foo,host=127.0.0.1 val=2 1694654000000000002
    insert foo,host=127.0.0.1 val=3 1694654000000000003
    ... ...
    insert foo,host=127.0.0.1 val=1999 1694654000000001999
    insert foo,host=127.0.0.1 val=2000 1694654000000002000

加速 sum, count, min, max 查询

查询条件中的时间范围包含了数据的时间范围

SELECT sum(val) from foo where time>1694654000000000000 and time<1694654300000003000
SELECT count(val) from foo where time>1694654000000000000 and time<1694654300000003000 
SELECT min(val),max(val) from foo where time>1694654000000000000 and time<1694654300000003000
  •  

加速 first和last 算子 (适用于部分查询场景)

  • 有的场景,存储的数据是单调递增的,一段时间范围内满足:min==firstmax==last,则可以使用预聚合数据进行加速查询

  • 在ChunkMeta中有存放每个 segment的最小和最大时间

  • 由于数据是按时间有序排列的,因此最小值对应的时间为第一行数据的时间,最大对应的时间为最后一行数据的时间

  • 在满足下列条件时,可以直接取chunkmeta 中的时间,不需要读取磁盘上的数据

first 查询,数据没有空值,且时间范围满足:query time range:    --------------segment time range:      ---------------此时取 segment 的最小时间即可例如:SELECT first(val) from foo where time>1694654000000000000
last查询,数据没有空值,且时间范围满足: query time range:         ---------------- segment time range: --------------- 此时取 segment 的最大时间即可例如:SELECT last(val) from foo where time<1694654000000002001
  • 了解更多

https://github.com/openGemini/openGemini/blob/main/engine/immutable/first_last_reader.go

补充:

除了时间范围限制外,还有以下场景不支持预聚合:

  1. field 过滤

  2. group by time

对比测试

 

  • 特别说明:本测试仅供参考,数据模型和查询模型的差异,测试结果差别可能非常大

  • 使用DevOps模型中的 redis 表进行测试,参考:

    http://opengemini.org/116.html

  • 精确查询:

SELECT /*+ Exact_Statistic_Query */ count(*) FROM redis GROUP BY hostname
  • 预聚合查询:

SELECT count(*) FROM redis GROUP BY hostname

总结

预聚合可以有效提升查询性能效率,但目前来说应用范围有待扩展。欢迎大家到社区一起探讨扩展预聚合数据的应用场景,社区将不断推动技术研发,优化用户体验,在未来提供更加丰富、多元的功能。


openGemini官网:http://www.openGemini.org

openGemini开源地址:https://github.com/openGemini

openGemini公众号:

欢迎关注~ 诚邀你加入 openGemini 社区,共建、共治、共享未来!

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部