Doris存储层设计介绍1——存储结构设计解析(索引底层结构)

目录

一、概述

1.1 存储结构的整体介绍

1.2 存储结构的设计目标

二、存储文件格式

2.1 存储目录结构

​编辑

2.2 Segment v2文件结构

三、Footer信息

3.1 列的meta信息

3.2 列索引的meta信息

四、前缀索引(Short Key Index)

4.1 功能介绍

4.2 索引生成

4.3 索引的底层存储结构

4.3.1 Segment footer

4.3.2 Short Key Page

4.4 查询过滤

4.5 应用案例

 五、Ordinal lndex (—级索引)

5.1 功能介绍

5.2 索引生成

5.3 索引的底层结构

5.4 查询过滤

六、ZoneMap索引

6.1 功能介绍

6.2 索引生成

6.3 索引的底层结构

6.4 查询过滤

6.5 应用案例

七、Bitmap索引

7.1 功能介绍

7.2 索引生成

7.3 索引的底层结构

7.4 查询过滤

7.5 应用案例

八、 Bloom filter索引

8.1 功能介绍

8.2 索引生成

8.3 索引的底层结构

8.4 查询过滤

8.5 应用案例

一、概述

1.1 存储结构的整体介绍

    Doris是基于MPP架构的交互式SQL数据仓库,主要用于解决近实时的报表统计和多维分析。Doris高效导入、查询离不开其存储结构的设计。通过阅读Doris BE模块代码,分析Doris BE模块存储层的实现原理,主要包括Doris列存的设计、索引设计、数据读写流程、Compaction流程、Tablet数据分片和Rowset版本管理等功能。通过三篇文章来逐步进行阐述,分别为:

Doris存储层设计介绍1——存储结构设计解析(索引底层结构)-CSDN博客文章浏览阅读1.1k次,点赞33次,收藏19次。Doris存储层设计介绍1——存储结构设计解析(索引底层结构)https://blog.csdn.net/SHWAITME/article/details/136155008?spm=1001.2014.3001.5502

Doris存储层设计介绍2——数据写入及删除流程-CSDN博客文章浏览阅读336次,点赞9次,收藏9次。Doris存储层设计介绍2——数据写入及删除流程https://blog.csdn.net/SHWAITME/article/details/136156144?spm=1001.2014.3001.5501

Doris存储层设计介绍3——读取流程、Compaction流程分析-CSDN博客文章浏览阅读34次。Doris存储层设计介绍3——读取流程、Compaction流程分析https://blog.csdn.net/SHWAITME/article/details/136223060?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22136223060%22%2C%22source%22%3A%22SHWAITME%22%7D

    该篇文章主要介绍了Segment V2版本的存储层结构,包括内置的前缀索引(Short Key Index)Ordinal 索引ZoneMap索引以及用户手动创建Bitmap索引Bloom filter索引

1.2 存储结构的设计目标

  • 批量导入,少量更新
  • 绝大多数的读请求
  • 宽表场景,读取大量行,少量列

二、存储文件格式

2.1 存储目录结构

  • (1)Doris中的数据分布如图1——分区,分桶及副本之间的关系

  • (2)Doris中数据组织如图2——表,分区分桶,tablet,rowset,segment文件的关系

  • (3)Segment文件结构

         Segment表示Rowset中的数据分段,多个Segment会构成一个Rowset。 segment文件既是存储文件的最小单元,也是索引文件生成的文件存储区域。segment文件的目录结构和segment文件本身的构成:

     对存储数据的路径管理是通过be.conf配置文件中的参数 storage_root_path配置的。在storage目录下的data目录中,存放着以分桶 id为命名的目录,下一层级是tablet id命名的tablet目录,在tablet目录下面就是Segment文件。Segment文件可以有多个,一般按照大小进行分割,默认为256MB。该文件命名是以{rowset_id}_{segment_id}.dat结构的dat文件。

   总结:分区是逻辑上的概念,只记录在表的元数据中,每个分区的数据会按照分桶键进行hash分桶,表中的数据经过分区分桶后,就会形成一个个数据分片tablet(实际的物理存储单元),尽量均匀的分布在集群的所有BE中。 tablet是StarRocks中数据均衡的最小单位,默认的三副本是指同一个 tablet会在集群中保留三份,每个tablet之间的数据没有交集,在物理上独立存储。集群的副本修复或磁盘均衡均是以tablet为单位移动或者克隆的。且每次的数据导入、更新或者删除,本质上也是对一个个tablet中的数据进行操作。

     一个tablet中包含若干连续的rowset,而rowset是逻辑概念,代表tablet中一次数据变更的数据集合(数据变更包括了数据新增,更新或删除等),rowset按版本信息进行记录,每次变更就会生成一个个版本。一个rowset可能会包含多个segment,segment可以认为是rowset中的数据分段。执行数据导入时,每完成写入一个segment就会增加一个文件块对应。segment文件可以有多个,一般按照大小进行分割,默认为256MB 。

