SSD 背景知识
在应用程序针对 SSD 优化之前,我们首先需要对 SSD 的结构和特性有所了解。
- 单元、页和块。目前主流 SSD 都使用 NAND 闪存芯片,数据存储在单元(cell)中,根据每个存储单元可以保存的位数,分为 SLC(Single-Level Cell,单阶存储单元)、MLC(Multi-Level Cell,多阶存储单元)和 TLC(Triple-Level Cell,三阶储存单元)。每个单元的擦除次数是有限的,单元内可存储的位越多,制造成本越低,但是可擦除次数也越少。一组单元组成了页,页是读写的最小单元。典型的页大小是 4K,每次擦除后一页内的数据需要一次写入。因此对于 SSD 没有“覆写”操作。页被组合成块,典型的块大小有 512KB、1MB 等。
- IO 和垃圾收集。SSD 的 IO 操作有三种,分别是读、写和擦除。对于读和写,最小操作单元是页,而擦除的单位是块。由于擦除的速度非常慢(通常为毫秒级),SSD 控制芯片会执行垃圾回收操作,即回收使用过的块,确保后续写操作能够快速分配到可用的块。通常 SSD 会维护一个需要执行垃圾回收的阈值,以减少擦除过程中对应用程序实时写入的影响。
- 损耗均衡和写入放大。SSD 存储单元的擦除次数是有限的,这称为 PE 周期(program/erase cycles)。不同的存储单元 PE 周期不同,SLC 最高,可以达到 10 万次,而 TLC 最低,只有几千次。如果到达了 PE 周期,存储单元就会永久失效。为了防止一个块经常被擦除而提前失效,SSD 需要平衡每个块的擦除次数,该机制被称为“损耗均衡(wear leveling)”。通过该技术,存储数据会在不同的块之间移动,以避免对同一块的频繁擦除。由于损耗均衡策略,再加上 NAND 闪存芯片的擦除特性(擦除操作的最小单位是块),会导致实际 SSD 在擦除块之前,必须将需要保留的数据移动到新的块上,然后再进行整块擦除,这些操作都会大大增加 SSD 的实际写入数据,这被成为“写入放大(write amplification)”。如果应用程序针对这些特性进行了优化,可以提高 SSD 性能。
SSD 友好应用程序的优点
相比于普通应用程序,SSD 友好的应用程序有以下优点:
更高的性能
虽然直接升级到 SSD 已经可以大大提高应用程序的 qps(queries per second,每秒查询数),如果针对 SSD 特性进行优化之后,还可以继续提升 qps。
我们有一个应用程序,之前在使用 HDD 时,最高的 qps 为 142;更换成 SSD 之后,即使没有对应用程序进行优化,借助于 SSD 的高 IOPS,qps 也提升到了 20,000,提升了超过 140 倍。
当应用程序针对 SSD 进行了优化之后,最高性能提升到了 100,000qps,又提升了超过 4 倍。这里的优化,主要利用了 SSD 内部并行处理机制(下文会提到),通过多个并行线程处理 IO 提升应用程序 IO 性能。
图 1,线程和应用程序吞吐率关系
更高效的存储 IO
前文提到过,SSD 内部的 IO 最小单元是页(通常大小是 4KB),因此即使是读写一个字节数据,SSD 还是会操作整页数据。这也是写入放大的其中一个原因。如果应用程序非 SSD 友好,可能会大大增加写入放大因素。
更长的 SSD 寿命
SSD 的寿命通常由以下因素决定:SSD 大小、PE 周期大小、写入放大因子和应用程序写入速率。考虑到 SSD 成本,如果能够优化应用程序减少写入放大因子和写入速度,能够有效延长 SSD 寿命。
其他层面的 SSD 友好设计
在进行应用程序本身的优化之前,SSD 友好设计可以先从文件系统、数据库、数据存储设施层面开始考虑。
文件系统
文件系统直接和存储进行交互,文件系统的优化主要针对以下几个 SSD 特性:
- 随机访问的性能比肩顺序访问;
- 覆写需要块擦除;
- 内部损耗均衡,这会引起写入放大。
SSD 友好的文件系统有两类。一类是适配了 SSD 的通用文件系统,这些文件系统都通过支持 SSD 的 TRIM 指令来进行优化,包括 Ext4 、 Btrfs 。另一类是专门为 SSD 设计的文件系统,它们自己维护了日志结构以迎合 SSD 的“读取 - 擦除 - 写入”流程,例如 NVFS 、 JFFS/JFFS2 、 F2FS 。
数据库
传统数据库组件的设计,都充分考虑到了 HDD 特性。其中最受人关注的就是 HDD 的顺序读写性能远优于随机读写,因此数据库存储、查询优化等都会尽可能的利用该特性。
但是对于 SSD 来说,这些特性都可能不复存在,目前有两类专门针对 SSD 优化的数据库:
- 专门针对闪存芯片设计的数据库,例如 AreoSpike 。它跳过文件系统直接将 SSD 当成快设备操作,通过写时复制尽可能避免 SSD 的写入放大;
- 针对混合闪存硬盘(Hybrid flash-HDD),将其中的闪存空间作为缓存使用,以提高 IO。
数据存储设施
由于 HDD 的延迟,读取本地 HDD 上的数据延迟,可能会大于网络加上内存的延迟。基于这种情况,一些公司会使用例如 Memcached 、 Redis 等内存存储集群,作为数据存储或者集中式的缓存。
但是,如果使用了 SSD,情况就不同了。SSD 的 IO 延迟可以降低到微秒级别,且相比于 HDD 有更高的读写带宽。相比于使用内存作为存储,SSD 除了性能的提升,还可以大大降低成本和软件设计的复杂度。
SSD 友好的应用程序设计
在应用程序层面,我们同样可以针对 SSD 特性进行优化,在提高应用程序性能的同时,提高 SSD 的使用寿命。
这些优化主要分为三类:数据结构、IO 处理和多线程。
1. 数据结构:避免就地(in-place)更新优化
由于 HDD 在查找数据时又寻道时间,为了避免寻道产生的延迟,应用程序常常被优化成就地更新。图 1(左)展示了 HDD 进行就地更新和随机更新时的 qps 差别,可以发现对于 HDD 避免寻道时间,对 IO 的提升还是比较大的。
图 2,HDD 和 SSD 随机更新和就地更新 qps
反过来看图 1 右侧图,对于 SSD 情况却截然相反。正如前文提到的,SSD 的特性决定了它无法直接写入已经有数据的块,而是需要经过“读取 - 擦除 - 写入”的流程。这个流程既降低了数据写入速度,又导致了写入放大,最终导致了如图所示的 qps 下降。反之,对于随机写入,SSD 可以寻找一个直接可写的块并写入,避免了上述流程。
2. 数据结构:区分冷热数据
通常来说,应用程序在存储数据的时候,不会考虑数据访问和修改的频率。假设我们将冷热数据混合排布在同一个区块,对于 SSD 来说,如果要修改其中的一小块内容(小于 1 页),SSD 仍然会读取整页的数据。这样同样会降低 IO 带宽和导致写入放大。
因此,出于性能考虑,如果应用程序将 SSD 作为数据存储,应该将数据按照访问和修改频率划分。将不同热度的数据存放在不同位置,以提高 SSD 读写性能。
3. 数据结构:采用紧凑的数据结构
SSD 的读取以页为单位,再加上操作系统通常会采用预读取操作,应用程序所采用的数据结构尽可能的紧凑,能够减少SSD 的读取操作,同时也能更好地利用页缓存。
同样的,由于SSD 写入方式的特殊性,紧凑数据结构将关联数据放置到相邻区域,减少可能的垃圾回收的同时,还能够降低写入放大带来的问题。
4.IO 处理:避免长时间大数据写入
前文介绍过,SSD 内部有类似 JVM 的垃圾回收机制。SSD 会收集内部可回收区域,并且设置一个空闲块的阈值。当空闲块数量低于阈值的时候,SSD 会进行后台垃圾回收,以擦除可写区域。由于后台垃圾回收操作是异步的,因此它不会阻塞应用程序的 IO 操作。但如果此时写入 IO 频率高于后台垃圾回收的清理速度,SSD 会启动前台垃圾回收。前台垃圾回收操作会阻塞的清理应用程序即将写入的块,此时应用程序 IO 必须等待待写入块被擦除完毕后才能执行后续的写入操作。此时应用程序 IO 的延迟可能会达到毫秒级。
下图是针对此特性进行的写入延迟测试,整个测试通过控制不同写入速率的数据写入,持续 2 小时,监测写入的延迟。
图 3,高频数据写入导致的延迟
从左图我们可以看见,当写入速率达到 800MB/s 的时候,监测到的大于 50ms 延迟数量达到了 61 次。而右图可以看见,此速率下监测到的最大延迟达到了 92ms。
5.IO 处理:避免 SSD 使用空间过大
SSD 的使用空间会影响到 SSD 的写入放大和垃圾回收频率。在垃圾回收过程中,块中的有效数据需要被移动到空闲的块上。假设 SSD 使用空间为 A%,平均情况下,如果需要擦除一个块,需要压缩的块会有 1/(1-A%)。在实际情况下可能会更糟糕,下图展示了随着 SSD 使用率上升,需要被压缩的块和页的数量:
图 4,SSD 使用率对块和页的压缩数量
6. 线程:对于轻量 IO 使用多线程
SSD 有不同层面的内部并行机制:通道(channel)、包装(package)、芯片(chip)和平面(plane)。单个 IO 线程无法充分利用这些并行机制。SSD 能够通过内部的多通道机制,将多个 IO 线程分配到不同的通道并行进行 IO 操作,以提供尽可能高的 IO 性能。
这里的轻量 IO 是有多轻呢?通常的计算方式是 IO 数据量不超过内部的并行机制。例如,页大小为 4K,并行度(通道数)为 16 的 SSD,阈值在 64KB 左右。
7. 线程:对于重量级 IO 使用较少的线程
该原则和第 6 条并不相矛盾,当 IO 的读写数据量很大时,少量 IO 线程(如 1 到 2 个)已经占满了 SSD 的总 IO 带宽,此时如果继续增加 IO 线程数,反而会降低总的 IO 性能。因为过量的 IO 线程之间,会对 SSD 映射表等资源产生竞争,同时也会破坏操作系统提供的预读取等优化机制。在我们的测试示例中,当写入大小为 10MB 的时候,单线程可以达到 414MB/s 的写入速率,两个线程可以提升到 816MB/s 的总写入速率,但是当写入线程增加到 8 个时,总的写入速率却降低到了 500MB/s。
图 5,IO 大小和线程数量对吞吐率的影响
总结
应用程序使用 SSD 会比使用 HDD 有更好的 IO 性能。然而如果不进行应用程序优化,可能无法达到最优的性能。本文介绍的 SSD 友好的应用程序设计思路,可以帮助应用程序充分利用 SSD 的性能。
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论