Cassandra源码阅读(4)-SSTableCompaction

本文Cassandra的Size Tiered Compaction Strategy 为例描述SSTable的Compact过程,主要考虑下面三个问题

  1. 为什么SSTable需要Compact
  2. 如何选取需要Compact的SSTable
  3. SSTable如何合并

为什么SSTable需要Compact

Compact的主要原因如下:

在Cassandra中所有的操作都是写操作,删除操作也只是写一个Tombstone记录,每一次写都是写到Memtable中,Memtable在一定条件下就会被Flush到磁盘上形成一个SSTable,同时内存里会新起一个Memtable。

如此往复,一张表就会有多个SSTable在磁盘上,且同一个Partition也会分布在不同的SSTable中,这时Compact一是可以释放磁盘空间,二是可以提高读的效率,因为需要读取的SSTable数量变少了。

Compact的同时可以去掉TombStone,Tombstone可能在delete的时候产生,同时也会在记录的TTL到期时还有插入null值时产生。

如何选取需要Compact的SSTable

下面以Cassnadra中的默认的SizeTieredCompactionStrategy为例,简单描述一下具体选取需要Compact的SSTable流程。
对于LeveledCompactionStrategy和TimeWindowCompactionStrategy的具体做法,见我的另一篇文章Cassandra源码阅读(5)-LevelCompactionStrategy与TimeWindowCompactionStrategy

Compaction是由cassandra启动的时候的定时任务触发的(ScheduledExecutors.optionalTasks.scheduleWithFixedDelay(ColumnFamilyStore.getBackgroundCompactionTaskSubmitter())),具体的入口代码是ColumnFamilyStore.enableAutoCompaction(),最后一路调用到AbstractCompactionStrategy.getNextBackgroundTask(int),由于这里用SizeTieredCompactionStrategy为例,所以我们看SizeTieredCompactionStrategy.getNextBackgroundTask(int)的具体实现。
该方法具体处理逻辑如下。

  1. 从CFS所属的所有SSTable中,选出不处于Compacting状态中的SSTable作为候选者。

  2. 按照大小把这些SSTable分桶,大小相近的放在一个桶内(具体算法在SizeTieredCompactionStrategy.getBuckets,简单说就是avg0.5-avg1.5的为一个区间(avg随着新加入桶的sstable进行更新)进行分桶)。

  3. 把分过桶的SSTable按照热度排序(一个桶就是一个List,对这个List逆序排序),SSTable的热度的定义是最近2小时的读取量除以key的数量。如果桶内SSTable数目有超过maxThreshold(默认32),就把末尾最不热的去掉,保持32个。如果桶内的SSTable数量小于4个,就不算这个桶。最后从所有桶中选取最热的桶(桶的热度就是把桶内SSTable的热度都加起来),这个桶内的SSTable就是要Compact的。

  4. 如果在3中没有选出任何的SSTable,那么就判断是否要对某个SSTable执行tombCompaction,这里通过worthDroppingTombstone来找,大致算法是tombstone比例大于0.2且与其他SSTable没有overlapping(partition没有重合的地方)

SSTable如何合并

选好了SSTable之后就要开始进行合并了,合并先从候选的SSTable中从小到大获取对应的partition,如果这个partition分布在多个SSTable里面,就要从多个SSTable里面读取,然后合成一个完整的partition。接着把这个partition写入到CompactionAwareWriter里面,这个writer封装了对应的底层文件操作以及写入SSTable的细节。

具体从sstable里面取partition的代码比较复杂,把sstable对应的scanner放到了一个binary heap里面去遍历,还有单个partition跨多个sstable时的reduce处理过程。这一块代码主要在CompactionTask.runMayThrow里面,且是一个公共代码,不同的CompactionStrategy主要影响的是选取候选SSTable的过程,选完之后的compact都是走的同一套代码。虽然这个代码很复杂,但是设计的非常模块化,有兴趣的同学可以看一下。