2.2 Segment v2文件结构

  Segment整体的文件格式分为data region数据区域、index region索引区域、footer三个部分,如下图所示:

       

  • Data Region: 用于存储各个列的数据信息,每个column的data数据是按照page为单位分块存储的,每个page的大小一般是64kb(即:Data Page数据块大小一般是64kb)
  • Index Region: Doris中将各个列的index数据统一存储在index region,这里的数据会按照列粒度进行加载。index索引类型包括:前缀索引(Short Key Index)、Ordinal 索引、ZoneMap索引、Bitmap 索引和Bloom filter索引。
  •  Footer:见下文

三、Footer信息

     Footer信息段在文件的尾部,存储了文件的整体结构,包括数据域的位置,索引域的位置等信息,其中有SegmentFooterPB,CheckSum,Length,MAGIC CODE4个部分。

SegmentFooterPB的数据结构如下:

   SegmentFooterPB采用了PB格式进行存储,主要包含了列的meta信息(Column Meta)、索引的meta信息(Column Index Meta),Segment的short key索引信息、总行数num_rows

3.1 列的meta信息

  • Columnld:当前列在模式中的序号
  • Uniqueld:全局唯一的id
  • Type:列的类型信息
  • Length:列的长度信息
  • Encoding:列的编码格式
  • Compression:列的压缩格式
  • Dict PagePointer:列的字典信息

3.2 列索引的meta信息

  • Ordinal lndex:存放列的一级索引的meta信息
  • ZoneMapIndex:存放ZoneMap索引的meta信息,内容包括了最大值、最小值、是否有空值、是否没有非空值。SegmentZoneMap存放了全局的ZoneMap信息,PageZoneMaps则存放了每个页面的统计信息
  • BitMapIndex:存放BitMap索引的meta信息,内容包括了BitMap类型,字典数据BitMap的数据
  • BloomFilterIndex:存放了BloomFilter索引信息

四、前缀索引(Short Key Index)

4.1 功能介绍

    由于Doris底层数据是按照排序键排序后存储的,而Short Key Index前缀索引,是在key (duplicate key、aggregate key、unique key、primary key)排序的基础上,实现的一种根据给定一定数量(不超过3列,不超过36个字节,遇到字符串会自动截断)前缀列,每间隔一定行数(1024),生成的一个索引项 (稀疏索引)。当查询的过滤条件命中前缀索引时,就能快速定位到数据存储所在的比较精确地址。

4.2 索引生成

    前缀索引采用了稀疏索引结构,在数据写入过程中,每隔一定行数,会生成一个索引项。这个行数为索引粒度默认为1024行

4.3 索引的底层存储结构

4.3.1 Segment footer

   Segment文件的footer中保存了Short Key Page在Segment中的offset和size大小,以便数据读取时能够正确的从Segment文件中加载出前缀索引数据。

4.3.2 Short Key Page

   KeyBytes中存放了索引项数据,OffsetBytes存放了索引项在KeyBytes中的偏移。

