<audio id="audio" title="大咖助场2｜庄振运：与程序员相关的SSD性能知识" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/48/037884390233cb1cc6b8bbaa3f348b48.mp3"></audio>

你好，我是庄振运。我是[《性能工程高手课》](https://time.geekbang.org/column/intro/100041101)的专栏作者，很荣幸受邀来到陶辉老师的专栏做一期分享。今天我们来讲一点SSD相关的性能知识。SSD（Solid State Drive）是硬盘的一种，有时候也叫Flash或者固态硬盘。

最近几年，SSD的发展和演化非常迅速。随着市场规模的增大和技术的进步，SSD的价格也大幅度降低了。在很多实时的后台系统中，SSD几乎已经成了标准配置了。所以了解它的机制和性能，对你的工作会很有益处的。

相对于传统硬盘HDD（Hard Disk Drive），SSD有完全不同的内部工作原理和全新的特性。有些机制不太容易理解，而且根据你工作的领域，需要理解的深度也不一样。所以，我把这节课的内容按照由浅入深的原则分成了三个层次。

第一个层次是关注SSD的外部性能指标；第二个层次是了解它的内部工作机制；第三个层次是设计对SSD友好的应用程序。

## 比HDD更快的硬盘

很多人对传统硬盘了解较多，毕竟这种硬盘在业界用了好几十年了，很多教科书里面都讲述过。所以，对SSD的性能，我先用对比的方式带你看看它们的外部性能指标和特性。

一个硬盘的性能最主要体现在这三个指标：IOPS，带宽/吞吐率和访问延迟。**IOPS** (Input/Output Per Second) ，即每秒钟系统能处理的读写请求数量。**访问延迟**，指的是从发起IO请求到存储系统把IO处理完成的时间间隔。**吞吐率**（Throughput）或者带宽（Bandwidth），衡量的是实际数据传输速率。

对于传统硬盘我们应该比较熟悉它的内部是如何操作的，简单来说，当应用程序发出硬盘IO请求后，这个请求会进入硬盘的IO队列。当轮到这个IO来存取数据时，磁头需要机械运动到数据存放的位置，这就需要磁头寻址到相应的磁道和旋转到相应的扇区，然后才是数据的传输。对于一块普通硬盘而言，随机IO读写延迟就是8毫秒左右，IO带宽大约每秒100MB，而随机IOPS一般是100左右。

SSD的种类很多，按照技术来说有单层和多层。按照质量和性能来分，有企业级和普通级。根据安装的接口和协议来分，有SAS, SATA, PCIe和NVMe等。

我用一张表格来对比一下HDD和SSD的三大性能指标的差异。这里考虑比较流行的NVMe协议的SSD。你可以看到，SSD的随机IO延迟比传统硬盘快百倍以上，一般在微妙级别；IO带宽也高很多倍，可以达到每秒几个GB；随机IOPS更是快了上千倍，可以达到几十万。

<img src="https://static001.geekbang.org/resource/image/7c/04/7c0d537e9334e1d622d40f28168e1c04.jpg" alt="">

## SSD的性能特性和机制

SSD的内部工作方式和HDD大相径庭，我们先了解几个概念。

**单元（Cell）**、**页面（Page）**、**块（Block）**。当今的主流SSD是基于NAND的，它将数字位存储在单元中。每个SSD单元可以存储一位或多位。对单元的每次擦除都会降低单元的寿命，所以单元只能承受一定数量的擦除。单元存储的位数越多，制造成本就越少，SSD的容量也就越大，但是耐久性（擦除次数）也会降低。

一个页面包括很多单元，典型的页面大小是4KB，页面也是要读写的最小存储单元。SSD上没有“重写”操作，不像HDD可以直接对任何字节重写覆盖。一个页面一旦写入内容后就不能进行部分重写，必须和其它相邻页面一起被整体擦除重置。

多个页面组合成块。一个块的典型大小为512KB或1MB，也就是大约128或256页。块是擦除的基本单位，每次擦除都是整个块内的所有页面都被重置。

了解完以上几个基础概念，我们重点看看**IO和垃圾回收(Garbage Collection)** 。对SSD的IO共有三种类型：读取、写入和擦除。读取和写入以页为单位。IO写入的延迟具体取决于磁盘的历史状态，因为如果SSD已经存储了许多数据，那么对页的写入就经常需要移动已有的数据。一般的读写延迟都很低，在微秒级别，远远低于HDD。擦除是以块为单位的。擦除速度相对很慢，通常为几毫秒。所以对同步的IO，发出IO的应用程序可能会因为块的擦除，而经历很大的写入延迟。为了尽量地减少这样的场景，保持空闲块的阈值对于快速的写响应是很有必要的。SSD的垃圾回收（GC）的目的就在于此。GC可以回收用过的块，这样可以确保以后的页写入可以快速分配到一个全新的页。

<img src="https://static001.geekbang.org/resource/image/4b/70/4b3705248ccb0144516a77849c17dd70.png" alt="">

**写入放大（Write Amplification, or WA)。** 这是SSD相对于HDD的一个缺点，即实际写入SSD的物理数据量，有可能是应用层写入数据量的多倍。一方面，页级别的写入需要移动已有的数据来腾空页面。另一方面，GC的操作也会移动用户数据来进行块级别的擦除。所以对SSD真正的写操作的数据可能比实际写的数据量大，这就是写入放大。一块SSD只能进行有限的擦除次数，也称为编程/擦除（P/E）周期，所以写入放大效用会缩短SSD的寿命。

