最近一直在做Cassandra优化相关的工作,大的方面就是主要考虑如何提升Cassandra的读性能。我主要集中在两点上:
- 索引的优化
- Cassandra在多级存储介质的环境下的改进
这 两点改进目前都已经做完,这里我的师弟也做出了突出的贡献。但是,还有一点,是我除了以上两点以外思考比较多的:就是Compaction操作。现在的 NoSQL数据库必须要有Compaction操作。但是似乎研究界,工业界对于Compaction的关注没有那么多。也可能是这个问题比较简单,大家 不愿意关注。也可能这个问题想要得到好的结果与实际付出不相符合。不管怎样,我还想结合这些天的测试和自己的思考,和大家一起讨论以下的问题:
- Compaction究竟是什么
- 为什么需要Compaction
- 我们需要什么样的Compaction
- 选择适合自己项目的Compaction策略
Compaction究竟是什么如果要对Compaction进行翻译,我不知道那个词合适,合并(好像merge更合适)?但是Compaction不是merge。那Compaction究竟是什么呢?我无意去钻研字眼。Wiki上有一个解释,尽管不是数据库领域的,但是意思确实一样的:
In telecommunication, data compaction is the reduction of the number of data elements, bandwidth, cost, and time for the generation, transmission, and storage of data without loss of information by eliminating unnecessary redundancy, removing irrelevancy, or using special coding.
主要的宗旨就是在传输过程中节省开销。那对应到存储领域,就是要节省存储开销,节省读取开销的操作,后面我们会分析,为什么这样做就会达到节省了以上两项开销的目的。Compaction在实际中,有的是合并删除过期数据,也有的是整理数据,保证key的有序。 为什么需要Compaction这个和“Compaction究竟是什么”有些重复,不过我觉得还是要强调突出一下。在现在的一些存储系统中,如Cassandra,HBase等,为了保证写的速度,将随机写转化为顺序写,都在内存中采用了类似memtable的结构,达到一定大小之后,顺序flush到磁盘,通常叫做sstable。当系统不只是写,还有一定量更新,删除的话,会有重复数据,这些重复数据会造成两个后果:
- 会占用额外的空间,更新,删除越是频繁,额外的空间开销越大
- 同一份数据,在多个文件中,在读取的时候,会需要seek多次,降低读的性能
当数据规模达到海量,比如存储搜索引擎快照信息,采集信息等,单机可达2T,上面的缺点会越发明显。快照信息读要求比较低,主要是空间方面的考虑。采集信息存储对读的要求很高,这取决于每天的采集量,如果每天采集量在10亿规模,可以计算压力是非常大的。所以,如何改进或者规避上面两点呢?那就需要Compaction操作,当然,一个不合适的Compaction策略,会使得空间占用剧增,这个我后面会提到。 我们需要什么样的Compaction这里主要针对Cassandra的两种Compaction策略进行分析,进而得出关于我们需要的Compaction策略的一些构想。 Cassandra在1.0版本以前采用的Compaction策略是SizeTieredCompactionStrategy。基本原理很简单,就是积累几个小的sstable,合成一个大的sstable,默认的是四个。比如Tier0由四个sstable文件合并到Tier1的1个文件,依次类推。这样下去,当存储的数据量越来越大,单个sstable的文件也会越来越到,例如存储1T数据,最大的sstable文件竟可达到500G左右。这会有以下的问题:
- 做Compaction操作需要的额外空间会急剧增加,删除重复数据带来的空间节省,已经远不够Compaction操作的额外空间开销
- 当更新比较频繁的时候,会在同Tier多个sstable文件中,最恐怖的是,每个Tier中都有,会对读性能造成影响
第一条的影响毋庸质疑。对于第二条,无论是官方的数据,还是鼓吹LeveledCompaction策略的人,都认为会较严重的影响读性能。但实际上并非如此。因为这种Compaction策略产生的文件非常大,文件总数少。直接的结果就是seek的次数比较少。会少到什么程度呢?同样是1T的数据,当SizeTierdCompaction已经只有几十个文件的时候,LeveledCompaction策略的L0还是有几千个文件。如果这里面存在重复的数据的,如果假设为均匀分布,那前者的读性能会好很多(当然,重复数据不是均匀分布的,这和我们实际应用有很大的关系,所以在实际使用中,我们又有一个可以深入改进的方向)。还有一些数据指出,SizeTiredCompaction因为单个数据文件很大,给很多操作带来了不便,这个确实。500G的一个文件,Compaction出错了,就悲剧了。尽管这样的情况很少。 Cassandra1.0以后推出的Compaction策略是LeveledCompaction,是实现的LevelDB的Compaction策略。各种文章评论都将它说得很明白,也确实很厉害。它有几个特点:
- 文件大小一致(L0会有少许变化,主要看MAX_HEAP的大小设置)
- Compaction额外空间占用比较小
- 分层(level),同层sstable之间没有重复数据(这个想法比较牛)
- 高层是底层的数据量大小的10倍,这个保证了理想情况下,重复数据最多七分,但实际中,情况很不乐观(这个想法也不错)。
网上很多的文章,在讲到LeveledCompaction的时候,说读性能如何好,都是在理想情况下的,也就是L0有很少的文件的时候,Compaction接近完成的时候。那这个理想情况,是否能够达到呢?这与具体的应用非常相关。别的应用场景我没有接触,根据快照,以及我个人测试的经验,写入在7000-8000以上,value大小为1k的时候,LeveledCompaction策略就跟不上了(这只是一个例子,大概的意思就是写入很多的时候)。有的同学会说,将Compaction操作的带宽设置大一些,但是这样会更加影响读性能,不过如果是快照,你可以设置为最大。通常我的应用场景下,我的测试中,L0都是很多的,所以读很不乐观,比SizeTieredComaction还要低10%-20%,但是理想的情况要达到的时候,或者接近的时候,LeveledCompaction的读性能要比SizeTieredCompaction高出10%-20%。但是在我的测试中,这种情况很少。 我并没有详细介绍LeveledCompaction策略,我之前的博客有,同学也可以看别人的介绍。通过上面的分析,那我们想要的Compaciton操作是什么样的?
- 额外空间占用比较小
- 能够更快的将重复数据合并,过期数据删除
- 能够在需要的时候,进行Compaction。不需要的时候,不能够影响读写性能。
注意,上面的分析,都是在Cassandra基础之上完成的,难免有偏颇。 选择适合自己项目的Compaction策略通过上面的一些分析,我们在自己的项目中,如何设置CompactionStrategy呢?我们可以通过区分“通用情况”和“特殊情况”来讨论。 针对通用情况,我们讨论Cassandra的SizeTieredCompaction策略和LeveledCompaction策略如何选择,一些经验如下:
- 单从空间角度,数据量偏小,单机1T左右,可以使用SizeTiedCompaction策略
- 如果更新特别频繁,推荐SizeTiedCompaction
- 如果更新比较多,但是同一个key的更新间隔不是很短,可以使用LeveledCompactionStrategy
- 就要节省空间,就用LeveledCompactionStrategy
- 单机数据量大于1T,甚至更多,请使用LeveledCompactionStrategy,并且设置sstable大小可以为100m
那么特殊情况呢?我举一个例子,如,单个节点上的数据,一个月更新一次,也就是我们有一个月的时间来做Compaction,如果使用通用的策略的话,也可以,但是很耗时。比如1T数据,LeveledCompaction一遍,需要20天(Compation的时候,写放大造成实际读写量远大于1T),但是这个时间,我完全可以用来做别的,而不是浪费在Compaction操作上。那这个时候,我们就需要定制自己的Compaction策略。而且,更重要的是,明确的更新周期,可以让我们知道哪些数据会和哪些数据进行Compaction。这个带来的提升是非常巨大的。但是由于是针对特定应用的,也需要有相应的投入才行。 小结 这篇博客主要介绍了Compaction的一些内容,主要是我在优化测试过程中的一些经验,体会。写出来也是想和大家交流。也写了不少,后续会介绍一些我们自己改进的Compaction策略,自已写特定的场景,表现要优于SizeTieredCompaction策略以及LeveledComaction策略。
[引用]
- http://en.wikipedia.org/wiki/Data_compaction
- http://www.datastax.com/docs/1.0/configuration/storage_configuration