4.4 查询过滤

   数据查询时会打开Segment文件,从Segment文件的footer中获取Short Key Page的offset和大小,然后从Segment文件中读取Short Key Page保存的索引,从中解析出每一条的前缀索引项。

   ps: 因为前缀索引是稀疏索引,只能粗粒度的定位出key可能存在的范围,然后使用二分查找算法去精确定位定位key所在的行。

4.5 应用案例

     Short Key Index前缀索引采用了前36个字节,作为这行数据的前缀索引。当遇到varchar类型时,前缀索引会直接截断。前缀字段的数量不超过 3 个,前缀索引项的最大长度为 36 字节

#创建明细表,在 duplicate key 中指定排序列为 uid 和 name。说明建表时如何指定排序列,以及其前缀索引的组成。

create table user_access (
    uid int,
    name varchar(64),
    age int, 
    phone varchar(16),
    last_access datetime,
    credits double
)
duplicate key(uid, name)
distributed by hash (uid, name);

# 由于前缀索引项的最大长度为36字节,超过部分会被截断,因此该表的前缀索引项为 uid (4 字节) + name (只取前 32 字节),前缀字段为 uid 和 name。

select * from user_access  where uid  = 1 and name = 'xx'

 上述案例中:当查询条件是前缀索引的前缀时,可以极大的加快查询速度,代码如下:

select * from user_access  where uid  = 1 and name = 'xx'
# 上述代码的查询性能远高于下面的代码

select * from user_access  where  phone  = '150003212317'

 五、Ordinal lndex (—级索引)

5.1 功能介绍

    Doris底层采用列存的方式来存储数据,每一列数据会被分为多个Data Page数据页,见下图:

5.2 索引生成

    数据刷写时,会为每一个 Data Page(每列的每一个数据块,大小一般是64kb)生成一条Ordinal lndex索引,该索引保存Data Page在Segment文件中的offset、Data Page的大小以及Data Page的起始行号。而所有Data Page数据页的索引项会保存在一个Ordinal lndex Page中,而Segment文件中的footer会保存Ordinal lndex Page在Segment文件中的offset、Ordinal lndex Page的大小。综上,首先通过Segment文件的footer找到Ordinal lndex Page,然后通过Ordinal lndex Page中的索引项找到Data Page数据页

   Ordinal lndex索引提供了通过行号来查找Column Data Page数据页的物理地址,Ordinal lndex索引能够将按照列存储的数据按行对齐,可以理解为一级索引。因此,其他类型的索引在查找数据的时候,都要借助Ordinal lndex(一级索引)查找 Data Page数据页的物理地址。

    在一个segment文件中,数据始终按照key排序存储,数据写入的过程中,每一列的data page会由Ordinal Index管理,他会记录每一列对应的data page的offset,size大小,和该data page的第一个数据的行号信息。这样在查询的时候,就能通过 Ordinal lndex索引够快速定位到对应的data page的物理地址。

5.3 索引的底层结构

   ps: column的data数据按照page为单位分块存储,每个page大小一般为64kb。

5.4 查询过滤

   数据查询时,会加载每一列的Ordinal 索引数据,通过Segment footer中记录的Ordinal索引的Meta信息判断当前列是否存在Ordinal Index Page,即判断当前列是否有多个Data Page。

   如果当前列存在Ordinal Index Page,则从Segment footer中获取Ordinal Index Page在Segment中的offset和Ordinal Index Page的大小,然后从Segment文件中读取Ordinal Index Page数据,然后通过Ordinal lndex Page中的索引项找到当前列的每一个Data Page的信息,包括Data Page在Data Page在Segment中的offset以及Data Page的大小。

六、ZoneMap索引

6.1 功能介绍

   Doris会为Segment文件中的一列数据(key 列)自动添加ZoneMap索引,注意:当表的模型为dupulcate时,会所有字段开启zonemap索引。

   ZoneMap索引存储了Segment和每个列对应每个Page的统计信息。Doris可以根据这些统计信息,快速判断这些数据块是否可以过滤掉,从而减少扫描数据量,提升查询速度。统计信息包括了Min最大值、Max最小值、HashNull空值、HasNotNull不全为空的信息。

6.2 索引生成

  待补充!

6.3 索引的底层结构

  待补充!

