详解磁盘 IO: 第 5 部分 LSM 树中的访问模式

37 阅读7分钟

第一部分第二部分中,我们讨论了有助于执行磁盘写入的底层操作系统机制。在第三部分中,我们开始讨论不可变的磁盘数据结构LSM 树。今天我们将讨论 LSM 树中的随机和顺序访问模式。

该系列由 5 件组成:

访问模式是程序读取和写入数据的模式。一般来说,我们区分随机和顺序访问模式。但当然,没有什么是绝对的。对于即席查询来说,完全顺序读取是不可能的,因为必须先找到数据,但一旦找到数据,就可以顺序读取。

通过顺序访问,我们通常意味着读取单调地从较低的偏移量到较高的偏移量,并且较高的偏移量紧跟在较低的偏移量之后。

_随机_访问是读取不连续的数据块。它通常涉及磁盘寻道,跳过文件的部分内容以找到数据。跳跃大小通常很难预测,并且跨越许多页面(例如,在磁盘上遍历 B 树时,我们必须跳过整个级别才能继续搜索)。总之,顺序访问意味着单调地读取连续的块,而随机访问几乎就是其他任何事情。

由于顺序问具有可预测性,因此通常更倾向于顺序访问。在之前的一篇文章中,我们讨论过避免页面错误可以提高性能,因为读取操作是从 RAM 而不是磁盘进行的。在顺序读取数据时,内核可能会提前加载页面,这个过程称为预取:根据对未来请求的预测从磁盘进行推测性读取。此外,顺序读取可以避免额外的寻道。

针对顺序读取和顺序写入进行优化是正交问题。顺序写入的记录并不总是一起读取(例如,顺序写入的 LSM 树中的点查询仍然是随机的)。同样,一起读取的数据不一定以顺序方式放在磁盘上(例如,B 树中级别的顺序读取,可能已随机更新)

SSD 上的随机读取

HDD上,由于其物理组织和工作方式,顺序访问优于随机访问。读/写头连接到机械臂,机械臂必须穿过磁盘才能读取数据块;磁盘必须旋转以将磁道扇区定位在读/写头下方。这一切都涉及大量的移动。操作系统试图通过最佳地缓存、缓冲和调度操作来分摊成本。

SSD由电子元件制成,没有任何移动部件。在这方面,SSD 本质上不同于 HDD,并且不会因数据在磁盘上的物理存储位置而导致性能下降。但是,当前的 SSD 技术存在由写入放大引起的性能下降问题。没有移动部件可以实现其他几个特性,例如并行性,但我们不会在本文中讨论它们。

SSD 上的最小读取单元是。读写操作以页面为单位执行。删除一页数据不会立即从物理上删除数据。相反,页面会被标记为陈旧,并等待垃圾回收来回收可用空间。

由于写入是按页进行的,因此即使需要更新单个字节,整个页也会被写入。同时,由于 NAND 存储的特性,页无法就地更新,因此只能对空页执行写入。这两个特性导致了SSD 上的写入放大。

经过大量随机写入之后,FTL(闪存传输层)将用完可用页面并必须执行垃圾收集:读取、合并然后将活动页面写入可用块、释放被陈旧页面占用的块并回收磁盘空间的过程。

一些 SSD 会实施后台垃圾收集,利用空闲时间整合块并在必须写入新数据之前回收陈旧页面,从而确保未来前台写入进程有足够的可用页面。但如果写入压力足够大,垃圾收集进程可能无法跟上工作量,从而对写入性能产生负面影响

日志结构系统的一个关键目标是顺序写入。但是,如果 FTL 由两个日志结构应用程序共享(或者甚至由具有多个附加流的单个应用程序共享),则进入 FTL 的数据可能看起来是随机的或不相交的。您可以在本文中阅读有关“堆叠”日志操作的更多信息。

我们讨论了使用 SSD 时需要考虑的多个问题写入完整页面比写入小于页面大小的数据更好,因为最小的 SSD 存储单位是页面由于更新页面实际上会分配一个新页面并使前一个页面无效,因此更新可能会导致垃圾收集。最好保持写入操作页面对齐,以避免额外的写入倍增。最后,将具有相似生命周期的数据放在一起(例如,同时写入和丢弃的数据)将有利于提高性能。这些观点中的大多数都支持不可变的 LSM 类存储,而不是允许就地更新的系统:写入是批量的,SSTable 是按顺序写入的,文件是不可变的,当删除时,整个文件会立即失效。

预写日志

某些数据结构本质上是顺序的。例如,数据库和文件系统使用的预写日志。它用于提高耐用性:对数据文件的更改首先按顺序附加到日志中。

当主存储赶上进度并将记录提交到数据文件时,保存恢复数据的提交日志段将被丢弃。如果进程在主存储赶上进度之前终止,则重放预写日志以恢复重新启动之前的状态数据库。如果我们遵循此过程,则不必在每次操作时将数据文件刷新到磁盘上:操作可以一起批处理,同时仍能保证耐用性。使用预写日志可显著减少可变和不可变存储类型的写入量。

通常建议为预写日志使用单独的物理设备,以确保内存表刷新和 WAL 写入都是连续的。这样做还有许多其他原因:避免 IO 饱和、实现更好的故障转移、更可预测的延迟。

批量写入

LSM-Trees 使用内存表,在数据到达主存储之前,先将数据存储在内存表中,用于读取和批量写入。达到大小阈值后,内存表将写入磁盘。

在这里,内存表充当缓冲区:读取、写入和更新操作都是针对内存表执行的,允许将一些项目批量处理在一起。当数据写入磁盘时,它会按顺序一次性完成。这可以摊销小规模随机写入的成本,并将其转换为磁盘上更大的顺序分配,将逻辑上不相关的数据的更新转换为物理上顺序的 I/O。

与预写日志(按传入顺序写入项目)不同,内存表在数据到达磁盘之前对其进行预排序,以方便顺序读取访问。更有可能一起读取的记录将一起写入。

结束语

如您所见,LSM 树中的所有写入操作都是顺序的:预写日志附加、Memtable 刷新、压缩。使用每个 SSTable 索引或预排序数据也有助于使至少一些读取操作顺序化。这只能在一定程度上完成,因为必须对多个文件执行读取,然后将它们合并在一起。

至少目前,这将是 IO 系列的最后一篇文章:为发布而进行的准备工作非常繁重,还需要进行一些编辑。在接下来的几个月里,我将完善现有的文章并为其添加更多有用的信息。