如何写一个为SSD优化的数据库?

July 5th, 2011 no comment

SSD应该是近几年来硬件技术最nb的革命,困扰计算机性能的最短木板--磁盘IO终于有了巨大的突破。而且SSD在未来的几年中会快速普及(我还琢磨着给我笔记本换SSD)。

前一段cm同学也想做一个MVCC支持的k-v数据库 ,现有的数据库都是根据几年前的硬件水平(小内存、无NUMA、机械磁盘)进行的设计的,不可能针对SSD专门优化,SSD上的MySQL性能提升应该是只用了SSD IOPS超高这一个特性。在SSD未来几年应该能广泛普及的前提下,针对SSD的数据库优化是必须的。集中查了查资料,聊聊想法,有不对的地方欢迎各位补充。

先简要的介绍一下SSD的特点。SSD没有传统机械硬盘的机械寻道时间而带来的延迟,所以IOPS性能可以达到10, 000甚至是100, 000(而15K的SAS一般在100左右),所以能提供相对于机械硬盘100+倍的的小文件读取性能。而且在连续读取方面也能带来3倍左右的性能提升。

SSD最小的写入单元是4KB的一个page,写入空白位置时是按照4KB的单位写入。但是“改写”操作是需要进行一个额外的“擦除”操作,擦除则是按照block进行(一般来说一个block是128个pages即512KB)。也就是说向一个空白区域,那么直接按照4KB对齐写入;但是如果是向已经有数据的一个区域写入新数据,那么操作过程是:整个block读入缓存 -> 修改数据 -> 擦除原有block -> 写入整个block。这个过程常称作erase-before-write,显然SSD重写已有数据区域的代价非常高。

因为SSD的erase-before-write,所以对一个区域不停的“改写”操作不仅仅带来巨大的写入延迟,也会大大减少flash的寿命。所以设计了一套均衡算法来保证其内部的每个区域都被均衡使用,简称wear leveling。wear leveling是通过SSD内部的FTL实现(Flash Translation Layer,相当于磁盘控制器,主要功能是把flash的物理地址转换为OS需要的LBA地址)。具体的方式是:首先在SSD内部分为两个block pool,一个是free block pool,一个是data block pool。顾名思义一个是尚未使用的block集合和一个已经使用的block集合。由此很容易想到的一个优化策略是当要改写一定数据时,将数据合并为block,然后直接从free block pool里取出一个无数据的区域直接写入,原有的data block pool标记为 “可擦除”,同时新使用的block进入data block pool。“可擦除”的block定期执行擦除并放入free block pool。这其实就是最常用的deferred garbage collection策略,这样做的好处是:0,写入速度较快延迟小,延迟回收而非即时擦除;1,不会反复写入同一个flash物理区域,减少物理磨损。

新的SSD TRIM技术采用了另外的一种回收策略。OS直接告诉SSD控制器:一个block的数据可以立即delete。此时SSD做的操作是把整个block读入缓存,并重置所有数据,最后回写。同时把这个块儿加入free block pool中。这样做的好处首先是把数据的控制权从OS交给了SSD FTL,FTL可以做出若干优化策略执行此事;提前把将来要进行的GC操作提前完成,避免因为没有free block pool时临时执行GC带来的延时;同时由于这些block的数据迟早要擦除,所以对SSD的寿命无影响。

同时erase-before-write还带来了一个写入放大效应,最差的情况可能是只需要改1个page以内的数据,但是却需要对1整个block进行重写。虽说上面提到的wear leveling方法会减少写入放大发生的概率,但是一但发生free block pool耗尽,需要去即时擦除data block pool的数据时,那么wear leveling就会失效,基于此SSD盘在使用重保持一个相对大的空闲空间会使其性能变化相对较小。同时随机的小文件写入的性能在某种情况下会比连续大文件写入性能下降很多,因为随机的写带来随机的擦除,从而加大写放大效应发生的概率。

同时不要忘记SSD一般会有一个64MB甚至是128MB的memory cache的存在,所以在write-back使用条件下,猜测SSD自身应该有能力去合并多次小文件写入并入一个或几个block内,并结合wear leveling做出更优化的操作。

总结一下SSD的特点:

  1. SSD有写入磨损问题,寿命有一定的影响;
  2. 最小写入单位为4KB的page,擦除的时按block进行(通常为128pages);
  3. 重写数据时由于写放大效应的存在随机写入可能会比顺序写入带来的损耗更大;
  4. 带TRIM的wear leveling理论上能大大减少随机写入的写放大效应发生的概率;
  5. 使用上尽量保持一个相对大的空闲空间;
  6. 随机小文件读取性能强大,能达到1w甚至5w IOPS,基本是现在机械硬盘的100倍+;
  7. 连续读取性能是机械硬盘的3倍左右,能达到400MB/s的吞吐能力;
  8. 读性能的延迟远低于写(传统机械硬盘读写延迟差别不大);
  9. 在空白区域的随机写和顺序写性能可能差别不大。

下面来聊聊数据库的IO特点。数据库主要分成三大块儿:最终数据文件、索引、日志。最终数据文件的读取和写入在多数场景下是大量的随机读写;索引的建立、更新和读取是随机读写;日志文件是连续读写。

先说日志文件,为了迎合传统物理硬盘连续读写性能相对优秀的特性,日志文件是采用了顺序日志记录的方式,因为需要在事务提交的时候快速响应,顺序读写减少了磁盘重新寻道的时间。只需要在日志提交时把log buffer中的数据依次写入磁盘,所以传统的数据库日志最精确的描述应该是:在连续扇区的多次小文件写入。同时索引和数据文件的update操作的随机性导致了随机写入情况的发生概率很高。全表扫描和索引文件的创建毫无疑问是一个连续大文件读写的过程。

目前想到的针对SSD特点进行优化的数据库应该有以下几点:

  1. 修改日志的顺序写入方式,因为固定目录顺序日志文件的反复写入带来很大的SSD磨损,稳定是第一;
  2. 是否考虑根据SSD的特点采用新的日志方式(我还没想好)?
  3. SSD随机读性能卓越,可以在代码层不考虑cache,直接读取源数据文件,减少同步cache的开销;
  4. 以往数据库为了尽量减少小文件的随机读而采用的一些优化方式是否可以取消?
  5. 4KB page的对齐原则;
  6. 写入数据时尽可能按照一个block的大小(512KB)去组织数据;
  7. 索引和数据文件的update操作是否可以考虑在程序内部按照block进行重组织,改多次小文件写入为一个或几个block的连续写入;

上面的第7条可能是一个过渡设计。另一个杯具是以上这些都是根据文档的纸上谈兵,到现在我还没用过SSD 🙁 ,希望以后有机会也搞一个6w的FusionIO PCIE SSD玩玩。

最后感谢万能的wikipedia,没有它的帮助不可能这么快完成这些资料的查找,主要参考以下文章:

http://en.wikipedia.org/wiki/SSD

http://en.wikipedia.org/wiki/Wear_leveling

http://en.wikipedia.org/wiki/TRIM