目录

        1. 简介

        2. 背景和之前的设计

        3. 设计和实施

        4. 评估

        5. 相关工作

        6. 结论


Haystack:Facebook 的照片存储

摘要:本文描述了 Haystack,一个为 Facebook 的照片应用程序提供优化的对象存储系统。Facebook 目前存储了超过 2600 亿张图像,相当于超过 20PB 的数据。用户每周上传 10 亿张新照片(60TB),峰值时 Facebook 每秒提供超过 100 万张图像。与我们以前的方法相比,Haystack 提供了一种成本更低、性能更高的解决方案,该方法利用了 NFS 的 NAS 设备。我们观察到,由于元数据查找,这种传统设计会导致过多的磁盘操作。我们仔细减少了每张照片的元数据,这样 Haystack 存储机器就可以在内存中执行所有元数据查找。这种选择节省了读取实际数据的磁盘操作,从而提高了整体吞吐量。

 

1. 简介

分享照片是 Facebook 最受欢迎的功能之一。迄今为止,用户已经上传了超过 650 亿张照片,使 Facebook 成为世界上最大的照片共享网站。对于每一张上传的照片,Facebook 都会生成并存储四张不同大小的图像,这意味着超过 2600 亿张图像和超过 20PB 的数据。用户每周上传 10 亿张新照片(60TB),峰值时 Facebook 每秒提供超过 100 万张图像。由于我们预计这些数字在未来会增加,照片存储对 Facebook 的基础设施构成了重大挑战。

本文介绍了 Haystack 的设计和实现,Haystack 是 Facebook 的照片存储系统,已经生产了24 个月。Haystack 是一个对象存储库,我们为在 Facebook 上共享照片而设计,数据只写一次,经常读,从不修改,也很少删除。我们为照片设计了自己的存储系统,因为传统文件系统在我们的工作负载下表现不佳。

根据我们的经验,我们发现传统的基于 POSIX 的文件系统的缺点是目录和每个文件的元数据。对于照片应用程序,大部分元数据(如权限)都没有使用,因此浪费了存储容量。更重要的成本是文件的元数据必须从磁盘读入内存,以便找到文件本身。虽然在小范围内微不足道,但在数十亿张照片和数十亿字节数据的基础上,访问元数据是吞吐量的瓶颈。我们发现这是我们使用 NAS 应用程序时的关键问题。读取一张照片需要几个操作:一个通常更多将文件名转换为索引节点号,另一个从磁盘读取索引节点,最后一个读取文件本身。简而言之,将磁盘 IOs 用于元数据读取是我们读取吞吐量的限制因素。请注意,在实践中,这个问题会带来额外的成本,因为我们必须依赖内容分发网络(CDNs),如 Akamai,来服务大多数的读取流量。

 

鉴于传统方法的缺点,我们设计 Haystack 来实现四个主要目标:

高吞吐量和低延迟。我们的照片存储系统必须跟上用户的要求。超出我们处理能力的请求要么被忽略,这对于用户体验来说是不可接受的,要么由 CDN 处理,这个花费是昂贵的,并且达到了收益递减的点。此外,照片应该快速提供,以方便良好的用户体验。Haystack 通过每次读取最多需要一次磁盘操作来实现高吞吐量和低延迟。我们通过将所有元数据保存在内存中来实现这一点,这样可以大幅减少在磁盘上查找照片所需的元数据。

容错。在大规模系统中,故障每天都在发生。我们的用户相信他们的照片是可用的,尽管不可避免的服务器崩溃和硬盘故障,也不应该出现错误。可能会发生整个数据中心断电或跨国连接中断的情况。Haystack 在不同的地理位置复制每张照片。如果我们丢失了一台机器,我们会引入另一台机器来代替它,必要时复制数据以实现冗余。

性价比高。Haystack 比我们以前基于 NFS 的方法性能更好,成本更低。我们从两个方面来量化我们的优化:Haystack 每 TB 可用存储的成本和 Haystack 每 TB 可用存储的标准化读取速率(术语"可用"考虑了由诸如磁盘阵列级别、复制和底层文件系统等因素消耗的容量)。在 Haystack 中,每个可用 TB 的成本比同等 TB 的 NAS 设备低 28%,每秒处理的读取量高 4 倍。

简单。在生产环境中,我们不能夸大直接实施和维护的设计的优势。由于 Haystack 是一个新系统,缺乏多年的产品级测试,我们特别注意保持它的简单性。这种简单性让我们可以在几个月而不是几年内构建和部署一个工作系统。

这项工作描述了我们的经验,从 Haystack 的概念到生产质量系统的实施,每天服务数十亿张图像。我们的三个主要贡献是:

(1) Haystack 是一个对象存储系统,针对高效存储和检索数十亿张照片进行了优化。

(2) 在构建和扩展廉价、可靠且可用的照片存储系统方面的经验教训。

(3) 对 Facebook 照片共享应用程序的请求的描述。

我们将本文的其余部分组织如下。第 2 节提供了背景,并强调了我们以前的体系结构中的挑战。我们在第 3 节描述了 Haystack 的设计和实现。第 4 节描述了我们的照片读写工作负载,并展示了 Haystack 符合我们的设计目标。我们在第 5 节中对相关工作进行了比较,并在第 6 节中对本文进行了总结。

 

 

2. 背景和之前的设计