6.4 查询过滤

  待补充!

6.5 应用案例

   在数据查询涉及到范围条件过滤时,会按照ZoneMap统计信息选取扫描的数据范围。

#创建明细表,排序键是uid, name
create table user_access (
    uid int,
    name varchar(64),
    age int, 
    phone varchar(16),
    last_access datetime,
    credits double
)
duplicate key(uid, name)
distributed by hash (uid, name);

# 上述代码会对udi,name字段创建 ZoneMap索引,对字段uid进行过滤
select * from user_access  where uid >5 --范围条件过滤

七、Bitmap索引

7.1 功能介绍

      Doris支持对低基数列创建Bitmap位图索引来加速数据查询。高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

      Bitmap位图索引创建时需要通过  create index 进行创建。Bitmap的索引是整个Segment中的Column字段的索引,而不是为每个Page单独生成一份。在写入数据时,会维护一个map结构,去记录下每个key值对应的行号,并采用Roaring位图对rowid进行编码。生成索引数据时,首先写入字典数据,即将map结构的key值写入到DictColumn中。然后,key对应Roaring编码的rowid(value值)以字节方式将数据写入到BitMapColumn。

 总结而言,Bitmap索引由两部分组成:

  • 有序字典(key值):有序保存一列中所有的不同取值
  • 字典值的Roaring位图:保存有序字典中每一个取值的Roaring位图,即:表示字典值在该列中的行号(value值)。

   如下图:column列取值为:[x, x, y, y, y, z, y, x, z, x],一共有10行。则该列数据对应的Bitmap索引,其中有序字典为{x, y, z};x, y, z字典值对应的位图分别是:

  • x的位图: [0, 1, 7, 9]
  • y的位图: [2, 3, 4, 6]
  • z的位图: [5, 8]

ps辅助理解:bitmap索引的实现原理

 例:有一列的取值如下

如果针对column_xxx 列创建 bitmap索引后,那么它在数据库中的存储方式就是这样的:

 

   有序字典记录的是该索引列去重后,且排序后的数据(key),但是bitmap(字典值的位图)中记录每个列数据所在的行号。这样设计的好处:既可以等值匹配,也可以使用in等范围查找,快速定位到目标列的数据。

Doris的Bitmap迷惑体验这个Bitmap索引功能,真有那么神奇吗?icon-default.png?t=N7T8https://mp.weixin.qq.com/s/LbyMvVEZ2RalWtkIP2LHSw

7.2 索引生成

    数据刷写时,会给用户指定的列创建Bitmap索引。向列中每添加一个值,都会更新当前列的Bitmap索引。从Bitmap索引的有序字典中查找添加的值是否已经存在,如果本次添加的值在Bitmap索引的有序字典中已经存在,则直接更新该字典值对应的Roaring位图,如果本次添加的值在Bitmap索引的有序字典中不存在,则将该值添加到有序字典,并为该字典值创建Roaring位图。此外null值也会有单独的Roaring位图。Bitmap索引的字典数据和Roaring位图数据分开存储。  

7.3 索引的底层结构

  待补充!

7.4 查询过滤

 待补充!

7.5 应用案例


#创建明细表,对字段city 设置位图索引
create table user_access (
    uid int,
    name varchar(64),
    age int, 
    city varchar(16),
    index city_index(city )
)
duplicate key(uid, name)
distributed by hash (uid, name);

#建表后使用create index 创建Bitmap索引
create index city_index on user_access (city);

#由于cty的取值比较少,建立数据字典和位图后,通过扫描位图便可以快速查找出匹配行。
select * from user_access where city in ('北京','上海');


注意事项:

  1. Bitmap索引适用于可使用等值条件 (=) 查询或 [not] in 范围查询的列(低基数的列)
  2. 主键表和明细表中所有列都可以创建 Bitmap 索引;聚合表和更新表中,只有维度列(即 Key 列)支持创建 bitmap 索引。
  3. 支持为如下类型的列创建 Bitmap 索引:
  • 日期类型:date、datetime。
  • 数值类型:tinyint、smallint、int、bitgint、largeint、decimal 和 boolean。
  • 字符串类型:char、string 和 varchar。

