亚马逊AWS官方博客

一骑绝尘 – 如何在短时间里遍历 Amazon S3 亿级对象桶(工具篇)

在上一篇博客中,我们介绍了实现加速遍历 Amazon S3 存储桶的核心思路:并发 – 使用 Amazon S3 ListObjectsV2 API 的 start-after 参数来解耦顺序执行的调用,实现并发执行。

在这篇博客中,我们将介绍围绕这个核心思想来构建的 Amazon S3 存储桶快速遍历工具:s3-fast-list

基本特性包括了:

  1. 并发执行 ListObjectsV2 API(通过指定输入的 S3 前缀分段文件);
  2. list 模式下单边遍历一个 Amazon S3 存储桶;
  3. diff 模式下同时遍历两个 Amazon S3 存储桶,并根据对象的大小和 ETag 比较两边对象的一致性;
  4. 输出获取的对象元数据到 parquet 文件;
  5. diff 模式下同时输出标记对象差异的标志位;

工具目标

Amazon S3 的 ListObjectsV2 是一个非常常用的 API,只要你在使用 Amazon S3,你可能都有意无意地正在调用这个 API:

  • 比如,你的大数据工具在每次加载分区表里的数据之前,都会先通过 ListObjectsV2 获取分区表下的对象列表,随后再读取每个对象进行数据加载;
  • 又比如,你使用 Mountpoint for Amazon S3 把 Amazon S3 存储桶挂载到操作系统本地目录,使用文件接口访问 Amazon S3 上的数据时,每一次 ls 操作或者其他相关的文件系统列表操作,都会转换成 Amazon S3 的 ListObjectsV2 API 请求;
  • AWS CLI 里也已经实现了上层封装,你每次输入 aws s3 ls s3://YOUR-BUCKET/ 也都会被转换成 ListObjectsV2 API 请求;

在上一篇博客中我们也介绍了,我们希望工具使用的时候足够灵活,且能够支持高并发,进而可以用比较小的代价来实现亿级对象存储桶的快速遍历,并且能够导出遍历得到的对象元数据到文件以便后续使用。所以,在评估了现有的工具及其能力之后,我们认为还是需要围绕我们的目标场景,打造一个专门的工具。

节俭架构

re:Invent 2023 大会上 Werner 博士分享了 节俭架构师 的七条黄金法则,其中的第一条法则便是:把成本视作非功能性的需求

  • 核心要点:在设计架构的每一步都考虑成本因素在设计系统时,像安全、合规性和可访问性这样的事项是非功能性、不可谈判的要求。它们不会直接影响特定的特性或功能,但对系统的运行是必要的。Werner 博士在演讲中分享道:“组织也需要将成本和可持续性视为非功能性要求。如果成本不是设计过程的一部分,组织就有可能面临成本超过收入的风险。”

    通过在早期阶段思考并持续考虑成本的影响,我们可以针对性地设计系统,以实现特性、上市时间和效率之间的平衡。如此一来,开发团队可以专注于维护精益高效的代码。运营团队则可以微调资源使用和支出,以最大化盈利能力。

虽然 s3-fast-list 只是解决特定场景的小工具,但同样秉承着节俭架构师黄金法则的核心思想:

  1. 考虑工具的使用成本 – Consider cost at every step

保持最精简的软件架构: 单独的可执行程序,可以在任何场合快速运行,同时也提供了很高的并发性能。

  1. 通过可观测性衡量效率 – Define your meter

定义了内部可观测性指标:在高并发下,追踪 API 请求的返回并进行错误处理,保障每个请求都按预期完成。

  1. 持续优化 – Continuously optimize

在 re:Invent 2023 中亚马逊云科技发布了全新的 Amazon S3 Express One Zone,并推出了全新的存储桶类型 Directory Buckets,ListObjectsV2 API 在 Directory Buckets 上有着不一样的使用特性,我们后续会在工具中支持这个新的类型的存储桶。

构建一亿个对象的 Amazon S3 存储桶

为了验证工具的执行效率,我们使用 github 2023 年 12 月的历史归档事件作为数据源,来构建一个拥有一亿个对象的 Amazon S3 存储桶。

我们把一亿条事件按以下格式进行了重组:

Object Key:     {repository.name}/{org.login}/{actor.login}/{created_at}/{type}.{id}
Object Content: {payload}

更详细的 github 事件定义可以参考官方文档

上传数据集以后,在目标桶里,我们看到的一亿个对象的前 20 条如下:

$ aws s3 ls s3://YOUR-BUCKET/ --recursive
2023-12-31 20:53:25        537 0--key/archpy/0--key/2023-12-15/PushEvent.34204226999
2023-12-31 18:00:37        530 0--key/lib/0--key/2023-12-06/PushEvent.33932217935
2023-12-31 18:02:16        494 0--key/lib/0--key/2023-12-06/PushEvent.33935138253
2023-12-31 18:05:50       1120 0--key/lib/0--key/2023-12-06/PushEvent.33941367447
2023-12-31 18:12:10       1118 0--key/lib/0--key/2023-12-06/PushEvent.33952164660
2023-12-31 18:29:29       1437 0--key/lib/0--key/2023-12-07/PushEvent.33974709111
2023-12-31 19:02:01       2561 0--key/lib/0--key/2023-12-08/PushEvent.34022772751
2023-12-31 19:20:28        822 0--key/lib/0--key/2023-12-09/PushEvent.34039760201
2023-12-31 19:20:30        518 0--key/lib/0--key/2023-12-09/PushEvent.34039793058
2023-12-31 19:32:12        517 0--key/lib/0--key/2023-12-10/PushEvent.34049909591
2023-12-31 19:32:32        839 0--key/lib/0--key/2023-12-10/PushEvent.34050166903
2023-12-31 19:50:29       1121 0--key/lib/0--key/2023-12-11/PushEvent.34078374155
2023-12-31 19:50:32        520 0--key/lib/0--key/2023-12-11/PushEvent.34078474255
2023-12-31 20:03:13        810 0--key/lib/0--key/2023-12-12/PushEvent.34100403879
2023-12-31 20:33:53       2553 0--key/lib/0--key/2023-12-14/PushEvent.34164389981
2023-12-31 20:40:38        812 0--key/lib/0--key/2023-12-14/PushEvent.34179803354
2023-12-31 21:08:07       4036 0--key/lib/0--key/2023-12-16/PushEvent.34221794929
2023-12-31 21:08:16        505 0--key/lib/0--key/2023-12-16/PushEvent.34221920638
2023-12-31 18:00:40        816 0--key/org-pub/0--key/2023-12-06/PushEvent.33932304420
2023-12-31 18:01:01        514 0--key/org-pub/0--key/2023-12-06/PushEvent.33932940600