在本节中,我们描述了 Haystack 之前存在的体系结构,并强调了我们学到的主要经验。由于空间的限制,我们之前对这个设计的讨论忽略了生产级部署的几个细节。

 

2.1 背景

我们首先简要概述 Web 服务器、内容分发网络(CDN)和存储系统如何交互以在一个流行站点上提供照片的典型设计。图1 描述了用户访问包含图像的页面到从磁盘上的位置下载该图片的步骤。当访问一个页面时,用户的浏览器首先向 Web 服务器发送一个 HTTP 请求,该服务器负责生成浏览器要呈现的标记。对于每个图像,Web 服务器构造一个 URL,将浏览器指向下载数据的位置。对于流行的网站,这个 URL 通常指向 CDN。如果 CDN 有缓存的图像,那么 CDN 立即响应数据。否则,CDN 会检查 URL,该 URL 包含足够的信息,可以从网站的存储系统中检索照片。然后,CDN 更新它的缓存数据,并发送图像到用户的浏览器。图 1 设计典例如下:

大量图片数据的存储数据仓库 海量图片存储_缓存

 

 

2.2 基于 NFS 的设计

在我们的第一个设计中,我们使用基于 NFS 的方法实现了照片存储系统。虽然本小节的其余部分提供了关于该设计的更多细节,但我们学到的主要经验是,CDN 本身并不能为在社交网站上的照片服务提供实用的解决方案。CDN 确实有效地提供了最热门的照片——个人资料照片和最近上传的照片——但是 Facebook 这样的社交网站也产生了大量对不太受欢迎通常是旧的内容的请求,我们称之为 long tail。来自 long tail 的请求占了我们流量的很大一部分,几乎所有的请求都访问了支持照片存储的主机,因为这些请求通常会在 CDN 中丢失。虽然为这种 long tail 缓存所有照片非常方便,但这样做并不划算,因为需要非常大的缓存大小。

我们基于 NFS 的设计将每张照片存储在一组商用 NAS 设备上的文件中。一组机器、照片存储服务器,然后挂载这些 NAS 设备通过 NFS 导出的所有卷。图2 说明了这个体系结构,并显示了处理图像 HTTP 请求的照片存储服务器。照片存储服务器从图像的 URL 提取卷和文件的完整路径,通过 NFS 读取数据,并将结果返回给 CDN。图 2 基于 NFS 的设计如下:

大量图片数据的存储数据仓库 海量图片存储_Haystack_02

我们最初在 NFS 卷的每个目录中存储了数千个文件,这导致读取单个图片的磁盘操作数量过多。由于 NAS 设备管理目录元数据的方式,将数千个文件放在一个目录中效率极低,因为目录的 blockmap 太大,设备无法有效缓存。因此,为检索一个图片而进行 10 次以上的磁盘操作是很常见的。在将目录大小减少到每个目录数百个图片后,生成的系统通常仍会发生 3 次获取图片的磁盘操作:一次是将目录元数据读取到内存中,第二次是将信息节点加载到内存中,第三次是读取文件内容。

为了进一步减少磁盘操作,我们让照片存储服务器显式缓存由 NAS 设备返回的文件句柄。第一次读取文件时,照片存储服务器会正常打开文件,但也会将文件名缓存到 Memcache 中的文件句柄映射。当请求的文件的文件句柄被缓存时,照片存储服务器使用我们添加到内核中的自定义系统调用直接打开文件,由open_by_filehandle完成。遗憾的是,这种文件句柄缓存只提供了很小的改进,因为不太受欢迎的照片一开始就不太可能被缓存。有人可能会说,将所有文件句柄都存储在 Memcache 中的方法可能是一个可行的解决方案。但是,这只能解决部分问题,因为它依赖于将所有信息节点都放在主内存中的 NAS 设备,这是传统文件系统的一项昂贵要求。我们从 NAS 方法中学到的主要经验是,只关注缓存——无论是 NAS 设备的缓存还是像 Memcache 这样的外部缓存——对减少磁盘操作的影响有限。存储系统最终会处理对不太受欢迎的照片的 long tail 请求,这些照片在 CDN 中不可用,因此很可能会在我们的缓存中丢失。

 

 

2.3 讨论

我们很难提供何时构建或何时不构建定制存储系统的精确指导。然而,我们相信这仍然有助于社区深入了解我们决定构建 Haystack 的原因。

面对我们基于 NFS 的设计中的瓶颈,我们探索了构建一个类似于 GFS 的系统是否有用。因为我们将大部分用户数据存储在 MySQL 数据库中,所以我们系统中文件的主要用例是工程师用于开发工作、日志数据和照片的目录。NAS 设备为开发工作和日志数据提供了非常好的性价比。此外,我们利用 Hadoop 来处理非常大的日志数据。在 long tail 中服务照片请求是MySQL、NAS设备和Hadoop都不适合的。

人们可以这样描述我们面临的困境,因为现有的存储系统缺乏正确的内存与磁盘比率。但是,没有合适的比例。系统只需要足够的内存,这样就可以一次缓存所有文件系统元数据。在我们基于 NAS 的方法中,一张照片对应一个文件,每个文件至少需要一个信息节点,这是数百个字节。在这种方法中有足够的主存是不划算的。为了实现更好的性价比,我们决定构建一个定制的存储系统,减少每张照片的文件系统元数据量,这样拥有足够的主内存比购买更多的 NAS 设备更具成本效益。

 

 

3. 设计和实施

