=================================
1. 介绍
===========
Flashcache是linux内核模块的write back缓存。这个文档描述了flashcache的设计、未来的idea、配置和调优,以及flashcache提供的可覆盖测试钩子和我们所做的测试。Flashcache目前主要作为InnoDB的块缓存,但是其是通用设计可以用于其他的应用。
========================================
2. Flashcache设计
==========
flashcache使用了Linux Device Mapper(DM),是Linux 存储栈架构中的一部分,在创建SW-raid和其他组件的时候经常会涉及到,例如,LVM使用的就是DM。
cache的结构使用了一系列的哈希,cache划分成多个固定大小的sets(buckets),在set内部使用线性探测照抄blocks。这一系列哈希使用的好处很多(后面的部分将会介绍)并且能够很好的发挥使用。
block size,set size和cache size都是可以配置的参数,在cache创建需要。默认的设置:set size为 512(个block),几乎没有什么理由需要修改这个值。(思:意思是推荐使用512)
在以下的内容中,dbn意思是"disk block number",在扇区内的逻辑设备块号。
计算一个给定的dbn所属于的target set的方法:
target set=(dbn/block size/set size)mod(number of sets)
确认了集合后,在集合内部线性查找block。一个顺寻范围的disk block全部映射在一个指定的集合内。
DM layer在IO发送到cache layer之前将所有的IO操作转化为对block的操作(思:即对其转换等操作)。默认地,flashcache缓存所有的blosksize IO,但是可以配置为之缓存随机IO忽略顺序IO。
Cache set内部的替换策略是FIFO或者LRU。默认是FIFO但是策略可以在运行期间任何一个点进行切换同时sysctl(see the configuration and tuning section)。
处理缓存的读操作,计算目标集合(根据dbn)现行查找到block。当缓存命中时,就从flash中奖数据读取出来;若未命中,数据则从磁盘中读取数据,将数据写入到flash中,然后返回数据。
由于flashcache采用的写回策略,写操作仅仅写入到flash中,同步更新cache的metadata(标记cache block是脏块),则完成写操作,当block再次被写入事,metadata就不用再更新了。(因为已经标记为脏块了)。
必须注意的是,in the first cut,cache写入不是原子的,例如会有"Torn Page Problem"存在。一旦遇上断电或者写错误,那么有可能部分块以纪念馆写入成功。我们已经有了idea去fix这个问题,并提供原子的cache写入操作(看the Futures section)。
对于每一个缓存块都有一个on-flash metadata为了cache的持久化(是这个意思么?
Each cache block has on-flash metadata associated with it for cache
persistence.),每个block的metadata包含了dbn和flags(disk block cached in this slot)and flags(Dirty,VALID,INVALID).
cache metadata只有在写操作或者一个cache block被清除的时候需要更新。执行写操作时,将flag标记为dirty;被清除时标记为~dirty。为了最小化flash写操作,cache block metadata在读的时候不需要更新。
另外,我们还有一个on-flash cache superblock,包含cache的参数(在cache重启时读取),也包含cache关闭是否是clean的(顺序关闭或者节点宕机,断电等等)。
当cache是clean shutdown时,所有cache blocks的metadat都会被写入到flash中。在顺序关机以后,VALID和DIRTY的块已经持久化,那么当cache重启时,会加载VALID和DIRTY的块。当节点宕机或者断电时,只有dirty cache blocks会重启。节点宕机或者断电不会导致数据丢失,但是有可能导致cache丢失valid和non-dirty的cached blocks(为什么?)。
cache 的metadata在可能的时候再批量进行更新。所以如果我们有多个待更新的metadata位于同一个metadata sector中,可以集中一次更新,当一个文件顺序写入时,我们通常将多个metadata一起更新。
脏的缓存块会在后台写入到disk中。flashcache的延迟写入是由可配置的参数dirty threshold控制的(请查看configuration and tunings section)。Flashcache维持每个set中的dirty block的百分比在dirty threshold之下,当set中脏块百分比超过了脏块阈值之后,set将被清理(即写入到disk中并把metadata清空)。
DIRTY blocks的清理选择基于置换策略(FIFO Vs LRU)。当我们选择一个目标set清理时,对这些block进行排序,将块进行合并成大的IO,写入到disk中。
之前提到过,在IO操作发送到flashcache之前,DM将IO分裂为blocksize的块操作。对于小IO或者一个IO操作跨两个cache blocks,直接将IO发送到disk中。但是在这之前,我们将cacheblocks中对应这些io的都标记为invalidate。如果覆盖的cacheblock为DIRTY,将数据写入到cacheblock中,并clean。将IO所在的cacheblocks置为invalidate非常容易,使用set相关的哈希算法计算io所在set。
Flashcache支持块校验,在cache上计算,并且在每次cache读取时有效。块校验是可以编译选择的,由于“Torn page”的问题,默认是关闭的。如果cache写失败,有的block已经成功提交到flash中,那么block checksum出现错误的block以及之后的所有的block写入都认为是失败的。
那么cache metadata的开销有多大呢?每个cache block有24字节的内存状态(64位架构)和16字节on-flash metadata state。对于一个300GB的cache有16KB的blocks,有将近20 Million(300GB/16KB~20,000,000) cacheblocks,内存元数据消耗是480MB(20,000,000*24B = 480MB),如果我们配置300GB的cache使用4KB的block时,内存消耗为1.8GB。
允许进程标记为不cache不可用的,只要通过flashcache ioctl,如果进程是顺序的便利一个大的table(例如备份操作),可以标记为不是用cache的。当不使用cache的进程执行读操作时,命中率cache时,则从cache中读取,未命中时,则直接从硬盘中读取,但是并不加载至cache中;当执行写操作时,命中cache时,则将cache中的标记为invalidate(如果需要的话,首先将数据clean),然后直接写入到磁盘中去。
A few things to note about tagging pids non-cacheable. First, this only really works reliably with Direct IO. For buffered IO, writes will almost always happen from kernel threads (eg pdflush). So writes will continue to be cached. For most filesystems, these ioctls will make buffered reads uncached - readaheads will be kicked off the filemap code, so the readaheads will be kicked off from the same context as the reads.
如果一个进程标记他为不是用缓存,当进程退出时flashcache没有办法执行clean操作,(因为Linuxkernel没有at_exit()钩子)。因此,应用程序必须解决这个问题(见下文配置)。关于清理问题的解决方法是,使用缓存控制flashcache伪文件系统,在一个进程退出时最后一个关闭的fd负责清理工作。(see Futures for more details)。
尽管目前对于进程标记为不使用cache存在这些限制,我们仍然认为进程使用直接IO是有价值的,尤其是对于备份这种类型的操作。
另外一种不使用缓存的情况时,用户自己可以使用系统调用‘skip_seq_thresh_kb',自主决定接下来的顺序IO不使用缓存,通过一个可配置的阈值进行判断,什么时候的请求时顺序的,如果是顺序的则不执行缓存操作。判断是否是顺序IO的算法应该是能够区分不同的IO流的,例如,两个IO流是顺序的读写,但是第三个IO流是随机的需要使用缓存。在这里值得注意的一点是,可能存在这样的情况,多个小文件顺序写入到连续的块中,flashcache是无法判断这种情况的,因为flashcache是在块级别的而不是文件级别,因此这种情况下,将仍会被作为顺序读写,跳过缓存进行读写处理。
(更多的管理caching controls的细节,可以查看SA Guide手册)
=============================
Futures and Features:
==============
Cache Mirroring
------------------------
将两个物理的flash设备镜像,但是不需要更改任何代码。因为cache设备是块设备,我们能够在块设备中构建RAID-1,即可以在两个物理flash设备上构建然后使用我们的cache device。(这里并没有进行过测试)
Cache Resizing:
-------------------
最简单的扩充cache的方法就是离线的方法,在线进行cache扩展的方法是非常复杂而且容易出错的
Intergartion With ATA TRIM Command:
--------------------------------------------
The ATA TRIM命令作为以文件系统的方式访问SSD设备。Flashcache可以利用这一特点。
The ATA TRIM command was introduced as a way for the filesystem to
inform the ssd that certain blocks were no longer in use to faciliate
improved wear levelling algorithms in the ssd controller. Flashcache
can leverage this as well. We can simply discard all blocks falling
within a TRIM block range from the cache regardless of the state,
since they are no longer needed.
Deeper integration with filesystems:
---------------------------------------
将flashcache与文件系统进行更深一步的结合,使得文件系统能够轻松的标记IO是否使用缓存。
Fixing the "Torn Page Problem"(make Cache Write Atomic):
------------------------------------------------------------------
之前提到的,cache block的写入不是原子的写入,所以可能引起“Torn Page Problem”,即一部分写完成造成的不一致状态。当发生突然断电或者宕机时,可能产生这种写错误。
我们解决的思想是使用shadow paging techniques实现原子cache block写入。Mark C说我们能够通过原子cache block写避免doublebuffer writes。
We have ideas on how to fix this and achieve atomic cache block writes
using shadow paging techniques. Mark C. says that we could avoid
doublebuffer writes if we have atomic cache block writes. However,
with flashcache, doublebuffer writes will all be absorbed by the flash
(and we should get excellent write hits/overwrites for doublebuffer
blocks so they would never hit disk). So it is not clear how much of a
win atomic cache block writes will be.
It is however a handy feature to provide. If we have atomic block
writes we could also enable cache block checksums.
There are broadly 3 ways to fix this.
1) If the flash device offers configurable sector sizes, configure it
to match the cache block size (FusionIO offers upto a 4KB configurable
sector size).
2) If we choose a shadow page that falls in the same metadata sector
as the page being overwritten, we can do the shadow page write and
switch the metadata atomically.
3) If we don't want the restriction that the shadow page and the page
overwritten are part of the same metadata sector, to allow us to pick
a shadow page more freely across the cache set, we would need to
introduce a monotonically increasing timestamp per write in the cache
metadata that will allow us to disambiguate dirty blocks in the event
of a crash.
Breaking up the cache spinlock :
------------------------------
所有的cache状态都是由单独的自旋锁进行保护,所以当前的CPU利用非常低,目前没有精力用于自旋锁部分,将来可能会更改。
Make non-cacheability more robust(更强大的非缓存能力) :
---------------------------------
当进程死亡时,需要定期的清理非缓存。可能最好的解决方法是采用伪文件系统的方式。
The non-cacheability aspect need fixing in terms of cleanup when a
process dies. Probably the best way to approach this is to approach
this in a pseudo filesystemish way.
Several other implementation TODOs/Futures are documented in the code.