谈到数据仓库, 必然都会涉及海量历史数据, 但是对于历史数据有个共识, 就是越近的数据访问频率越高, 越久远的数据访问频率越低。
作者 | 祁国辉
编辑&设计 | 宇亭
责编 | 韩 楠
当历史数据访问频率低到一定程度,我们就会发现数据的存储成本和收益开始倒挂, 存储数据有点得不偿失。
这个时候解决的思路,有两个方向。
第一,把历史数据从系统中迁移出去, 搬迁到更加廉价的存储平台上, 比如Hadoop, 比如对象存储。第二,考虑使用数据压缩技术, 提升硬盘的利用率, 从而降低数据存储成本。这两种方式可以单独实施, 也可以结合使用, 建立起一整套的数据生命周期管理流程。
首先看看数据库压缩中的符号表
说到数据压缩, 大家一定都很熟悉, 首先是最常用的ZIP 和RAR;
另外,我们的图像压缩JPG和MPG,都是大家耳熟能详的技术。
但是这些都是文件压缩算法, 一般而言, 一个文件在使用的时候, 都是需要全文件装载的, 所以压缩和解压缩都是全文件进行的。可是对于数据库来说,使用场景有所不同, 由于我们对于数据库的访问, 绝大多数都是访问数据库中的一部分, 因此压缩和解压缩没必要 也不可能动辄就全表操作,所以数据库的压缩是有不同技术的。
所有数据压缩,都会涉及独有的压缩和解压缩的算法。一般而言, 压缩和解压缩的算法,需要有三个方面的指标,进行评估:
• 第一,压缩率。我们希望压缩率尽量高;
• 第二,算法复杂度, 即压缩和解压缩时的CPU消耗,我们是希望对CPU的消耗尽量小;
• 第三,速度, 即压缩和解压缩的速度,我们的目标是速度尽量快,对用户无感是最高要求。
但是这仨指标很难同时达到, 所以不同的算法都是在这几个指标之间折衷,寻找最佳的平衡点。
不过本文中, 我们不讨论具体的算法, 而是侧重讨论一下数据库压缩当中的一些基本概念,希望对大家在选用数据库压缩技术的时候,能够有所帮助。
数据库压缩中最常用的一个概念,就是符号表, 那么符号表是干什么的呢?
我们简单讲述一下数据库压缩的原理, 当数据库写入一行数据的时候,首先检查一下符号表, 然后根据符号表来把实际数据转换成为符号表中的符号存储在磁盘上;反之, 数据库读一行数据的时候,磁盘返回的是一行符号, 需要根据符号表的映射, 再解释成为真实的数据。
那么符号表和真实数据的映射和转换过程,就表现为压缩和解压缩过程。
下面看看静态符号表和动态符号表, 在数据库最早引入压缩算法的时候, 很多厂商采用的是静态符号表, 意思是在数据库的表压缩之前, 首先扫描一下全表数据, 然后建议一个全局的符号表, 那么之后的数据压缩和解压缩都可以基于这个全局符号表。小录
这种全局符号表的优势是速度会快, 但是有一个小问题, 如果这个表中出现压缩之前没有出现的数据, 那么这个数据就没有办法压缩。
所以这种压缩算法,只适合一些特殊的场合。
很快就被动态符号表的技术淘汰了。动态符号表的原理是这样, 数据入库的时候, 当一个数据首次出现, 系统就会给一个符号,当数据再次出现, 系统就会直接引用之前的符号。这样一来,所有的数据都会被压缩。但是缺点在于复杂度有所上升。不过因为它的使用场景比较广泛,所以大多数厂商都会选择动态符号表。
行压缩、列压缩和混合压缩中的压缩比
了解了符号表的原理, 那么我们就可以简要地分析一下,不同数据存储方式带来的不同的压缩方法, 因为压缩之后的数据是基于数据库进行存储的, 所以压缩和数据存储格式有很大的关系。
首先最简单的是行压缩, 这是大多数传统数据库采用的方式, 数据库存储数据是以行的方式进行存储的,而数据是存储在数据块中。那么一般情况下,动态符号表就保存在每个数据块中, 只负责本数据块内的数据的映射和转换。
一个简单的原理, 符号表中一个符号引用的次数越多, 那么数据压缩的压缩比就越高, 因为每引用一次符号,就代表数据块中使用较短的符号,来取代了原来较长的数据的次数越多。
所以想提高行压缩的压缩比,就是尽量把相同的数据保存在一起, 方法有下面这几种:
• 数据块越大, 数据重复的几率越高, 所以, 尽量采用大数据块;
• 表宽度要小, 因为表太宽, 一个数据块中相同的数据都可能出现不了几次;
• 尽量在入库前按照重复率高的大字段进行排序, 这样相同的数据就可以出现在相邻的位置。
接下来,我们看看列压缩, 大家都知道列式数据库的一个很大的优势就是,数据按照列进行存储, 而一般而言,数据库中的同一个列中数据重复的几率很高, 更别提某些比如性别、省份这样的属性列, 即使数据库中有上千万条记录, 这个列上的不同数据也是很小的几种。
这样的数据进行压缩的时候, 压缩比就会特别高, 但是我们也知道, 对于这种列式数据库, 如果需要对压缩数据进行update或者删除, 那么很有可能代价会特别大, 有可能会影响到所有的行, 所以列式数据库的数据压缩,适合一些不需要进行数据修改的场合。
但是,如果启用了列式压缩, 那么压缩比是所有数据库中最大的, 而且某些数据库还可以只针对特定的列进行压缩, 灵活性又有了更大的提高。
下面我们看一下Oracle的混合列压缩(HCC), 混合列压缩目前只在Exadata上可用, 它可以提供目前行式数据库中最高的压缩比。 我们可以简要了解一下:
混合列压缩, 顾名思义,首先它采用了列压缩的模式, 其次它是一种混合模式, 怎么理解呢?
这里需要引入一个新的概念 CU(压缩单元), 就是在数据块之上,进一步划分出一层逻辑概念, 叫做压缩单元。
在一个压缩单元内, 数据库使用列压缩的模式, 提高压缩比, 但是当数据需要修改和更新的时候, 会把影响限制在一个压缩单元之内, 这样一来就可以既享受列压缩的高压缩比, 又尽量减少列压缩中数据更改带来的全局影响。
和前面讨论的一样, 压缩单元越大, 数据重复率越高, 当然存取效率的影响也越大, 所以对于不更改的归档数据, 就可以采用大的压缩单元,对于一些访问比较频繁的数据, 则采用较小的压缩单元。
另外, 目前流行的MPP分析数据库, 也都采用了一种类似Parquet 或者ORC的存储格式,来实现数据的行列混存。要点如下:行组(Row Group):按照行将数据物理上划分为多个单元,每一个行组包含一定的行数列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。不同的列块可能使用不同的算法进行压缩。
所以, 这样一来, 大家可以看出来,MPP的行列混存, 压缩的时候是和Oracle的HCC是一样的。
数据压缩带来的不止空间节省
每次谈到数据压缩, 大家都在看的是数据压缩后,节省了大量的空间, 这样同等大小的磁盘就可以存储更多的数据, 进而降低数据存储的成本。但是我们在实际生产中, 除了节省空间之外, 还会有另外一种收益, 就是海量数据查询时的性能提升。
这个乍一听, 感觉有点匪夷所思, 但是我们仔细分析之后,发现原理很简单。
来,我们举个例子,假设我们的查询需要扫描1T的数据, 一般情况下, 数据块从磁盘到内存的事件是最长的, 为了容易理解,我们假设需要10秒, 而SQL解析和结果生成的时间, 几乎可以忽略不计。
当数据压缩之后, 我们用比较普遍的压缩比来看, 假设数据压缩之后,压缩为原来的1/3, 这样以来,数据块就会减少到原来的1/3 , 自然数据块到内存的时间也缩短到原来的1/3,大约3秒多。
但是因为数据是压缩的, 需要增加一个解压缩的过程,不过这个过程是在内存里实现的, 所以解压缩时间一定用不了6秒, 最多不会超过2秒,剩下的过程和不压缩没什么区别。
这样一来, 我们看看, 压缩之后SQL执行时间大约5秒的时长, 对比一下,我们发现数据压缩之后,SQL执行反而会比未压缩更快。
不过,我们知道, 数据压缩会增加数据库的入库时间, 但是对于数据仓库的海量历史数据,一般都是一次入库, 多次读取, 与其每次查询都浪费大量时间, 不如在入库的时候一次性增加少量入库时间, 带来将来的高效率查询。
其他与数据压缩相关的场景
看完上面的内容,相信大家对于数据库的压缩有了一定的了解, 那么我们接下来看看其他的一些场景。
◆ OLTP与延迟压缩
我们都知道数据压缩在OLAP场景下, 一次性的入库损耗和多次的查询性能提升,是比较好做选择的。但是有时候难免会碰到一些罕见的场景, 比如某个应用系统, 每天入库的数据量巨大, 造成存储成本居高不下, 需要进行数据压缩的采用, 那么这个时候就必须考虑数据压缩了。
但是对于OLTP来说说, 入库效率是也是至关重要的指标, 这个时候怎么才能两全其美呢? 这里介绍两种方法, 大家可以参考。
1.数据库表分区,或者分表, 原则上, 当天的数据不压缩,两天后,一周内的数据轻量压缩, 一周以上一月之内的数据,可以考虑更高压缩比的压缩方法, 三个月以上的数据甚至可以归档离线。
在各个不同压缩策略之间, 采用自动脚本, 在业务闲时定期进行迁移。这就是一个简单的数据生命周期管理的思路。
2.OLTP压缩, 目前不少数据库都可以支持这种OLTP压缩,或者成为延时压缩, 原理是, 新入库的数据不压缩, 这样对入库效率完全没有影响, 当达到某个时间阀值,或者新数据量达到某个阀值的时候, 数据库后台会自动对新的数据进行异步压缩。
根据LRU原则, 最新的数据容易被频繁访问, 那么这些容易被频繁访问的数据就暂时不压缩,等到过一段时间, 这些数据的访问频率降下来之后,再进行压缩;这样一来,就可以即不影响入库效率, 又能够提升磁盘使用效率。
◆ 什么场景不能压缩
我们前面了解了数据库压缩的一些使用场景, 但是这里还是要给大家说一下, 什么场景下, 不能采用压缩, 如果采用了, 反而会造成空间膨胀和大幅度性能下降。
我们在生产中,经常会在表设计里存在这样的设计, 某个大的备注字段, 缺省为空, 后面会根据实际情况进行更新, 添加大量的描述性信息。
这种情况下,如果采用压缩,尤其是数据库压缩之后,出现大量的update来补充信息, 那么对数据库的压缩,就会带来灾难性的结果。
因为原来的数据块在压缩之后, 数据块内数据已经充满, NULL 会用符号代替, 但是当新的数据需要写入的时候,数据库就不得不把新数据写在一个新的数据块中, 同时在原来的位置,使用指针来指向更新后的数据, 这就造成了DBA 最不愿意看到的行迁移,会造成数据库性能的严重下降。
所以对于压缩数据, 最不希望看到的就是update大量数据, 如果真的需要, 请解压之后update, 完成之后再次压缩。
写在最后
数据仓库中的数据压缩,使用越来越广泛, 除了能够降低存储成本之外, 同时还可以在某些场景下大幅提高查询性能。
当然由于数据库的空间变小了,因此数据库的灾难恢复时间也会缩短,实属数据仓库中比较热门的技术。
随着越来越多的智能设备的接入, 企业数仓的数据规模越来越大, 已经从TB跨越PB直达EB级别, 数据压缩在未来会有越来越多的用武之地。
关于数据库压缩, 今天就谈这么多, 如果大家有什么想要讨论的, 请不吝赐教。
如果您对我们的源码感兴趣,欢迎到我们的 GitHub 代码仓库阅读查看,觉得不错记得点个 Star 哦~
StoneDB 代码仓库:https://github.com/stoneatom/stonedb
StoneDB 社区官网:https://stonedb.io/