八、 Bloom filter索引

8.1 功能介绍

    Doris支持用户对适用于高基数列(取值区分度比较大的字段)添加Bloom Filter(布隆过滤器)索引,Bloom filter索引可以快速判断表的数据文件中是否可能包含要查询的数据,如果不包含就跳过,从而减少扫描的数据量。  ps:高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

   Bloom filter布隆过滤器实际上是由一个超长的二进制位数组和一系列的哈希函数组成的,通常应用在需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合,因为Bloom filter布隆过滤器有一定的误判率~

  例如:下图中 m=18, k=3 (m是该Bit数组的大小,k是Hash函数的个数),集合中已有的 x、y、z 三个元素通过3种不同的哈希函数散列到Bit 数组中。当判断元素w是否在该集合存在时,通过3 种Hash函数计算之后因为有一个比特为0,因此得出元素w不在该集合中。

   总结:布隆过滤器是怎么判断某个元素是否在集合中呢?

  元素经过哈希函数得到所有的偏移位置,若这些位置全都为1,则说明这个元素大概率是在这个集合中,若有一个不为1,则判断这个元素一定不在这个集合中。

8.2 索引生成

   数据刷写时,会给每一个Data Page数据块(大小一般是64kb)创建一条Bloom Filter索引项。

8.3 索引的底层结构

8.4 查询过滤

     数据查询时会加载列的Bloom Filter索引数据,并解析出每一个Data Page的Bloom Filter索引项

8.5 应用案例


#创建明细表,对字段name 设置BloomFilter索引
create table user_access (
    uid int,
    name varchar(64),
    age int, 
    phone varchar(16),
    last_access datetime,
    credits double
)
duplicate key(uid, name)
distributed by hash (uid, name)
properties("bloom_filter_columns" = "name");

select * from user_access  where name = 'xx';

#由于name的区分度较大,为了提升sql的查询性能,对name数据增加了BloomFilter索引
#properties("bloom_filter_columns" = "name");

 注意事项:

  1. 主键表和明细表中所有列都可以创建 bloom filter 索引;聚合表和更新表中,只有维度列(即 key 列)支持创建 bloom filter索引。
  2. 支持为如下类型的列创建 bloom filter 索引:
  • 数值类型:smallint、int、bigint 和 largeint。
  • 字符串类型:char、string 和 varchar。
  • 日期类型:date、datetime。

九、总结

  • 排序键

    在创建表的时候,可以指定一个列或者多个列(一般来说前三列)作为这个表的排序键(Sort Key),当数据导入时,数据会按照排序键的定义,按照顺序存储在磁盘空间上,当查询根据这些排序字段进行查询时,就能够根据已经排好序的数据,快速定位到你要查询的对应数据集所对应的磁盘地址,在scan阶段就能够大面积减少无关数据,加速查询。

  排序键的具体介绍可以见文章:

StarRocks表设计——排序键和数据模型-CSDN博客文章浏览阅读528次,点赞15次,收藏9次。2.2 StarRocks表设计——排序键和数据模型https://blog.csdn.net/SHWAITME/article/details/136136900?spm=1001.2014.3001.5501

  • 前缀索引

    由于Doris底层数据是按照排序键排序后存储的,而Short Key Index前缀索引,是在key (duplicate key、aggregate key、unique key、primary key)排序的基础上,实现的一种根据给定一定数量(不超过3列,不超过36个字节,遇到字符串会自动截断)前缀列,每间隔一定行数(1024),生成的一个索引项 (稀疏索引)。当查询的过滤条件命中前缀索引时,就能快速定位到数据存储所在的比较精确地址。

  • Ordinal Index

   Ordinal lndex索引提供了通过行号来查找Column Data Page数据页的物理地址,Ordinal lndex索引能够将按照列存储的数据按行对齐,可以理解为一级索引。因此,其他类型的索引在查找数据的时候,都要借助Ordinal lndex(一级索引)查找 Data Page数据页的物理地址。

    在一个segment文件中,数据始终按照key排序存储,数据写入的过程中,每一列的data page会由Ordinal Index管理,他会记录每一列对应的data page的offset,size大小,和该data page的第一个数据的行号信息。这样在查询的时候,就能通过 Ordinal lndex索引够快速定位到对应的data page的物理地址。

  • ZoneMap 索引

   Doris会为Segment文件中的一列数据(key 列)自动添加ZoneMap索引,注意:当表的模型为dupulcate时,会所有字段开启zonemap索引。

   ZoneMap索引存储了Segment和每个列对应每个Page的统计信息。Doris可以根据这些统计信息,快速判断这些数据块是否可以过滤掉,从而减少扫描数据量,提升查询速度。统计信息包括了Min最大值、Max最小值、HashNull空值、HasNotNull不全为空的信息。

  • BitMap索引

    Doris支持对低基数列创建Bitmap位图索引来加速数据查询。高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

    Bitmap位图索引创建时需要通过  create index 进行创建。Bitmap的索引是整个Segment中的Column字段的索引,而不是为每个Page单独生成一份。在写入数据时,会维护一个map结构,去记录下每个key值对应的行号,并采用Roaring位图对rowid进行编码。生成索引数据时,首先写入字典数据,即将map结构的key值写入到DictColumn中。然后,key对应Roaring编码的rowid(value值)以字节方式将数据写入到BitMapColumn。   

  • BloomFilter 索引

     Doris支持用户对适用于高基数列(取值区分度比较大的字段)添加Bloom Filter(布隆过滤器)索引,Bloom filter索引主要用于快速判断某列中是否存在某个值。BloomFilter判定该列中不存在指定的值,如果确定不存在,就不会读取这个数据文件;如果索引判定该列中存在指定的值,也有可能这个值实际上不会存在,这时,会读取数据文件来进一步确认。

  ps:高基数列:例如UserID,低基数列:例如性别,婚姻状态等。

参考文章:

聊聊分布式 SQL 数据库Doris(七)-腾讯云开发者社区-腾讯云

【Doris】Doris存储层设计介绍1——存储结构设计解析_doris 存储原理-CSDN博客

深度解析|Apache Doris 索引机制解析

索引概述 - Apache Doris

索引 | StarRocks

  • 37
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于 Apache Doris 的数据仓库平台架构设计如下: 1. 架构模式: - 采用分布式架构模式,将数据仓库划分为多个节点,每个节点可以独立存储和处理数据,同时支持横向扩展,能够处理大规模的数据量和并发请求。 2. 数据存储: - 使用分布式文件系统(如HDFS)存储数据,数据按照数据表的划分进行存储,支持数据的分片和复制,提高数据的可靠性和可用性。 - 数据以列式存储的方式存储,提高查询效率。 - 支持数据的压缩和索引,降低存储空间和提高查询效率。 3. 元数据管理: - 使用元数据管理系统(如MySQL存储数据的元信息,包括表结构、分区、数据位置等。 - 元数据管理系统支持水平扩展,保证元数据的一致性和高可用性。 4. 查询引擎: - 使用分布式查询引擎,支持SQL语法,能够高效地执行复杂的数据查询和分析操作。 - 支持预编译和查询优化技术,提高查询性能。 5. 数据加载和导出: - 支持多种方式的数据加载和导出,如批量导入、实时流入、增量导入、导出到外部系统等。 - 支持数据的转换和清洗,提高数据的质量和一致性。 6. 安全性和权限管理: - 支持访问控制,可以对用户和角色进行权限管理,确保数据的安全性和合规性。 - 支持数据加密和身份认证,保护数据的机密性和完整性。 7. 可视化和监控: - 提供用户友好的可视化界面,方便用户管理和操作数据仓库。 - 支持实时监控和告警功能,及时发现和解决系统故障和性能问题。 总之,基于 Apache Doris 的数据仓库平台架构设计具备高可扩展性、高性能和高可靠性的特点,可以满足大规模数据处理和查询的需求,并提供丰富的功能和工具支持,帮助用户实现高效的数据分析和决策。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值