Facebook 使用 CDN 来提供热点图像,并利用 Haystack 来有效地响应 long tail 中的照片请求。当一个网站有一个静态内容的输入输出瓶颈时,传统的解决方案是使用内容分发网络。CDN 承担了足够的负担,因此存储系统可以处理剩余的 tail。在 Facebook,为了使传统(且廉价)的存储方法不受输入/输出的限制,CDN 必须缓存不合理的大量静态内容。

我们知道在不久的将来,CDN 不能完全解决我们的问题,所以我们设计了 Haystack 来解决我们基于 NFS 的方法中的关键瓶颈磁盘操作。我们接受对不旧的照片的请求可能需要磁盘操作,但旨在将此类操作的数量限制在读取实际照片数据所需的数量。Haystack 通过显著减少内存文件系统元数据来实现这一目标,从而使将所有这些元数据保存在内存中变得切实可行。

回想一下,每个文件存储一张照片会导致文件系统元数据超出合理缓存的范围。Haystack 采用了一种直接的方法它将多张照片存储在一个文件中,因此可以维护非常大的文件。我们表明这种直接的方法非常有效。此外,我们认为它的简单性就是它的优势,便于快速实现和部署。我们现在讨论这个核心技术和围绕它的体系结构组件如何提供可靠和可用的存储系统。在下面对 Haystack 的描述中,我们区分了两种元数据。应用程序元数据描述了构建浏览器可用来检索照片的网址所需的信息。文件系统元数据标识主机检索驻留在该主机磁盘上的照片所需的数据。

 

 

3.1 概述

Haystack 架构由3个核心组件组成:Haystack 存储、Haystack 目录和 Haystack 缓存。为了简洁起见,我们用省略的 " Haystack " 来指代这些组件。该存储封装了照片的持久存储系统,并且是管理照片的文件系统元数据的唯一组件。我们按物理容量组织存储的容量。例如,我们可以将服务器的 10TB 容量组织成 100 个物理卷(physical volumes),每个物理卷提供 100 GB 的存储空间。我们进一步将不同机器上的物理卷分组为逻辑卷。当 Haystack 将照片存储在逻辑卷上时,照片会写入所有对应的物理卷。这种冗余使我们能够减轻由于硬盘故障、磁盘控制器故障等造成的数据丢失。该目录维护逻辑到物理的映射以及其他应用程序元数据,例如每个照片所在的逻辑卷和具有可用空间的逻辑卷。缓存充当我们的内部 CDN,它可以阻止响应最受欢迎的照片的请求访问 Haystack 存储,并在上游 CDN 节点出现故障并需要重新提取内容时提供隔离。

图3 说明了存储、目录和缓存组件如何适应用户浏览器、Web 服务器、CDN 和存储系统之间的规范交互。在 Haystack 架构中,浏览器可以指向 CDN 或缓存。请注意,虽然缓存本质上是一个 CDN,但为了避免混淆,我们使用 "CDN" 来指代外部系统,"Cache" 来指代我们缓存照片的内部系统。拥有内部缓存基础设施使我们能够减少对外部 CDN 的依赖。图 3 照片服务如下:

大量图片数据的存储数据仓库 海量图片存储_分布式存储系统_03

当用户访问网页时,Web 服务器使用目录为每张照片构造一个网址。该网址包含几条信息,每条信息对应于从用户浏览器联系 CDN(或缓存)到最终从存储系统中的机器检索照片的一系列步骤。将浏览器指向 CDN 的典型网址如下所示:

http://(CDN)/(Cache)/(Machine id)/(Logical volume, Photo)

URL 的第一部分指定从哪个 CDN 请求照片。CDN 可以只使用 URL 的最后一部分在内部查找照片:逻辑卷和照片 id。如果内容分发网络找不到照片,它会从网址中删除内容分发网络地址,并联系缓存。缓存会执行类似的查找来查找照片,如果没有找到,会从网址中删除缓存地址,并从指定的存储机器中请求照片。直接转到缓存的照片请求有类似的工作流程,只是网址缺少特定于 CDN 的信息。

图4 展示了 Haystack 中的上传路径。当用户上传照片时,她首先将数据发送到 Web 服务器。接下来,该服务器从目录请求可写逻辑卷。最后,Web 服务器为照片分配一个唯一的 id,并将其上传的数据到映射到所分配的逻辑卷的每个物理卷。图 4 上传照片如下:

大量图片数据的存储数据仓库 海量图片存储_Haystack_04

 

 

3.2 Haystack 目录

该目录有四个主要功能。首先,它提供了从逻辑卷到物理卷的映射。Web 服务器在上传照片和为页面请求构造图像 URL 时使用这种映射。其次,目录负载平衡跨逻辑卷进行写操作和跨物理卷进行读操作。第三,目录决定一个照片请求应该由 CDN 处理还是由缓存处理。这个功能允许我们调整对 CDN 的依赖。第四,目录标识那些由于操作原因或由于这些卷已达到其存储容量而为只读的逻辑卷。为了便于操作,我们以机器为单位将卷标记为只读。

当我们通过添加新机器来增加存储容量时,这些机器是可写的;只有启用了写操作的机器才能接收上传。随着时间的推移,这些机器上的可用容量会减少。当机器耗尽其容量时,我们将其标记为只读。在下一小节中,我们将讨论这种区别如何对缓存和存储产生微妙的影响。