**耗损平衡(Wear Leveling) 。**对每一个块而言，一旦达到最大数量，该块就会死亡。对于SLC块，P/E周期的典型数目是十万次；对于MLC块，P/E周期的数目是一万；而对于TLC块，则可能是几千。为了确保SSD的容量和性能，我们需要在擦除次数上保持平衡，SSD控制器具有这种“耗损平衡”机制可以实现这一目标。在损耗平衡期间，数据在各个块之间移动，以实现均衡的损耗，这种机制也会对前面讲的写入放大推波助澜。

## 设计对SSD友好的程序

SSD的IO性能相对于HDD来说，IOPS和访问延迟提升了上千倍，吞吐率也是几十倍，但是SSD的缺点也很明显，有三个：贵、容量小、易损耗。随着技术的发展，这三个缺点近几年在弱化。

现在越来越多的系统采用SSD来减轻应用程序的IO性能瓶颈。许多部署的结果显示，与HDD相比，SSD带来了极大的应用程序的性能提升。但是，在大多数部署方案中，SSD仅被视为“更快的HDD”，SSD的潜力并未得到充分利用。尽管使用SSD作为存储时应用程序可以获得更好的性能，但是这些收益主要归因于SSD提供的更高的IOPS和带宽。

进一步讲，如果应用程序的设计充分考虑了SSD的内部机制，从而设计为对SSD友好，则可以更大程度地优化SSD，从而进一步提高应用程序性能，也可以延长SSD的寿命而降低运用成本。接下来我们就看看如何在应用程序层进行一系列SSD友好的设计更改。

### 为什么要设计SSD友好的软件和应用程序？

SSD友好的程序可以获得三种好处：

- 提升应用程序性能；
- 提高SSD的 IO效率；
- 延长SSD的寿命。

我分别说明一下。

**更好的应用程序性能。**尽管从HDD迁移到SSD通常意味着更好的应用程序性能，这主要是得益于SSD的IO性能更好，但在不更改应用程序设计的情况下简单地采用SSD可能无法获得最佳性能。我们曾经有一个应用程序就是如此。该应用程序需要不断写入文件以保存数据，主要性能瓶颈就是硬盘IO。使用HDD时，最大应用程序吞吐量为每秒142个查询（QPS）。无论对应用程序设计进行各种更改还是调优，这都是可以获得的最好性能。

当迁移到具有相同应用程序的SSD时，吞吐量提高到2万QPS，速度提高了140倍。这主要来自SSD提供的更高IOPS。在对应用程序设计进行进一步优化使其对SSD友好之后，吞吐量提高到10万QPS，与原来的简单设计相比，提高了4倍。

这其中的秘密就是使用多个并发线程来执行IO，这就利用了SSD的内部并行性。记住，多个IO线程对HDD毫无益处，因为HDD只有一个磁头。

**更高效的存储IO。** SSD上的最小内部IO单元是一页，比如4KB大小。因此对SSD的单字节读/写必须在页面级进行。应用程序对SSD的写操作可能会导致对SSD上的物理写操作变大，这就是“写放大（WA）”。因为有这个特性，如果应用程序的数据结构或IO对SSD不友好，就会让写放大效果无谓的更大，导致SSD的IO不能被充分利用。

**更长的使用寿命。**SSD会磨损，因为每个存储单元只能维持有限数量的写入擦除周期。实际上，SSD的寿命取决于四个因素：SSD大小、最大擦除周期数、写入放大系数和应用程序写入速率。例如，假设有一个1TB大小的SSD，一个写入速度为100MB每秒的应用程序和一个擦除周期数为1万的SSD。当写入放大倍数为4时，SSD仅可持续10个月。具有3千个擦除周期和写入放大系数为10的SSD只能使用一个月。鉴于SSD相对较高的成本，我们很希望这些应用程序对SSD友好，从而延长SSD的使用寿命。

### SSD友好的设计原则

在设计程序的时候，我们可以把程序设计成对SSD友好，以获得前面提到的三种好处。那有哪些对SSD友好的程序设计呢？我这里总结了四个原则，大体上分为两类：数据结构和IO处理。

