Compact过程:
为了解决SSTable的读取速度问题。Bigtable提出了compact策略。Compact操作是将一个或多个SSTable作为输入,输出一个compact完成的SSTable。Compact操作完成这样几件事情:
1.将重叠的行聚集到一张SSTable中,这样在读取一行的时候就不需要多次读取SSTable了。
2. 删除过期数据(如果有设置TTL)。
3. 删除标记为tombstone的数据。
4. 重建索引和辅助索引。
5. 将新的SSTable写入磁盘
6.将旧的SSTable标记为废弃,等待garbage collector回收。
Compact过程需要将多个sstable读入内存,然后在内存中进行compact,然后再将完成的sstable文件写入磁盘。Compact过程本身的代价是比较大的,但是完成compact操作以后,能够非常可观的提升读性能,克服了sstable这种数据结构本身读性能支持不好的缺点。
现在主要的compaction策略有两种,一种是size-tired compaction,另一种是Level compaction。
Tired-Compaction:
Tired Compaction和bigtable中使用的compaction策略类似,当有若干小的sstable出现的时候(cassandra中默认值为4个),就会对他们进行合并。
在更新密集型的操作中,这样的策略会带来下面的3个问题:
(1) 性能得不到一致性保障,一行数据依然可能跨越多张表,最坏情况可能在每张表里都有。
(2) 由于没有保证删除的数据要多久才能被清除,在删除密集型操作中,有大量空间可能被浪费。它是以同样大文件的数量文件数量决定的compact的时机的。这样的话compact的频率无法保证。
(3) 在合并过程中,旧的SSTable必须在新的SSTable写入完成后才能删除,因此在频繁的compaction过程中磁盘空间也是个问题。在最坏情况下,如果旧的sstable没有标记为删除的数据(新的写入的sstable和原来的sstable的数据是一样大的),那么临时磁盘空间可以到达被合并的SSTable所占用空间的两倍。Compact的成本会越来越高。
LevelDB(Compaction):
Google提出的LevelDB的持久化存储策略, 能够很好的解决上面的问题。下面详细介绍一下LevelDB. 每个数据库系统都是通过在底层文件系统中存储的文件来进行组织的。LevelDB中的文件有以下几种。
Log Files
LevelDB中的logfile比较类似于BigTable中的commitlog。它存储一系列的更新插入操作每个更新插入操作都被添加在当前logfile的末尾,当logfile的大小超过了预定义的阈值的时候(默认为1MB),它就会写入sorted table(即SSTable)。并且开辟一个新的logfile来存储新的操作。当前的logfile能够写入到sorted table中,是因为在内存中有一个数据结构来保存当前logfile的副本,这个数据结构就是memtable。Logfile的写入过程其实就是将其内存中备份的memtable写入磁盘,然后再删除logfile。这个内存中的副本将会在当即的读操作中使用,所以更新操作会立即生效。这种机制实际上是双缓冲,双日志的方式。
Sorted tables
Sorted Table其实就是SSTable。在levelDB中,sorted table是按照levels来组织的(不同的level是存在不同的磁盘区的)。
Manifest
Manifest文件中有组成每层的sorted table的列表,相应的键值范围,还有其他的一些元数据信息。每一次打开数据库的时候,都有一个新的Manifest被创建(新旧是通过在文件名上加一个整数来区分的),Manifest文件是log的格式,只要文件添加或者删除,就会添加到这个log文件中。
Current
Current是一个文本文件,它存放最近的Manifest文件名。
Info log
消息信息存放在LOG和LOG.old文件中。
LevelDB的写入操作:
当logfile增长到一定大小以后(默认1MB)以后:
(1) 创建一个新的memtable和logfile,将以后的更新操作重定向到新的logfile中(不影响写数据)。
(2) 将以前的memtable中的数据flush到sstable中。
(3) 删除老的logfile和释放memtable空间
(4) 将新的sstable放入level0
Compaction过程:
从logfile直接写入的sorted table被放在level0,当level0中的文件(sorted table,默认大小1MB)到达了一定的数量后(默认4个), 每个level0中的文件都和level1中的所有有重叠(包含有相同的rowkey)的文件生成一系列新的sorted table文件(LevelL L>=1中的单个文件大小被限制在2MB). 在level0中的文件,可能会有重叠(指除了timestamp以外,有相同的key),但是在其他层的文件之间是不会有重叠的(称之为互斥),这是由compact的过程决定的。
对于任意的levelL(L>=1), 如果该层所有文件的大小的总和超过了10^L MB, 将会一个levelL中的文件(选择的策略下面讲),将和所有Level(L+1)中的有重叠的sorted table进行compact, 生成一系列level(L+1)中的文件。这样的操作将会产生这样一种效果,就是用批量读写操作(input SSTable,output SSTable)将更新的数据从低level向高level转移。因此对于一条记录而言,这是一个逐渐下沉的过程。
从L层选择一个文件与L+1层中所有重叠的文件进行compact的时候,涉及到一个选择的策略问题。LevelDB采用的策略是这样的,由于进行compact的SSTable都是有序的,所以很容易找到一个SSTable的最后一行数据。将L层中上次进行compact的SSTable的最后一个rowkey记录下来,再此选择的时候,将会选择从上次rowkey以后开始的最近的那个SSTable文件进行compact(因为同层的文件之间是严格互斥的). 如果没有这个文件就在key空间上循环重新来找。
Level(L+1)中的整个文件将会作为compaction的输入,compaction完成以后,这个文件将会被删除。Compaction过程将选择的file的内容归并在一起,然后产生一系列L+1层文件。(当归并完后写入磁盘的时候,如果文件大小超过2MB,就转向下一个文件写。如果当前写的文件与L+2层的10个文件以上产生了重叠,同样转向下一个文件进行写,这样就保证了L+1层在与L+2层compact的时候,不会涉及过多的数据。)旧的文件将在归并完成后被删除。
Compaction的核心功能就是丢弃覆盖数据,删除标记为删除的数据,以及清除过期数据。具体的compact的过程如下:
LevelDB的读取操作:
它会从低level向高level进行查询,只要找到了第一个数据就返回。由于同层的sstable是互斥的,所以在同层中定位sstable很容易(由于互斥,所以方便在每层level上建立索引).所以除了读取读取sstable本身,其他所有的操作都是在内存中进行的。所以它可以将读取效率大幅度提高。但它牺牲了较大的写性能。
性能分析:
Level0中的compaction将会最多读取4个leve0中的文件(每个文件大小1MB),算上level的所有10MB文件,这次compact操作最坏的情况总共需要读14MB写14MB。
在其他层的compact操作中,我们是在L层选择一个2MB的文件,在L+1层中顶多有12个文件与之重叠,所以compact操作顶多会读26MB文件,写26MB的文件,如果以I/O的速度为100MB/S,那么一次compact操作将会消耗0.5s。如果我们限制后台写操作的速度,假设全速的10%,那么一次compact将会最多消耗5s,如果用户以10MB/s的速度写文件,那么在level0将会堆积大量的文件,这样在读取的时候同样需要归并大量的小文件。
Solution1: 增加log文件的阈值,使level0中的文件变大。尽管这样需要更大的内存来存放相应的memtable。
Solution2: 当level0中的文件个数在不断增加的时候,我们需要人为的控制写的速度。
文件个数:
系统默认的文件大小为2MB,我们可以将这个文件设置得更大一些,以减少总文件的个数,这样会增加一次compaction的代价。作为一种替代方案,我们可以将一些2MB的文件集存放在一个目录中,在ext3上的实验显示了在不同文件个数的目录中打开100k的文件需要的时间。
Files in directory | Microseconds to open a file |
1000 | 9 |
10000 | 10 |
100000 | 16 |
Cassandra中的Level Compaction:
Cassandra中的Level Compaction操作就是基于LevelDB的。不同的是,它根据上面提到的一些优化思想,做了一些修改。首先,在level0的sstable,它规定为固定的5MB(LevelDB中是1MB)。Cassandra中的level0并不实际存在,当有一个level0的sstable产生以后,它会立刻level1中的数据进行compact。其他规则基本相同。
Level Compaction可以保证90%的读取操作能够在一个SSTable查找完成。大约有10%的空间会被废弃的数据浪费掉。在compact过程中只有最多sstable 10倍的空间需要为compaction操作保留下来。
故障恢复:
(1) 读取CURRENT文件,找到当前提交的manifest文件。
(2) 读取这个Manifest文件。
(3) 清除旧文件。
(4) 将当前log中的数据写入sstable。
(5) 将新进来的写操作重定向到新的文件中。
文件的垃圾回收:
DeleteObsoleteFiles()方法在每次compaction操作的末尾和恢复操作的末尾被调用。它会去查找磁盘上所有文件的名字。它会删除所有非当前使用的log file和所有没有被引用的且不是正在执行compact的表文件。
http://blog.sina.com.cn/s/blog_aed82f6f0102wn77.html