目录是一个相对直接的组件,它将其信息存储在一个复制的数据库中,该数据库通过一个PHP 接口访问,该接口利用 Memcache 来减少延迟。如果我们丢失了存储机器上的数据,我们会删除映射中相应的条目,并在新的存储机器联机时替换它。

 

 

3.3 Haystack 缓存

缓存从 CDN 接收对照片的 HTTP 请求,也直接从用户的浏览器接收。我们将缓存组织成一个分布式哈希表,并使用照片的 id 作为定位缓存数据的关键。如果缓存无法立即响应请求,则缓存会从网址中标识的存储机器中获取照片,并根据需要回复 CDN 或用户的浏览器。

我们现在强调缓存的一个重要行为方面。只有满足两个条件时,它才会缓存照片:(a) 请求直接来自用户,而不是 CDN;(b) 照片是从可写的存储机器中提取的。第一个条件的理由是,我们对基于 NFS 的设计的经验表明,post-CDN 缓存是无效的,因为在 CDN 中未命中的请求不太可能命中我们的内部缓存。第二个原因是间接的。由于两个有趣的特性,我们使用缓存来保护启用写操作的存储机器不被读取照片在上传后很快就会被频繁访问,并且我们工作负载的文件系统通常在执行读或写操作时性能更好,但不能同时执行这两种操作 4.1 因此,如果没有缓存,启用写操作的存储机器将会看到最多的读取操作。鉴于这一特点,我们计划实施的一项优化是主动将最近上传的照片推送到缓存中,因为我们预计这些照片将很快被经常性的读取。

 

 

3.4 Haystack 存储

存储机器的接口是基本的。读取操作会发出非常具体且适度的请求,要求提供给定 id 的照片、特定逻辑卷以及来自特定物理存储机器的照片。如果找到照片,机器会将照片返回。否则,机器将返回一个错误。

每个存储机器管理多个物理卷。每卷都有数百万张照片。具体来说,读者可以将物理卷简单地理解为一个非常大的文件(100GB),保存为 ' /hay/ haystack_<logical volume id>'。存储机器仅使用相应逻辑卷的 id 和照片所在的文件偏移量就可以快速访问照片。这一点是 Haystack 设计的基础:在不需要磁盘操作的情况下检索特定照片的文件名、偏移量和大小。存储机器为它所管理的每个物理卷保存打开的文件描述符,并保存照片 id 到文件系统元数据(即文件、偏移量和字节大小)的内存映射,这对检索照片至关重要。

我们现在描述每个物理卷的布局以及如何从该卷导出内存映射。存储机器将物理卷表示为一个大文件,该文件由一个超级块和一系列 needle 组成。每个 needle 代表一张存储在 Haystack 中的照片。图5显示了一个卷文件(volume file)和每个 needle 的格式。表1 描述了每个 needle 中的字段。图 5 Haystack 存储文件的布局。表 1 needle 中域的解释如下:

大量图片数据的存储数据仓库 海量图片存储_Facebook_05

    

大量图片数据的存储数据仓库 海量图片存储_缓存_06

为了快速检索 needle,每个存储机器为它的每个卷维护一个内存中的数据结构。该数据结构将对key、备用键 key)映射到相应的 needle 标志、字节大小和卷偏移量(解释:由于历史原因,照片的 id 对应于 key,而它的类型用于备用 key。在一次上传过程中,Web服务器将每张照片缩放为四种不同的尺寸(或类型),并将它们存储为不同的针,但使用相同的键。这些针之间的重要区别是可选的关键字段,按降序可以是 'n'、'a'、's' 或 't'。)。崩溃之后,存储机器可以在处理请求之前直接从卷文件重建这个映射。现在我们描述存储机器如何在响应读、写和删除请求(存储支持的唯一操作)时维护其卷和内存映射。

 

3.4.1 照片读取

当缓存机器请求照片时,它会向存储机器提供逻辑卷 id、key、备用 key  cookie。cookie是嵌入在照片网址中的一个数字。上传照片时,cookie 的值随机分配并存储在目录中。该 cookie 有效地消除了旨在猜测照片有效网址的攻击。

当存储机器从缓存机器接收到照片请求时,存储机器在其内存映射中查找相关元数据。如果照片未被删除,存储机器会在卷文件中寻找适当的偏移量,从磁盘(其大小可以提前计算)中读取整个 needle,并验证 cookie 和数据的完整性。如果这些检查通过,则存储机器将照片返回到缓存机器。

 

3.4.2 照片写入

将照片上传到 Haystack 时,Web 服务器会向存储机器提供逻辑卷 id、key、备用 key cookie 和数据。每台机器同步地将 needle 的图片附加到其物理卷文件,并根据需要更新内存中的映射。虽然简单,但这种仅附加的限制使一些修改照片的操作变得复杂,例如旋转。由于 Haystack 不允许覆盖 needle,因此只能通过添加具有相同 key 和备用 key 的更新 needle 来修改照片。如果新 needle 写入不同于原始指针的逻辑卷,目录将更新其应用程序元数据,并且将来的请求将永远不会获取旧版本。如果新 needle 被写入相同的逻辑卷,那么存储机器将新 needle 追加到相同的相应物理卷。Haystack 根据它们的偏移来区分这种重复的 needle。也就是说,物理卷内的 needle 的最新版本是处于最高偏移的版本。

 

3.4.3 照片删除

删除照片很简单。存储机器在内存映射和卷文件中同步设置 delete 标志。请求删除照片首先检查内存标志,如果该标志启用,则返回错误。请注意,被删除 needle 占用的空间暂时是丢失的。稍后,我们将讨论如何通过压缩卷文件来回收已删除的 needle 空间。

 