**1.数据结构：避免就地更新的优化**

传统HDD的寻址延迟很大，因此，使用HDD的应用程序通常经过优化，以执行不需要寻址的就地更新（比如只在一个文件后面写入）。如下图所示，执行随机更新时，吞吐量一般只能达到约170QPS；而对于同一个HDD，就地更新可以达到280QPS，远高于随机更新（如下左图所示）。

<img src="https://static001.geekbang.org/resource/image/42/fb/42339220b197186548330ayy0b08d9fb.jpg" alt="">

在设计与SSD配合使用的应用程序时，这些考虑就不再有效了。对SSD而言，随机读写和顺序读写性能类似，就地更新不会获得任何IOPS优势。此外，就地更新实际上会导致SSD性能下降。原因是包含数据的SSD页面无法直接重写，因此在更新存储的数据时，必须先将相应的SSD页面读入SSD缓冲区，然后将数据写入干净页面。 SSD中的“读取-修改-写入”过程与HDD上的直接“仅写入”行为形成鲜明对比。相比之下，SSD上的随机更新就不会引起读取和修改步骤（即仅仅“写入”），因此速度更快。使用SSD，以上相同的应用程序可以通过随机更新或就地更新来达到大约2万QPS（如上右图所示）。

**2.数据结构：将热数据与冷数据分开**

对于几乎所有处理存储的应用程序，磁盘上存储的数据的访问概率均不相同。我们考虑这样一个需要跟踪用户活动的社交网络应用程序，对于用户数据存储，简单的解决方案是基于用户属性（例如注册时间）将所有用户压缩在同一位置（例如某个SSD上的文件），以后需要更新热门用户的活动时，SSD需要在页面级别进行访问（即读取/修改/写入）。因此，如果用户的数据大小小于一页，则附近的用户数据也将一起访问。如果应用程序其实并不需要附近用户的数据，则额外的数据不仅会浪费IO带宽，而且会不必要地磨损SSD。

为了缓解这种性能问题，在将SSD用作存储设备时，应将热数据与冷数据分开。以不同级别或不同方式来进行分隔，例如，存到不同的文件，文件的不同部分或不同的表。

**3. IO处理：避免长而繁重的写入**

SSD通常具有GC机制，不断地回收存储块以供以后使用。 GC可以后台或前台方式工作。

SSD控制器通常保持一个空闲块的阈值。每当可用块数下降到阈值以下时，后台GC就会启动。由于后台GC是异步发生的（即非阻塞），因此它不会影响应用程序的IO延迟，但是，如果块的请求速率超过了GC速率，并且后台GC无法跟上，则将触发前台GC。

在前台GC期间，必须即时擦除（即阻塞）每个块以供应用程序使用，这时发出写操作的应用程序所经历的写延迟会受到影响。具体来说，释放块的前台GC操作可能会花费数毫秒以上的时间，从而导致较大的应用程序IO延迟。出于这个原因，最好避免进行长时间的大量写入操作，以免永远不使用前台GC。

**4. IO处理：避免SSD存储太满**

SSD磁盘存储太满会影响写入放大系数和GC导致的写入性能。在GC期间，需要擦除块以创建空闲块。擦除块前需要移动并保留有效数据才能获得空闲块。有时为了获得一个空闲块，我们需要压缩好几个存储块。每个空闲块的生产需要压缩的块数取决于磁盘的空间使用率。

假设磁盘满百分比平均为A％，要释放一块，则需要压缩1 /（1-A％）块。显然，SSD的空间使用率越高，将需要移动更多的块以释放一个块，这将占用更多的资源并导致更长的IO等待时间。例如，如果A=80％，则大约移动五个数据块以释放一个块；当A=95％时，将移动约20个块。

## 总结

各种存储系统的基础是传统硬盘或者固态硬盘，固态硬盘SSD的IO性能比传统硬盘高很多。如果系统对IOPS或者延迟要求很高，一般都采用SSD。

现在已经有很多专门针对SSD来设计的文件系统、数据库系统和数据基础架构，它们的性能比使用HDD的系统都有了很大的提升。

SSD有不同于HDD工作原理，所以在进行应用程序设计的时候，如果可以做到对SSD友好，那么就可以充分发挥SSD的全部性能潜能，应用程序的性能会进一步提高。你可以参考我们今天总结的四个设计原则进行实践。

## 思考题

最后给你留几道思考题。你们公司里面，有哪些后台服务和应用是使用SSD作为存储的？而对这些使用SSD的系统，有没有充分考虑SSD的特性，做深层的优化呢（比如降低损耗）？

感谢阅读，如果今天的内容让你有所收获，欢迎把它分享给你的朋友。