从这个桶的监控指标中,我们可以确认桶里的对象数量为一亿个。

并行执行

在我们构建的测试桶上,我们验证了不同并发度下,遍历一亿个对象所需的时间,结果如下:

并发度 开始时间 结束时间 时间(秒)
1 1 2024-01-01T05:06:38Z 2024-01-01T07:23:32Z 8214
2 10 2024-01-01T09:23:14Z 2024-01-01T09:38:38Z 924
3 100 2024-01-01T09:47:50Z 2024-01-01T09:49:32Z 102
4 1000 2024-01-01T10:17:59Z 2024-01-01T10:18:31Z 32

说明:

  • 所有的测试都在同一台 C6i.8xlarge Amazon EC2 实例上完成;
  • 开始和结束时间以对应测试并发度下 ListObjectsV2 API 请求的整体开始和结束时间为准;
  • 一亿个对象被拆成了 1000 个前缀的分段;
  • 并发度 1000 的测试中,操作系统 CPU 利用率在 95% 左右;

测试结果符合我们的预期,通过提升 ListObjectsV2 的执行并发度可以加速完成 Amazon S3 存储桶的遍历时间,遍历所需的时间从完全顺序执行的 2 个多小时缩短到 1000 个并发的 32 秒钟。

费用计算

由于每次 ListObjectsV2 API 最多返回 1000 个对象,并发遍历与顺序执行相比,只是单纯提高了执行的并发度,每次 API 调用返回的对象数基本仍然饱和在 1000 个,所以从整体费用上来看,并发遍历与顺序遍历相比并不会带来额外的费用开销,以亚马逊云科技 us-east-1 区域上每 1000 个 LIST 请求 $0.005 价格来计算:

遍历一亿个对象所需的请求数 = 100,000,000 对象 / 每次 1000 个对象 = 100,000 次 LIST 请求
遍历一亿个对象所需的总费用 = 遍历一亿个对象所需的请求数 * $0.005 / 1000 = $0.5

使用场景

秒级遍历亿级 Amazon S3 对象桶基本已经达到了近实时的效果,可以满足绝大部分应用场景的需求。

让我们再回顾一下上篇博客中列举的一些典型场景:

  1. 数据迁移上云

数据迁移的场景里, 在迁移过程中、迁移完成后,我们通常希望尽快掌握已经完成数据迁移的对象列表,以准确评估数据迁移的进展,为后续操作提供决策依据。

  1. 跨桶或跨区域数据复制

Amazon S3 提供了原生的数据复制特性 S3 Replication,可以实现数据在同区域或跨区域之间进行复制。那些被 S3 Replication 规则覆盖的对象可以很放心的交由 S3 的 Replication 来进行纳管实现复制,但同时,我们也需要一些方法来及时发现那些复制规则没有覆盖到的对象,并形成列表,进行后续是否需要进行复制的判断。

  1. 基础架构管理

许多团队中,基础设施运维与业务应用在工作界面上相互分开。作为使用方,业务应用部门负责构建应用,但在资源使用,销毁等流程上依赖基础设施运维部门来具体执行。而运维部门很难准确把握业务应用在 Amazon S3 上的具体使用细节,在大体量数据的管理中,难免出现一些对象最终变成了管理上的“孤儿”。及时获取对象的详细列表有助于运维和应用部门在资源的管理上达成统一的对话平面。

在以上这些场景中,通过并发执行,遍历效率得到了提升,为客户节省了大量的等待时间,加速了 IT 流程的迭代周期。

结论:从小时级到秒级 – 时间也是成本的维度

回到最开始我们讲到的节俭架构,Werner 博士在节俭架构师的七条黄金法则中反复提到了“Cost” – 成本。

如果时间也是我们度量成本的维度,那么 s3-fast-list 给客户带来的量化成本增益是:

8214 秒 / 32 秒 = 256.68 倍

但是,相信大家都明白一个道理: 时间是无价的。

本篇作者

戴逸洋

AWS 数据存储资深架构师,致力于推广存储技术在云上的各种最佳实践。

方浩

AWS Startup 团队解决方案架构师,致力于云计算、大数据技术在初创企业中的推广。拥有近二十年的 IT 从业经验。加入 AWS 之前,在驻云科技担任高级解决方案架构师和大数据架构师。更早以前,在多家创业公司担任分公司经理一职。

陈超

亚马逊云科技资深解决方案架构师,主要负责迁移相关的技术支持工作,同时致力于亚马逊云服务在国内的应用及推广。加入亚马逊云科技之前,曾在阿里巴巴工作 8 年,历任研发工程师、云计算解决方案架构师等,熟悉传统企业 IT、互联网架构,在企业应用架构方面有多年实践经验。

李佳

亚马逊云科技快速原型解决方案研发架构师,主要负责微服务与容器原型设计与研发。