用户一般会为备份数据设置一个retention time,过时的数据应该被回收再利用。数据去重复杂化了垃圾回收,因为每个数据块都可能被多个备份所引用。如何进行引用管理仍是很有挑战的问题。最近的一些论文讨论了这些问题,包括ATC’11的best paper,FAST’13,我想简单总结下它们的思想。
垃圾回收可以分为两阶段:第一阶段是标记可回收的数据块,称为标记阶段;第二阶段是回收数据块,称为回收阶段。回收阶段的设计与数据组织格式有关,比如大多数去重系统使用container组织数据,它们就需要一个合并稀疏容器的操作来回收空间。关于回收阶段的讨论目前还不多。现在的相关论文讨论的问题实际上都属于第一阶段,如何能高效地判断一个数据块不再被任何备份引用?
目前的这些工作可以分为两大类,包括在线(MSST’10和ATC’11)和离线(FAST’13)两种。离线方式指的是所有垃圾回收的操作都在系统空闲时(或者专门安排停机维护)进行;在线方式则会在系统运行时(备份过程中)就收集元数据,减轻垃圾回收的负担。
在指纹索引中增加一列“引用计数”。当一次备份引用了某个数据块,就将其引用计数增1;当一次备份被删除,所有被其引用的数据块的引用计数减1。当计数为0时,表明该数据块可以被回收。在垃圾回收时,直接查询引用计数为0数据块,回收。
这种方法的优点是非常简单,容易理解。然而,它增加了索引的开销;因为索引无法放在内存中,每次引用一个数据块都要更新磁盘上的引用计数,如果可以按顺序批量处理就还不成问题,但有些索引(比如DDFS)的访问是完全随机的;可靠性差,一旦出现错误(比如硬件错误),很难发现;出错后难以恢复,恢复计数需要遍历系统中所有的备份元数据(recipe),代价昂贵。
扫描标记法是完全离线的。垃圾回收时扫描所有recipe,记录下所有出现过的指纹,而那些只存在于指纹索引中的指纹(数据块)是可以被回收的。这种方法需要扫描一遍recipes,一遍指纹索引。
首先扫描recipe是非常耗时的,一个压缩率20倍的1PB存储系统(块长8KB,指纹20B),recipe大约可以达到50TB。其次,标记几乎需要将整个索引放到内存中,这几乎是不可能的。一些分区的索引方案(比如Extreme Binning)可以缓解内存问题,不过期间要多次读取索引分区,性能受损。
扫描标记法消耗太多内存,可以用bloom filter减少内存开销。bloom filter是一个bit array,并需要准备n个不同的哈希函数。插入一个指纹时,用n个哈希函数计算出n个位置,并将bit array上对应bit标记为1;查询指纹时,计算出n个位置,若n个位置不全为1,说明指纹不存在,若全为1,则很可能存在。构造bloom filter需要扫描所有recipe,然而遍历指纹索引找出哪些指纹不在bloom filter中。
bloom filter存在false positive(全为1,但是并不存在)的问题。为了保证98%以上的准确性,每个指纹大约需要1个字节,内存开销是方法2的1/20。false positive会造成少量的无用数据块不能被回收,在大多数场景下不是问题。FAST’13提出的场景,sanitization,则不允许有false positive。
为了避免false positive,可以使用bit vector技术,每个bit代表一个数据块,而数据块的索引由其所属container的id和内部偏移决定。因此bit vector等于是用位图法标记存储系统所有数据块的使用情况。构造bit vector需要遍历所有recipe(可能还要读取container的元数据区确定偏移);然后遍历指纹索引时,找出没有标记的指纹。
bit vector的内存开销比bloom filter低,无false positive。但是它需要获知每个数据块在container内部的偏移,要么增加recipe和指纹索引的存储开销,要么增加构造bit vector的时间(临时读取container的元数据区)。
在垃圾回收时,可以将存储系统中的指纹集看成静态集合。当我们知道集合的所有n个元素时,我们可以构造一个哈希函数,将所有元素映射为0~n-1之间的整数,并不产生冲突。这个函数就是完美哈希函数。这个算法的关键是如何在遍历recipe时构造完美哈希函数。有了哈希函数后,只需遍历指纹索引,找出不在哈希表中的指纹进行回收即可。
这种方法的内存开销比bit vector稍高,并且无需知晓偏移信息。
Guo等人把引用计数法和扫描标记法喷了一遍后,提出了GMS算法,这是扫描标记法的改良。为了方便垃圾回收,GMS在备份时就开始维护一些数据结构。具体地,每次备份都会产生很多bitmaps,每个bitmap对应一个container,bitmap标记了该次备份引用了container内的哪些数据块。每个container也就会有多个bitmaps,代表了多个备份对其中数据块的引用情况,合并这些bitmaps,就可以知道这个container内部有哪些块是无用的。
GMS是在线的。它避免了大规模的扫描操作,合并bitmaps是简单高效的;当某个bitmap损坏了,可以通过扫描对应的备份进行恢复。然而bitmaps会带来不菲的存储开销。对于压缩率为20倍的存储系统,每个数据块大约需要2.5B(20/8),因此1PB存储系统至少需要320GB。如果考虑碎片的问题,开销会翻倍。
方法2~5是离线垃圾回收,它们都需要遍历recipe产生特定数据结构,然后遍历指纹索引确定哪些指纹可以被回收。它们的垃圾回收时间较长,期间不能进行备份等操作。
方法1和6是在线垃圾回收,它们都在备份时就维护了数据结构,因此垃圾回收时无需扫描recipe。但是需要额外的存储开销。
参考文献
[1] Wei et al. MAD2: A scalable high-throughput exact deduplication approach for network backup services. MSST’2010.
[2] Guo et al. Building a high-performance deduplication system. USENIX ATC’2011.
[3] Botelho et al. Memory Efficient Sanitization of a Deduplicated Storage System. FAST’2013.