作者简介:严军(花名吉远),十年以上专注于数据库存储领域,对大数据、分布式、高并发、高性能、高可用有丰富的经验.主导过蚂蚁集团核心系统去IOE,数据库LDC单元化多活项目,常年负责蚂蚁重大数据库活动(如双11、双12、春节红包大促),现任阿里云分布式数据库架构师和云数据库架构师,专注于金融行业数字化转型过程中头部客户同构和重构数据库架构和迁移咨询工作。精通Oracle、Mysql、OceanBase数据库 ,擅长分布式数据库架构设计、数据库优化、容灾高可用方案设计。
问题描述
某保险客户核心系统,从Oracle 迁移到OceanBase 之后,发现数据存储空间出现明显膨胀问题,数据空间datasize=9857715.48M,实际存储占用空间17790702.00M. required_mb - data_mb 值很大判断数据空洞较为严重,客户提出要求降低存储空间。
上图查询sql参考:空洞情况检查方法
原因分析
OceanBase 存储出现空洞的原因:OceanBase的数据文件SSTABLE按照主键顺序进行存储,如果业务数据插入比较离散,期间有合并时,2M宏块出现分裂会导致数据空洞率提升,进而导致存储空间大于数据数据空间, 这种现象多见于业务主键非递增插入的场景。
解决方法
对空洞较大的表强制执行全量合并
强制执行全量合并,不执行渐进合并。
- 对于新建表:set default_progressive_merge_num=1。
- 对于现存表:ALTER TABLE $table SET progressive_merge_num=1; 这样把需要的表设置上,再进行合并。
注意:全量合并会消耗大量资源,需要设置完之后再设置回0。
progressive_merge_num值说明:
- 0 :表示执行渐进合并,且渐进合并的次数为 100。
- 1:表示强制执行全量合并,不执行渐进合并。
- 大于 1 :表示发生 Schema 变更时按照指定轮次做渐进合并。
空洞情况检查方法
select avd.database_name,
avt.tenant_id,
Case avt.table_type
When 3 Then
'TABLE'
When 5 Then
'INDEX'
Else
''
End As segment_type,
Case avt.table_type
When 3 Then
Sum(avmt.row_count)
Else
''
End As row_count,
round(Sum(avmt.data_size) / 1024 / 1024, 2) As data_mb,
round(Sum(avmt.required_size) / 1024 / 1024, 2) As required_mb
From __all_virtual_table avt
Inner Join __all_virtual_partition_table avmt
On avt.tenant_id = avmt.tenant_id
And avt.table_id = avmt.table_id
Inner Join __all_virtual_database avd
On avt.database_id = avd.database_id
And avt.tenant_id = avd.tenant_id
Where avmt.role = 1
And table_type In (3, 5)
Group By avd.database_name, table_type, avt.tenant_id
Order By database_name, table_type;
/*
select table_type, index_status, index_type, part_level from __all_virtual
_table;
table_type: 系统表(0),系统视图(1),虚拟表(2),用户表(3),用户视图(4),索引表(5)
index_status: 不可用(1),可用(2)
index_type: 局部普通索引(1),局部唯一索引(2),全局普通索引(3),全局唯一索引(4),主键索
引(5)
part_level: 不分区(0),一级分区(1),二级分区(2)
__all_virtual_meta_table 是基线数据
__all_virtual_storage_stat 是基线加转储数据
*/
合并管理概述
select avd.database_name,
avt.tenant_id,
Case avt.table_type
When 3 Then
'TABLE'
When 5 Then
'INDEX'
Else
''
End As segment_type,
Case avt.table_type
When 3 Then
Sum(avmt.row_count)
Else
''
End As row_count,
round(Sum(avmt.data_size) / 1024 / 1024, 2) As data_mb,
round(Sum(avmt.required_size) / 1024 / 1024, 2) As required_mb
From __all_virtual_table avt
Inner Join __all_virtual_partition_table avmt
On avt.tenant_id = avmt.tenant_id
And avt.table_id = avmt.table_id
Inner Join __all_virtual_database avd
On avt.database_id = avd.database_id
And avt.tenant_id = avd.tenant_id
Where avmt.role = 1
And table_type In (3, 5)
Group By avd.database_name, table_type, avt.tenant_id
Order By database_name, table_type;
/*
select table_type, index_status, index_type, part_level from __all_virtual
_table;
table_type: 系统表(0),系统视图(1),虚拟表(2),用户表(3),用户视图(4),索引表(5)
index_status: 不可用(1),可用(2)
index_type: 局部普通索引(1),局部唯一索引(2),全局普通索引(3),全局唯一索引(4),主键索
引(5)
part_level: 不分区(0),一级分区(1),二级分区(2)
__all_virtual_meta_table 是基线数据
__all_virtual_storage_stat 是基线加转储数据
*/
合并操作(Major Compaction)是将动静态数据做归并,会比较费时。当转储产生的增量数据积累到一定程度时,通过 Major Freeze 实现大版本的合并。合并与转储的最大区别在于,合并是集群上所有的分区在一个统一的快照点和全局静态数据进行合并的行为,是一个全局的操作,最终形成一个全局快照。
合并分类
按照合并数据量,合并可以分为:
- 全量合并:将静态数据全部读出并和动态数据合并为最终的静态数据。合并时间长,耗费 IO 和 CPU。
- 增量合并:仅仅合并被修改过的宏块,没有改变的宏块进行复用。增量合并极大地减少了合并的工作量,是 OceanBase 数据库目前默认的合并算法。
- 渐进合并:每次全量合并一部分,若干轮次后整体数据被重写一遍。
- 并行合并:将数据划分到不同线程中并行做合并。
全量合并与渐进合并
渐近合并是什么
OceanBase在设计之初就考虑到了Online DDL的需求,目前在OceanBase中加列、减列、建索引等DDL操作都是不阻塞读写的,也不会影响到多副本间的paxos同步。加减列的DDL变更是实时生效的,OB将对存储数据的变更延后到每日合并的时候来做。和Mysql一样,对于某些DDL操作如加减列等,OB是需要将所有数据重写一遍的,如果在一次每日合并过程中完成对所有数据的重写,那么对存储空间和合并时间都会是一个比较大的考验。为了解决这个问题,OB引入了渐进合并,既然一次合并做代价太大,那就搞多次。OB会将DDL变更造成的数据重写分散到多次每日合并中去做,假设把渐进轮次设置为60,那么一次合并就只会重写60分之一的数据,在60轮合并过后,数据就被整体重写了一遍。渐进合并减轻了DBA做DDL操作的负担,同时也使得DDL变更更加平滑。
渐近合并的参数
schema中的progressive_merge_num属性来决定渐近的轮次,假设progressive_merge_num=5,表示5轮合并重写完major sstable。 schema中的progressive_merge_round表示本次合并所处的渐近合并轮次
如何指定全量合并
当progressive_merge_num=0或1时,如果发生了DDL对于存储层的变更,会在一轮合并中重写掉major sstable
全量合并与非全量合并
全量合并:所有宏块不重用,全部打开重写
非全量合并:宏块会重用,只打开有数据变更的宏块
当执行渐近合并时,只有本次渐近轮次相关的宏块会做全量合并,其他部分做非全量合并