3.4.4 索引文件

存储机器在重新启动时使用一个重要的优化——索引文件。理论上,机器可以通过读取所有物理卷来重建其内存映射,但这样做很耗时,因为必须从磁盘中读取大量数据(相当于万亿字节)。索引文件允许存储机器快速建立其内存映射,从而缩短重启时间。

存储机器为它们的每个卷维护一个索引文件。索引文件是内存中数据结构的检查点,用于有效地定位磁盘上的 needle索引文件的布局类似于卷文件,包含一个超块(superblock),后面跟着与超块中每个 needle 对应的索引记录序列。这些记录的出现顺序必须与卷文件中相应 needle 的出现顺序相同。图 6 展示了索引文件的布局,表 2 解释了每个记录中的不同字段。图 6 Haystack 索引文件的布局。表 2 索引文件中字段的说明如下:

大量图片数据的存储数据仓库 海量图片存储_Facebook_07

   

大量图片数据的存储数据仓库 海量图片存储_分布式存储系统_08

重启使用索引比仅仅读取索引和初始化内存映射稍微复杂一些。出现复杂情况是因为索引文件是异步更新的,这意味着索引文件可能代表过时的检查点。当我们写一张新照片时,存储机器同步地在卷文件的末尾添加一个 needle,并异步地在索引文件中添加一条记录。当我们删除一张照片时,存储机器同步设置该照片的 needle 中的标志,而不更新索引文件。这些设计决策允许写入和删除操作更快地返回,因为它们避免了额外的同步磁盘写入。它们还会导致两个我们必须解决的副作用needle可以在没有相应索引记录的情况下存在,并且索引记录不会反映删除的照片。

我们把没有对应索引记录的 needle 称为 orphans。在重新启动过程中,存储计算机依次检查每个孤立文件,创建一个匹配的索引记录,并将该记录追加到索引文件中。请注意,我们可以快速识别 orphans,因为索引文件中的最后一条记录对应于卷文件中的最后一个非 orphans needle。为了完成重启,存储机器现在只使用索引文件初始化它的内存映射。

由于索引记录不反映已删除的照片,存储机器可能会检索实际上已被删除的照片。为了解决这个问题,在存储机器读取整个 needle 的照片后,该机器可以检查删除的标志。如果 needle 被标记为已删除,存储机器会相应地更新其内存映射,并通知缓存未找到该对象。

 

3.4.5 文件系统

我们将 Haystack 描述为一个对象存储,它利用了一个通用的类似 Unix 的文件系统,但是有些文件系统比其他文件系统更适合 Haystack。特别是,存储机器应该使用不需要太多内存的文件系统,以便能够在大文件中快速执行随机搜索。目前,每台存储计算机都使用 XFS ,这是一种基于扩展区的文件系统。XFS 对 Haystack 有两个主要优势。首先,几个连续的大文件的 blockmap 可以小到足以存储在主内存中。其次,XFS 提供了高效的文件预分配,减少了碎片并控制了大数据块映射的增长。

使用 XFS,Haystack 可以消除读取照片时检索文件系统元数据的磁盘操作。然而,这种好处并不意味着 Haystack 可以保证每次读取照片时只需要一次磁盘操作。当照片数据跨越盘区或磁盘阵列边界时,文件系统需要多个磁盘操作,这种情况时有发生。Haystack 预分配 1GB 的扩展区,并使用 256KB 的 RAID 条带大小,因此实际上我们很少遇到这些情况。

 

 

3.5 从故障中恢复

像许多运行在商用硬件上的其他大规模系统一样,Haystack 需要容忍各种故障:有故障的硬盘、出现问题的 RAID 控制器、坏的主板等。我们使用两种直接的技术来容忍故障——一种用于检测,另一种用于修复。

为了主动发现有问题的存储计算机,我们维护一个后台任务,称为后台工作,它定期检查每个存储计算机的运行状况。pitchfork 远程测试与每个存储机器的连接,检查每个卷文件的可用性,并尝试从存储机器读取数据。如果 pitchfork 确定存储机器始终未通过这些运行状况检查,则 pitchfork 会自动将驻留在该存储机器上的所有逻辑卷标记为只读。我们会手动解决离线检查失败的根本原因。

一旦诊断出来,我们也许能很快解决问题。有时,这种情况需要更繁重的批量同步操作,其中我们使用副本提供的卷文件来重置存储机器的数据。批量同步很少发生(每月几次),并且很少发生。主要瓶颈是要进行大容量同步的数据量通常比每个存储机器上网卡的速度大几个数量级,导致平均恢复时间为几个小时。我们正在积极探索解决这一限制的技术。

 

 

3.6 优化

我们现在讨论对 Haystack 的成功很重要的几个优化。

 

3.6.1 压缩

压缩是一种在线操作,可回收已删除和重复的 needle具有相同 key 和备用 key  needle所占用的空间。存储机器通过将 needle 复制到新文件中来压缩卷文件,同时跳过任何重复或删除的条目。在压缩过程中,两个文件都会被删除。一旦此过程到达文件的末尾,它会阻止对卷的任何进一步修改,并自动交换文件和内存结构。

我们从已删除的照片中使用压缩空间。删除的模式类似于照片视图:新的照片更有可能被删除。在一年的时间里,大约 25% 的照片被删除。

 

