蚍蜉实验室

海量小文件问题简介

随着计算机技术和互联网的发展,各类应用,比如图片,音视频,社交,电子商务等,涉及的数据量越来越大,单机存储技术和分布式存储系统也随之不断发展,但其中的海量小文件问题(通常数量在千万以上级别,1MB以内文件),成了业界的难题。许多公司根据自身的业务场景,对此提出了相应的存储解决方案。本文将分析海量小文件的问题所在,并结合业界已有的解决方案,探讨一些通用的可能解决点:硬件设施,元数据管理优化等。

原创文章版权归原作者所有,商业转载请联系原作者获取授权,任何转载请注明来源:https://xupifu.github.io/2017/08/18/losf/

1 存储设备

传统的存储设备为磁盘,访问时需要先寻道定位(seek),磁头的这种物理限制,导致磁盘的随机性能一般都比较差,而且很难有实质性的提升。因此,许多应用都会尽量使用顺序IO进行访问,遇到随机IO时,也会通过一些技术尽量将随机IO转成顺序IO,比如缓冲,缓存,日志等。

近几年,SSD存储在不断的发展,闪存没有寻道操作,这也使得应用不必再额外考虑如何将随机IO转为顺序IO。但是,闪存的先擦后写(Out-Of-Place Write)特性以及有限的擦除次数,导致其内部的管理(FTL,Flash Translation Layer)相对复杂,一旦管理不当,性能可能还不如传统磁盘。不过,只要管理得当,SSD就能够提供较低的IO延迟,很好的随机IO性能和可预期的性能一致性表现。无论是作为缓存层(Cache Tier)设备还是存储层(Capacity Tier)设备,SATA/NVMe SSD都能够为整系统的性能提升提供非常好的帮助。

另外,外存设备都有最小的读写单位,大小通常有512B,1KB,2K,4KB等,它们无法向内存一样可以按比特或字节进行改写。较大粒度的读写单位,将直接影响存储时的数据分布以及空间利用率等问题。

2 文件访问方式

一个文件通常包含路径,描述信息和数据内容,文件操作包括打开/关闭/读/写/创建/删除(open/close/read/write/create/delete)等。不同的操作系统,一般都提供了虚拟文件系统(比如Linux的VFS),它可以屏蔽具体文件系统的实现差异,借此兼容各类具体文件系统,获得较好的扩展性,而对上层应用,则是提供统一的文件系统接口。

读写一个文件一般需要以下步骤:

  • 路径解析
  • 读取目录项(dentry)内容
  • 根据文件名搜索目录项,获取inode位置
  • 读取inode信息
  • 定位所要访问的块位置
  • 访问相应数据块

为描述方便,将目录项和inode内容统称为元数据。通常,元数据和文件的数据内容存放在不同的区域。

在分布式文件系统中,管理区和数据区一般还分散在不同的物理机器上。

有些文件系统,会将inode信息和文件内容放置在一起,这对于小文件的管理是一个非常好的优化。

为了尽量减少IO,内存一般会作为缓冲/缓存使用,元数据和热点文件数据尽量会缓存在内存中。结合合缓存,还可以延缓数据的写入。同时,大量的文件系统都采用了日志技术,更是将大量可能的随机IO写转化成了顺序IO,真正的IO被延缓再异步写入。

另外,文件系统的数据块分配(相邻块),底层的IO调度器(次序调整/合并)都是针对传统存储设备的限制进行的优化。

3 问题原因

磁盘设备存在的磁头寻道的限制,导致其天然的不适合进行随机IO访问。对于以随机IO为主的应用,比如数据库应用,海量小文件存储,对象存储等,性能瓶颈就主要发生在磁盘IO上。在SSD设备没有被广泛使用前,为了缓解IO的性能瓶颈,上层系统和应用为此提出了各种解决方案,比如IO调度器设计,缓存系统,文件系统,特定应用场景优化等。SSD的出现,理论上可以非常好的解决IO瓶颈问题,但实际情况却非如此。现有的成熟系统和应用,都是基于磁盘的特性进行设计的,这里面有历史原因,也有兼容性考虑和过渡期等因素。可以预期的是,后端存储将不再是整系统的主要瓶颈,系统和应用将面临重构。不过,目前的状况是,大多数的解决方案采用了折衷方案:将SSD作为缓存层设备

  • 优点:现有系统和应用基本不用修改,只要缓存系统设计得当,绝大部分的IO将发生在SSD上,磁盘的性能瓶颈也基本被消除
  • 缺点:专为磁盘设计的IO逻辑没有发生变化,比如访问方式,数据布局,随机转顺序IO等,这也导致SSD的性能优势无法被充分利用

以随机访问为主的应用,IO是导致其性能不佳的最根本原因。当IO造成的影响减少后,上层的IO处理逻辑就成了主要的因素。数据库要处理的是一条条的记录,海量小文件存储面临的是众多的小文件,对象存储则是一个个的对象,无论是记录,小文件还是对象,其本质上是类似的,所面临的问题也是类似的。可以想见,解决方案也将是类似的。下文将以海量小文件存储所面临的问题进行说明。

假设某一目录下存放了千万级/亿级以上的小文件,当要读取其中的一个文件时,步骤可见”文件访问方式”一节描述。

目录项和数据块的组织一般是多重指针/链表/Hash/B/B+树等,类似数据库的索引,可以加速查询。但是,前提条件是,所有的目录项内容和inode信息可以缓存在内存中,否则一次IO访问将实际产生多个随机IO请求。那么,一般需要多大的内存呢?以100M个文件为例,目录项对应的每条文件描述信息+每个inode信息以1K计算,就需要100G内存作为缓存,这还只是1亿个文件,而且还不包括其他的缓存。可以想见,缓存所有的管理信息代价非常高昂。

