针对聚合模型的分析
- 列式存储,每个列单独存储
- 数据排序,每个列的数据都根据agg key的顺序进行存储
- 前缀索引,将agg key组合到一起,取前36个字节,创建一个前缀索引
为什么每一列都需要按照agg key进行排序
最左匹配的时候查找很快,比如说agg key的组合是:bu_id、cat1_id,输入条件where bu_id=1 and cat1_id=102的时候:
- 先通过最左的bu_id=1找到一波文档编号,bu_id排序的,可以二分查找快速找到文档编号
- 然后在用这波文档编号去cat1_id的列中去查找符合cat1_id=102的文档编号,bu_id=1对应的cat1_id也是排序的,查找cat1_id=102的时候理论上也可以进行二分查找
- 再去用最终得到的文档编号去查找指标列的数据,再进行聚合
对于每一列来说,都是文档编号对应列内容的形式,最左列是根据列内容排序的,所以可以很快找到文档编号,其他列都是根据文档编号排序的,所以通过最左列找到文档编号之后,在用文档编号去其他列找对应的内容会很快,因为文档编号排序所以,可以直接二分查找。
文档编号 | bu_id | Cat1_id |
---|---|---|
1 | 1 | 100 |
2 | 1 | 101 |
3 | 1 | 102 |
4 | 2 | 100 |
5 | 2 | 101 |
6 | 2 | 102 |
文档编号 | bu_id |
---|---|
1 | 1 |
2 | 1 |
3 | 1 |
4 | 2 |
5 | 2 |
6 | 2 |
文档编号 | cat1_id |
---|---|
1 | 100 |
2 | 101 |
3 | 102 |
4 | 100 |
5 | 101 |
6 | 102 |
可以发现,第一列是按照bu_id进行排序的,并且也是按照文档编号进行排序的,但是第二列并不是按照cat1_id排序的,仍然是按照文档id排序的,严格上来讲第二列cat1_id是基于bu_id内部按照cat1_id进行排序的。
为什么还需要前缀索引
上面通过多列的方式查找,需要查找两次的,先通过bu_id查找到文档id,在用文档id去cat1_id查找符合条件的cat1_id,前缀索引的作用是将这种多次的查询优化成1次查询。前缀索引的结构大概如下:
文档编号 | bu_id&cat1_id |
---|---|
1 | 1&100 |
2 | 1&101 |
3 | 1&102 |
4 | 2&100 |
5 | 2&101 |
6 | 2&102 |
通过前缀索引查询bu_id=1 and cat1_id=102,直接一次查询就能找到文档编号3。
一个有趣的现象
最近在做一个数据源的改造,将es的查询迁移到doris,迁移之后发现,原来es的索引占用空间有10几个G,但是迁移到doris之后,只占用了不到2个G,为什么差异会如此之大?
其实,了解了es和doris的底层储存原理之后,可以很容易发现其中的原因:
es的底层存储结构包括
- 倒排索引:需要查询的列都需要开启
- 正排索引:需要排序和聚合的列需要开启
- 原始文档:写入es的文档,json格式
doris底层存储结构包括
- 列式存储:对标es的正排索引
- 前缀索引:相当于一列单独的正排索引
- 一些其他的稀疏索引:布隆、最大最小值等
很容易发现,es比doris多了倒排索引和原始文档,而且原始文档是json格式,众所周知json格式会有冗余很多key的信息的,这个占了一个大头;对于两者都有的列式存储,至少在es5.x版本,es的列式存储的压缩效率是比较一般的,这部分es占用的空间也会比doris大一些。
下面是一个es索引各种数据结构占用空间的例子:
可以看出,source文件占用了大部分的空间。
1 | _44c.fdt 3.6G source的物理存储文件 |