分区内并行创建索引
介绍
OceanBase分区能按照某种规则自动地对数据表进行划分,每个分区存储表格的一部分数据。相对于分库分表方案,分区功能既能对用户比较透明,又具有良好的可扩展性。
在OceanBase中,针对分区表构建索引时,会对该表格的每个分区进行外部排序,构建相应的索引表,由于分区的数据量通常是GB,甚至是TB级别的,采用串行的外部排序构建索引容易达到瓶颈,因此,需要分区内并行构建索引的功能。
本文主要描述了OceanBase 分区内并行构建索引的方案,全文包括如下部分:
- OceanBase分区和索引介绍
- 现有构建索引方案的瓶颈分析
- 分区内并行建索引优化方案
OceanBase分区和索引介绍
分区
OceanBase分区能够自动地按照用户定义的分区规则,将表格中的数据划分到相应的分区,并且分区能够分布在不同的服务器上,具有良好的可扩展性。目前OceanBase支持二级分区,用户可以根据需求,制定分区格式。
索引
OceanBase的数据存储格式为SSTable,SSTable中的数据是按照表格的主键升序组织的。索引表的存储格式也是SSTable,其按照索引表的主键升序存储。例如,有一张数据表test,包含列a b c d e,其中主键为a,那么,test的SSTable中的数据是按照列a的升序方式存储的;如果在数据表test上建立一个索引index,假设包含列c、b、a,那么,index的SSTable按照主键c、b、a的升序存储的。
在数据表建立索引之后,需要按照分区针对索引列排序,以便得到按索引列升序方式组织的SSTable,通常地,分区的数据量往往比较大,无法完全装入内存直接排序,此时,一般采用磁盘作为辅助存储空间,进行外部排序,来构建索引。
现有构建索引方案的瓶颈分析
在数据量超过一定大小时,现有构建索引的方案采用单线程外部排序的方式来构建索引,先简单地介绍下这个流程:
假设允许使用的内存的缓存数量为B,待排序的数据总量为N个缓存大小,整个排序过程如下:
- 第一轮:每次读B个缓存的数据量,排序,然后写入磁盘,形成一个分片,重复该过程,直到所有的数据都排序过
- 第二轮到结束:合并上一轮的B个分片,形成一个更大的分片并写入磁盘,重复该过程,直到最后只有一个全局排序的分片
在分区数据量较大时,上述单线程构建索引的方案容易出现瓶颈,以某业务的某个索引构建时间进行分析,每轮花费的时间如下:
- 第一轮:3.6小时
- 第二轮:0.75小时
- 第三轮:0.63小时
- 第四轮:0.65小时
以第一轮为例,其中排序的开销为3小时,其他的开销,如写盘,迭代行的开销总共为0.6小时,因此,排序为瓶颈所在,主要考虑外部排序的并行化。
分区内并行建索引方案
分区内并行建索引的本质是外部排序的并行化,本节首先描述了外部排序并行化的常见思路,为了方便,以如下例子来说明:
假设有一张表,其创建语句为
CREATE TABLE test (c1 int primary key, c2 int);
在其上建立一个索引
CREATE INDEX index_test on test(c2);
数据表test已经按照c1排好序,构建index_test时,则需要按照索引列c2排好序,并存储到SSTable中。
外部排序并行化的思路
外部排序的并行化一般有如下思路:
- 思路一:并行局部排序后,单线程归并
- 思路二:并行局部排序后,按照索引列范围划分数据,接着做一次并行局部排序
- 思路三:先按照索引列范围划分数据,接着并行局部排序
目前采用思路一方案。
主要包含以下流程:
- 数据划分:按照数据表的主键范围进行数据划分
- 并行局部排序:每个线程对自己所属的数据进行局部排序
- 归并局部排序结果:局部排序后的结果并不是全局有序的,需要按照索引列的排序规则归并成全局有序的结果
在数据划分阶段,先按照数据表的主键范围进行数据划分,由于数据表已经是按照主键排好序,因此,划分数据的开销很小。但是,划分的数据是按照数据表的主键列(图中的C1)排好序的,通常对于索引列(图中的C2)并不是排好序的,例如,上图中CPU0分到的索引列数据为19 1 5 3 13。
在并行局部排序阶段,每个线程针对划分的数据,对索引列进行排序,例如,CPU0划分到的索引列数据C2为19 1 5 3 13,按照索引列排好序后,数据为1 3 5 13 19。
由于并行局部排序后,得到的数据并不是全局有序的,因此,还需要对各路局部排序的结果进行归并,最终得到全局有序的数据。
思路一的优点是:
- 数据划分可以做到均衡,每个线程局部排序的工作量大致相同
思路一的缺点是:
- 归并局部排序结果阶段是单线程工作的,扩展性较弱
从时间分析来看,最后一轮单线程merge的时间大约占串行建索引整体时间的1/8左右,采用思路一能提升的空间还是比较可观的。
外部排序并行化的实现
在实现时,除了上述的并行外部排序的逻辑以外,还需要考虑以下问题:
- 如何控制局部排序和归并排序任务的执行顺序?
- 外排过程中的临时数据如何存储?
如何控制局部排序和归并排序任务的执行顺序?
从思路一的执行过程看出,局部排序和归并排序的依赖关系如下图:
如果采用普通的任务调度器,建索引的逻辑里就必须要处理两种任务之间的依赖关系,并且有部分局部排序失败时,需要记录其状态,会使得建索引本身逻辑变得比较复杂。在OceanBase后台任务调度中,有基于DAG(有向无环图)的调度器,只需要把建索引的所有任务描述成一个有向无环图,调度器能自动按照依赖关系调度任务,并且部分前置依赖任务执行失败时,能够自动的终止建索引的DAG,从而把任务管理从建索引本身逻辑中解耦出来。
外排过程中的临时数据如何存储?
临时排序数据的存储方式有两种选择:
- 采用OceanBase存储层的宏块来存储
- 采用临时文件来存储
与使用临时文件来存储相比,采用宏块的存储方式有如下优点:
- 可以把建索引的资源消耗纳入到OceanBase的资源调度隔离中,方便对建索引过程中租户使用的资源进行控制
- 一般OceanBase服务器上用于宏块数据文件的空间较大,而临时的空间不太多,因此,使用宏块存储能够避免临时空间不足无法建索引的问题
性能测试
测试场景为,在一个lz4压缩后共440GB的数据表中,构建一张约40GB数据的索引,测试结果如下
文章转载自公众号:OceanBase