访问某一数据块的检索步骤主要包括路径解析,inode检索和数据块号检索,建立索引以辅助检索是常见的优化方式。

使用文件系统时,一般也会人为建立各级目录(实质就是建立索引),防止单目录下存在海量文件。只要目录层次设计合理,这种方法就能够提高inode检索效率,当然,路径解析的时间也会相应增加,不过整体上看是值得的。

数据块的位置信息是inode信息的一部分,文件系统一般会专为大文件进行支持和优化,如连续块分配,多级数据块指针/索引等。如果文件不是经常进行增删改,这种方法就非常有效。但是,一旦发生频繁的增删改,块分配最终将呈现离散的状态,碎片化会非常严重,磁盘的顺序IO访问的优势也将不复存在。

当后端存储的IO性能显著提升后,目录项及目标数据块的检索时间将占主要的比重,检索效率自然就成了影响性能的主要因素。

为了充分利用磁盘的顺序IO,文件系统一般都会采用日志技术:将改写的内容以追加的方式顺序写入到日志空间,实际位置的写则是延迟写入。如果还考虑类似数据库的原子写保证(Double-Write Buffer),写带宽将至少浪费一半。

综上可以得出海量小文件存储性能瓶颈的主要原因:

  • 磁盘的物理限制使其天然不适合进行随机IO访问,这是最根本的原因
  • 专为磁盘设计的IO逻辑没有变化:
    • 元数据和文件数据一般是分区域的,一次IO访问将实际产生多个IO请求
    • 元数据相对较大,缓存代价比较昂贵
    • 目录项及目标数据块的检索效率
    • 频繁增删改带来的碎片化问题
    • 原子写保证等导致的带宽浪费

4 解决策略

4.1 硬件设施

既然磁盘的物理限制是导致问题的最根本的原因,而且适合随机IO的高性能SSD设备已经出现,并且还在不断的发展过程中(SATA->NVMe),使用SSD设备已经成为了必然的趋势。

解决方案中,主要分为两类:

  • 作为缓存层设备(Cache Tier),存储层设备依然是传统磁盘
  • 作为存储层设备(Capacity Tier)

SSD的先擦后写(Out-Of-Place Write)特性,即数据会被写到新的位置,老数据依然保留,这种特性非常适合用来实现原子写。针对磁盘,可以在文件系统层实现原子写,减少带宽浪费。

注:目前只有少量的文件系统已经实现原子写的特性。

对于跨网络的分布式存储/分布式文件系统,元数据和文件数据一般分布在不同的物理机器上,除了尽量不访问远端的数据外,网络开销也需要重点考虑。有高性能要求的应用需要使用相应的高性能网络设备。

4.2 元数据管理优化

在文件系统中,元数据包含目录项信息和inode节点信息,inode节点信息包含文件的描述信息和数据块的位置信息。在海量小文件中,比如小于4K的文件,可以考虑将文件内容和inode信息集中放置,访问时,一次性读取或写入。这种策略,已经在一些文件系统中实现,对小文件的存储性能有显著提升。

对于特定业务场景,文件的描述信息大部分是无用的,可以去除。同时,为了减少目录项的大小,小文件可以合并追加(有效利用顺序IO)到一个大文件中,配合索引文件,可以获取快速小文件的位置等信息。合并小文件的方法本质上和数据库非常类似。小文件合并最适合一次写入,多次读取的应用。对于频繁改写的应用,空间碎片将会不断产生。为了减少空间浪费,需要在系统相对空闲的时候进行碎片规整。

注:对于SSD设备,考虑如何将随机IO转为顺序IO是多余的,如何快速定位文件数据块的位置才是最主要的。

无论是将文件数据和inode信息进行集中管理,还是合并小文件(自行管理文件信息),目的都是为了尽量减少IO的次数,尽快获取到实际的文件内容。

注:大文件和小文件的分属不同的存储场景,一个偏重顺序IO,一个偏重随机IO,应该分开放置,以免互相影响。

4.3 缓存

在计算机系统中,缓存几乎无所不在,基于局部性原理,缓存能够很好的弥补不同存储层级的速度差异,从而提高整系统的性能。从应用的角度看,缓存可以由应用自行管理,也可以是后端存储进行管理(即对应用是透明的)。由于闪存的快速发展和良好的性能,使其能够和内存类似,成为缓存系统的承载介质之一。

分布式系统的应用,通常会有自己的独立本地缓存,从而减少跨网络的访问请求,这种缓存系统设计相对简单。另外,还有分布式缓存系统(协作式/共享式缓存),设计比较复杂,但能够充分利用各节点之间的缓存资源,提高缓存命中率。

无论哪种缓存系统,其目的都是为了减少甚至避免发生IO,尤其是远端的IO,从而提高系统性能。

5 小结

近几年,计算机和互联网等技术的发展,海量小文件的应用场景也越来越多,但是性能瓶颈也越来越突显。磁盘的物理限制使得磁盘不适合大量的随机IO访问,这也是海量小文件存储性能问题的根本原因所在。高性能SSD的出现,使得该问题得到了很大的缓解,但没有从根本上解决。专为磁盘而设计的IO逻辑,在已有的系统和应用中已经根深蒂固,SSD的特性和性能也无法充分发挥。结合成本考虑,大多数解决方案都是将SSD作为缓存层设备进行使用,系统和应用本身基本不做修改。除了更换存储设备外,有能力的厂商会根据自身的业务特点,结合SSD特性,从元数据管理和缓存系统等方面进行优化。与海量小文件问题类似的还有数据库应用,对象存储等,优化的思路也与上文描述类似。总之,要充分发挥高速设备的性能,系统和应用都需要做出相应的变化,好消息是,变化正在不断的进行中。