3.6.2 节省更多内存

如上所述,存储机器维护一个包含标志的内存数据结构,但是我们当前的系统只使用标志字段来标记一个被删除的 needle。我们通过将已删除照片的偏移量设置为 0,消除了对内存中标志表示的需要。此外,存储机器不会跟踪内存中的 cookie 值,而是在从磁盘读取 needle 后检查提供的 cookie。通过这两种技术,存储机器减少了 20% 的内存占用。

目前,Haystack 每张照片平均使用 10 字节的内存。回想一下,我们将每个上传的图像扩展为四张照片,所有照片都具有相同的 key(64位)、不同的备用 key(32位)以及不同的数据大小(16位)。除了这32个字节之外,由于哈希表,Haystack 每张图像消耗大约 2 个字节的开销,使得同一图像的四张照片的总开销达到 40 个字节。作为比较,考虑 Linux 中的一个xfs_inode_t结构是 536 字节。

 

3.6.3 批量上传

由于磁盘通常更擅长执行大规模的顺序写入,而不是小的随机写入,因此我们尽可能一起批量上传。幸运的是,许多用户将整个相册上传到 Facebook,而不是单个图片,这显然提供了一个将相册中的照片批处理在一起的机会。我们在第 4 节中量化了聚合写入的改进。

 

 

4. 评估

我们将我们的评估分为四个部分。首先,我们描述了 Facebook 发现照片的请求。在第二个和第三个例子中,我们分别展示了目录和缓存的有效性。最后,我们分析了存储在使用合成和生产工作负载时的表现。

 

4.1 照片请求的特征

照片是用户在脸书上分享的主要内容之一。用户每天上传数百万张照片,上传的照片往往比旧照片更受欢迎。图7 显示了每张照片受欢迎程度与照片年龄的关系。为了理解图表的形状,讨论是什么推动了 Facebook 的照片请求是很有用的。图 7 按年龄(自上传以来的时间)分类的一天中请求的照片数量的累积分布函数如下:

大量图片数据的存储数据仓库 海量图片存储_Haystack_09

 

4.1.1 驱动照片请求的功能

Facebook 98% 的照片请求都有两个功能:动态消息(NewsFeed)和相册。动态消息功能向用户展示他们的朋友最近共享的内容。相册功能允许用户浏览朋友的照片。她可以查看最近上传的照片,也可以浏览个人相册。(NewsFeed 是 Facebook 的叫法,即用户获取订阅感兴趣的信息)

图7 显示了对几天前的照片的请求急剧上升。动态消息驱动了大部分最近照片的流量,在 2天左右,当许多消息不在默认的提要视图中显示时,流量就会急剧下降。从这个数字可以看出两个关键点。首先,流行度的迅速下降表明,在 CDN 和缓存中进行缓存对于托管流行内容非常有效。第二,图表有一个 long tail,这意味着大量的请求不能使用缓存的数据处理。

 

4.1.2 流量

表3 显示了 Facebook 上照片的流量。由于我们的应用程序将每个图像扩展到 4 个,并将每个大小保存在 3 个不同的位置,所以 Haystack 照片的数量是上传照片数量的 12 倍。表格显示,Haystack 能够响应来自 CDNs 的大约 10% 的照片请求。观察一下,较小的图像占了大部分被浏览的照片。这个特性强调了我们希望最小化元数据开销的愿望,因为效率低下会迅速增加。此外,对于 Facebook 来说,阅读较小的图像通常是一种对延迟更为敏感的操作,因为它们显示在动态消息中,而较大的图像显示在相册中,可以通过预取来隐藏延迟。表 3 每日照片流量如下:

大量图片数据的存储数据仓库 海量图片存储_大量图片数据的存储数据仓库_10

 

 

4.2 Haystack 目录

Haystack 目录平衡了 Haystack 存储机器之间的读写。图8 描述了如预期的那样,目录的直接散列策略分配读和写是非常有效的。该图显示了同时部署到生产环境中的 9 台不同存储机器的多写操作数量。这些盒子中的每一个都存储了一组不同的照片。由于这些行几乎无法区分,我们得出结论,目录平衡写得很好(Since the lines are nearly indistinguishable, we conclude that the Directory balances writes well)。跨存储机器比较读取流量显示出类似的良好平衡行为。图 8 发送到 9 个不同的支持写操作的 Haystack 存储机器的 multi-write 操作卷。该图有9条不同的线,彼此紧密重叠。

大量图片数据的存储数据仓库 海量图片存储_Facebook_11

 

 

4.3 Haystack 缓存

图9 显示了 Haystack 缓存的命中率。回想一下,如果照片保存在可写的存储机器上,缓存仅存储照片。这些照片是相对较新的,这解释了大约 80% 的高命中率。由于启用写操作的存储机器也将获得最多的读取次数,因此缓存可以有效地大幅降低受影响最大的机器的读取请求率。图 9 可能存储在 Haystack 缓存中的图像的缓存命中率如下:

大量图片数据的存储数据仓库 海量图片存储_大量图片数据的存储数据仓库_12

 

 

4.4 Haystack 存储

回想一下,Haystack 针对照片请求的 long tail,旨在保持高吞吐量和低延迟,尽管看起来是随机读取。我们展示了存储机器在合成和生产工作负载下的性能结果。

 

4.4.1 实验设置

我们在商用存储分片上部署存储机器。典型的硬件配置 2U 存储分片包括 2 个超线程四核英特尔至强处理器、48 GB内存、一个具有 256–512 MB NVRAM 的硬件 RAID 控制器和 12 个 1TB SATA 驱动器。

每个存储分片服务器提供大约 9TB 的容量,配置为由硬件 RAID 控制器管理的 RAID-6 分区。RAID-6 提供了足够的冗余和出色的读取性能,同时降低了存储成本。控制器的 NVRAM 写回缓存缓解了 RAID-6 下降的写性能。由于我们的经验表明,在存储机器上缓存照片是无效的,所以我们将 NVRAM 完全保留为写。我们还禁用磁盘缓存,以确保在崩溃或断电的情况下数据的一致性。

 

4.4.2 基准性能

我们使用两个基准来评估存储机器的性能:Randomio 和 Haystress。Randomio 是一个开源的多线程磁盘 I/O 程序,我们使用它来测量存储设备的原始容量。它发出随机的 64KB 读取,使用直接输入/输出进行扇区对齐请求,并报告最大的可持续吞吐量。我们使用随机化来建立一个读取吞吐量的基线,我们可以将其他基准的结果与之进行比较。

Haystress 是一个定制的多线程程序,我们使用它来评估存储机器的各种合成工作负载。它通过 HTTP(如缓存)与存储机器通信,并评估存储机器可以保持的最大读写吞吐量。Haystress 对大量虚拟图片进行随机读取,以减少机器缓冲区缓存的影响;也就是说,几乎所有的读取都需要磁盘操作。在本文中,我们使用了七种不同的工作负载来评估存储机器。

表4 描述了在我们的基准测试下,存储机器可以承受的读写吞吐量和相关延迟。工作负载 A对具有 201 个卷的存储机器上的 64KB 图片执行随机读取。结果显示,Haystack 提供了设备原始吞吐量的 85%,而延迟仅增加了 17%。表 4 多工作负载上读和 Multi-write 操作的吞吐量和延迟。配置 B 使用 8KB 和64KB 图像的混合。其余配置使用 64KB 映像如下:

大量图片数据的存储数据仓库 海量图片存储_大量图片数据的存储数据仓库_13

我们将存储机器的开销归因于四个因素:(a) 它直接运行在文件系统存储处理磁盘上;(b)磁盘读取大于 64KB,因为需要读取整个 needle;(c) 存储的图片可能与底层的 RAID-6 设备条带大小不一致,因此有一小部分图片是从多个磁盘读取的;(d) 以及 Haystack 服务器的 CPU 开销(索引访问、校验和计算等)。

在工作负载 B 中,我们再次检查只读工作负载,但更改了 70% 的读取,以便它们请求更小的图片(8KB而不是 64KB)。实际上,我们发现大多数请求不是针对最大尺寸的图像(如相册中所示),而是针对缩略图和个人资料图片。

工作负载 C、D 和 E 显示了存储机器的写吞吐量。回想一下,Haystack 可以一起批量写入。工作负载 C、D 和 E 分别将组 1、4 和 16 写入单个多写入。该表显示,在 4 个和 16 个图片上分摊写入的固定成本分别提高了 30% 和 78% 的吞吐量。正如预期的那样,这也减少了每个图片的延迟。

最后,我们来看看读写操作的性能。工作负载 F 混合使用 98% 的读取和 2% 的多次写入,而 G 混合使用 96% 的读取和 4% 的多次写入,每次多次写入写入 16 个图片。这些比率反映了生产中经常观察到的情况。该表显示,即使存在写入,存储也能提供高读取吞吐量。

 

4.4.3 生产工作量

本节检查了存储在生产机器上的性能。如第 3 节所述,有两种类型的存储——可写和只读。启用写操作的主机服务于读和写请求,只读主机只服务于读请求。由于这两个类有相当不同的流量特征,我们在每个类中分析一组机器。所有机器都有相同的硬件配置。

以每秒间隔来看,存储看到的照片读写操作量可能会有很大的峰值。为了确保即使在出现这些峰值时也有合理的延迟,我们保守地分配了大量启用写操作的机器,以使它们的平均利用率较低。

图10 显示了只读和可写存储机器上不同类型操作的频率。请注意,我们在周日和周一看到照片上传的峰值,在周四到周六之前,这一周的其余时间都会平稳下降。然后一个新的星期天到来了,我们达到了一个新的周高峰。总的来说,我们的足迹每天增长 0.2% 到 0.5%。图 10:两个 Haystack Store 机器上不同操作的速率:一个只读,另一个可写。

大量图片数据的存储数据仓库 海量图片存储_分布式存储系统_14

如第 3 节所述,对存储的写操作总是在生产机器上多次写,以摊销写操作的固定成本。寻找图像组是相当简单的,因为每张照片的 4 个不同大小的存储在 Haystack。用户将一批照片上传到相册中也很常见。结合这两个因素,这个支持写的机器每次写一次的平均图像数是9.27。第 4.1.2 节解释说,新上传的照片的读取率和删除率都很高,并且会随着时间的推移而下降。这种行为也可以在图10 中观察到;启用写操作会看到更多的请求(即使部分读流量由缓存提供)。另一个值得注意的趋势是:随着越来越多的数据被写入可写机器,照片的数量会增加,从而导致读取请求率的增加。图11 显示了在与图10 相同的时间段内,在相同的两台机器上的读操作和多写操作的延迟。图 11 在相同的 3 周时间内,图 10 中的两个 Haystack Store 机器上的读取和 Multi-write  入操作的平均延迟如下:

大量图片数据的存储数据仓库 海量图片存储_分布式存储系统_15

Multi-write 操作的延迟相当低(在 1 到 2 毫秒之间),即使通信量变化很大,延迟也很稳定。Haystack 机器有一个 NVRAM 支持的 RAID 控制器,它为我们写入数据。如第 3 节所述,NVRAM 允许我们异步写入 needle,然后在多写完成后发出一个 fsync 来刷新卷文件。多写延迟非常平滑和稳定。

即使流量变化很大(在 3 周时间内达到 3 倍),只读机器上的读取延迟也相当稳定。对于可写机器,读取性能受三个主要因素影响。首先,随着存储在机器上的照片数量的增加,机器的读取流量也在增加(比较图10 中的每周流量)。其次,可写机器上的照片缓存在缓存中,而只读机器上的照片不缓存。这表明缓冲区缓存对只读机器更有效。第三,最近写的照片通常会马上读回来,因为 Facebook 突出了最近的内容。启用写操作的机器上的这种读取将总是命中缓冲区高速缓存,并提高缓冲区高速缓存的命中率。图中线条的形状是这三个因素结合的结果。存储机器的中央处理器利用率低。CPU 空闲时间在 92-96% 之间变化。

 

 

5. 相关工作

据我们所知,Haystack 的目标是一个新的设计点,专注于大型社交网站所看到的照片请求的 long tail。

文件系统Haystack 采用了日志结构的文件系统,Rosenblum 和 Ousterhout设计该系统的目的是优化写吞吐量,其思想是大多数读取可以从缓存中提供。虽然测量和模拟表明日志结构文件系统在本地文件系统中还没有发挥出全部潜力,但核心思想与 Haystack 非常相关。照片被附加到 Haystack 存储中的物理卷文件,Haystack 缓存保护启用写操作的机器免受最近上传数据的请求率的影响。主要区别是:(a) Haystack Store 机器以这样一种方式写入数据,即一旦变为只读,它们就可以有效地提供读取服务;(b) 对旧数据的读取请求率随着时间的推移而降低。

一些著作提出了如何更有效地管理小文件和元数据。这些贡献的共同主题是如何智能地将相关文件和元数据分组在一起。Haystack 避免了这些问题,因为它在主存中维护元数据,用户经常批量上传相关照片。

 

基于对象的存储Haystack 的体系结构与 Gibson 等人在《网络连接安全磁盘》(NASD)中提出的对象存储系统有许多相似之处。Haystack 目录和存储可能分别与 NASD 的文件和存储管理器概念最相似,它们将逻辑存储单元与物理存储单元分开。在 OBFS 中,王等人构建了一个用户级的基于对象的文件系统,其大小是的 25 分之一。虽然 OBFS 获得了比 XFS 更大的写吞吐量,但它的读吞吐量(Haystack 的主要关注点)稍差。

 

管理元数据Weil 等人在 Ceph 中解决了扩展元数据管理,Ceph 是一个十亿字节规模的对象存储。Ceph 通过引入生成函数而不是显式映射,进一步将逻辑单元到物理单元的映射解耦。客户端可以计算适当的元数据,而不是查找它。在 Haystack 中实现这项技术仍然是未来的工作。Hendricks 等人观察到,传统的元数据预取算法对于对象存储不太有效,因为由唯一编号标识的相关对象缺少目录隐含的语义分组。他们的解决方案是将对象间的关系嵌入到对象 id 中。这个想法与 Haystack 正交,因为 Facebook 将这些语义关系明确地存储为社交图的一部分。在 Spyglass 中,Leung 等人提出了一种通过大规模存储系统的元数据进行快速和可扩展搜索的设计。Manber 和 Wu 还提出了一种搜索整个文件系统的方法。Patil  等人在 GIGA+ 中使用复杂的算法来管理与每个目录数十亿个文件相关联的元数据。我们设计了一个比许多现有工作更简单的解决方案,因为 Haystack 不需要提供搜索功能,也不需要传统的 UNIX 文件系统语义。

 

分布式文件系统Haystack 的逻辑卷概念类似于 Lee 和 Thekkath 的在 Petal 中的虚拟磁盘。Boxwood 探索使用高级数据结构作为存储的基础。虽然对更复杂的算法来说很有吸引力,但像 B 树这样的抽象可能不会对 Haystack 有意精简的界面和语义产生很大影响。同样,Sinfonia 的小型事务和 PNUTS 的数据库功能提供了比 Haystack 需要的更多的功能和更强的保证。Ghemawat 等人为主要由附加操作和大型顺序读取组成的工作负载设计了谷歌文件系统。Bigtable 为结构化数据提供了一个存储系统,并为谷歌的许多项目提供了类似数据库的功能。目前还不清楚这些功能在一个为照片存储而优化的系统中是否有意义。

 

 

6. 结论

本文描述了 Haystack,一个为 Facebook 的照片应用程序设计的对象存储系统。我们设计 Haystack 是为了满足在大型社交网络中共享照片时出现的 long tail 请求。关键是在访问元数据时要避免磁盘操作。与使用 NAS 设备的传统方法相比,Haystack 以显著更低的成本和更高的吞吐量为照片存储提供了一个容错和简单的解决方案。此外,Haystack 是可增量扩展的,这是我们的用户每周上传数亿张照片所必需的质量。