Alluxio在小红书的实践:加速云端机器学习
分享嘉宾
李亚斌
小红书大数据技术专家
负责小红书多云统一数据加速层的建设
关于小红书
小红书是年轻人的生活记录、分享平台,用户可以通过短视频、图文等形式记录生活点滴,分享生活方式。
分享提纲
本文主要介绍小红书多云统一数据加速层的内容,主要内容包括以下几个部分:
- 小红书在复杂的多云环境下面临的挑战;
- 如何通过构建多云统一数据加速层来解决这些技术问题;
- 结合小红书的具体实践案例,介绍多云统一数据加速层是如何解决这些问题的;
- 未来规划。
观看完整分享
面临的挑战
我们先看看小红书的一些业务都碰到了哪些挑战。小红书作为云上的原住民,从成立之初就构建在公有云上,下图是小红书多云架构的示意图。
图中的三个圈代表两个云厂商的不同 Zone(区域),云厂商1是在 ZoneA 与 ZoneB,云厂商2是在 ZoneC。核心业务分别分布在多个云厂商的不同可用区,核心业务如搜广推、社区通常在主要机房都会存在,是多活架构;主要业务如电商直播及部分大数据服务,是双活架构;其他如训练等服务,是单活架构,这个只是个简化后的示意图,真实情况远比这复杂。多云架构对小红书带来了非常明显的成本优势和可用性优势,但业务的通信链路也随之复杂,各种跨专线调用;还有个特点是不同 region 之间 rt 差异比较大,且专线容量非常稀缺,因此带来了一些业务使用上的痛点。
多云架构下有哪些典型的问题
- 机器学习训练在小红书是资源大户,属于公司 Top 级别,但海量的 CPU 和 GPU 资源的利用率不及预期,训练速度上不去,业务体感比较差。
- 推荐服务是小红书最核心的服务之一。它的核心逻辑是推荐召回需要有索引分发的过程,比如刷小红书刷到的个性化的笔记列表,就主要用到了索引。索引服务因为索引分发慢,从而导致稳定性比较差,且因为索引数据存储在云盘里,成本也很高。
- 在AI场景下,有业务面临 60 亿+的元信息小文件场景,需要有一个低成本的解决方案。而且随着AI的飞速发展, AI 模型从之前的百 GB 级发展到如今的 TB 级别。原来的架构通常先把模型拉到本地盘,再从本地盘加载到内存,才可以进行在线推理。这个过程在模型增大到 TB 级之后,会有两个严重的问题:一个是加载过程非常缓慢,另一个是模型存储在本地盘的成本非常高昂。
- 多云架构本身带来的网络链路复杂,跨专线传输数据量非常大,专线单价也是非常高昂,有成本和稳定性的双重挑战。
多云数据加速层
接下来介绍下我们是如何通过构建多云统一数据加速层来解决这些痛点的。
上图是小红书业务架构的示意图。最上层是业务层,主要包括社区、搜广推、直播、电商这些核心服务。接下来是平台层,这里只列了一些和数据强相关的,如机器学习平台、 AI 训练平台,模型发布平台、推荐索引平台、数据平台等。最底层是公有云的存储产品,这里只列了我们主要依赖的对象存储,其实包括异构的 HDFS 等产品都可以适用于这个通用的解决方案。在平台层和存储层之间,我们基于 Alluxio 构建了一层多云统一数据加速层并研发了对应的智能缓存服务。
为什么选择 Alluxio 作为多云统一的加速层
首先我们基于面临的问题,抽象出了选型的重点目标:
- 需要能够复用业务历史上已经存储的数据,不需要再次搬迁或者导入到其他高速存储或文件系统就能享受到数据的加速。以典型的搜广推和训练业务为例,这些业务的存储量大概是数百PB级别,搬迁一遍才能使用不太可行。
- 需要支持 S3 和 POSIX 协议,以便于与其他平台无缝对接。如机器学习训练通常是 S3 协议, AI 训练通常是 POSIX 协议,如果加速层能够兼容这类协议,业务方就不需要改代码,迁移成本也会低很多。
- 需要实现跨云专线传输的带宽控制和节省。因为跨云本身业务是多活、多云的。但多云之间本身就有实时的数据同步,如果我们把带宽打爆,那稳定性问题就会立马出来,所以我们一定要能够控制住带宽。
- 能够支撑百亿级别的 AI 训练,我们有单个训练任务就有60亿+元信息的场景需要支持。
- 能够支持常见的云存储产品,以主流云厂商的对象存储为主。
经过调研,我们发现业界目前唯一能同时满足上述诉求的产品,就是 Alluxio。
首先简要介绍下 Alluxio 架构。
Alluxio 主要有五部分模块:
- Master:主要负责元信息的服务,维护了文件及其Block的位置信息,并负责处理元信息请求;
- Worker:主要负责数据的缓存以及读取;
- Job Master:主要负责一些异步任务的管理,比如加载缓存、淘汰缓存等这些异步的任务的下发及管理;
- Job Worker:主要是负责 Job Master 下发任务的具体执行,其执行状态会在 Job Master 进行维护;
- Alluxio Client:会通过 Master 去拿文件的元信息,之后会通过元信息知道这个文件的对应位置的数据会在哪个 worker 上存储,接着就可以去对应的 worker 取数据。如果取数据的时候发现这个数据并不在Alluxio缓存里,就会去Under Store 取数据( Under Store 是对底层存储的统称,此案例里主要指对象存储),取下来之后响应给客户端,同时 Alluxio 会把这个数据缓存下来,当再次读取同样数据的时候就能够命中缓存了,这是非常重要的特性。
对于S3协议,会有个专门的 Proxy 做代理,来兼容S3协议。该架构图是2.x的架构,而 Alluxio 最新 3.x 架构已经升级为去中心化的元信息架构,从而大大扩展了元信息的支持能力,这对 AI 训练是非常有帮助的。
Alluxio主要特性
- 特性一:格式透明,不侵入业务数据存储格式。我们数据湖里的数据量非常大,是不可能再导入进来重复存储的,有了Alluxio,只需要在原来存储上加一层Alluxio,就可以直接去访问那些数据了,让业务直接享受到加速收益,这是非常关键的特性。
- 特性二:协议兼容。Alluxio 支持非常丰富的S3\POSIX\HDFS\Java File API\REST API等协议,帮助Alluxio上层AI/ML训练引擎(如Pytorch、Ray、TensorFlow)和查询引擎(如Presto、Spark、Trino)与底层存储进行无缝对接。
- 特性三:多云统一视图。不管底层存储是HDFS、Ceph还是各云厂商的对象存储,对于用户,只要通过Alluxio,任何API都可以去访问不同存储位置的数据,从而可以实现多云统一视图的效果。
- 特性四:数据仅需通过专线传输一次,后续可通过缓存就近读取,不需要再次跨专线,这个关键特性对我们专线的保护意义重大。通过合理地利用这些特性,就能够解决上述提到的小红书多云架构的业务痛点。
通过合理地利用这些特性,就能够解决上述提到的小红书多云架构的业务痛点。
小红书实践案例
机器学习训练场景
主要问题:
- 训练速度不太符合预期,导致一些任务训练很慢,以及其他人排队调度不上,体验很差。
- 海量的集群资源利用率太低对成本也带来了很大挑战。主要原因是一些热点的数据集(如小红书的笔记样本),是公共的样本,总量非常大(大概每天几百TB)。这些公共数据集会被很多任务使用,在我们的场景下大概是 20 倍的扇出,如果是几百TB的数据会有 20 倍的扇出,这个总传输数据量是非常大的,整体流量达到了Tbps级,触达到了对象存储桶的带宽瓶颈。如果数据集大、扇出也大的话,一定会有额外的带宽需要,云厂商的解决方案通常是要么增加存储量,要么对增加带宽进行额外收费,两种方式都不太友好。
- 因为业务会直连对象存储,而对象存储本身是高吞吐的产品,并不会过分强调单线程有多快,这就需要业务不断的去调优,才能达到适合的吞吐。
基于以上三个问题,机器学习训练架构经过了升级改造,最新架构如下图所示。
新架构对于普通数据还是直接会去对象存储读取,对于笔记这种热点的训练数据集,我们会把它缓存到 Alluxio,当业务来 Alluxio 访问的时候,如果第一次数据不存在,就会去对象存储透传,然后把数据返回给训练框架,如果数据已经在Alluxio 上,那就可以命中缓存,直接由 Alluxio 返回数据。
虽然第一遍读完数据,Alluxio 一定会去缓存数据,但这还很难解决业务的问题。
第一种情况是 Alluxio 缓存是用到本地磁盘把数据缓存下来,但磁盘容量是有限的,如果总训练的样本空间远大于磁盘缓存容量,就会不断的淘汰缓存数据,可能第一个任务缓存了,第二个任务就没有空间缓存了,或是会把别的缓存数据给挤掉。
第二种情况是有些训练任务可能因为计算资源或者故障的原因带来严重的延迟,之后这个业务一旦跑上来,可能需要训练 30 天之前的数据,或者直接回溯很老的数据去训练,那这 30 天的数据很容易就把所有的缓存空间全部用掉。
以上两种场景我们通过研发了智能缓存管理服务来解决。
智能存储管理服务
智能缓存管理服务主要是基于任务的历史运行规律,可以基于任务的历史运行规律,更加智能的把数据提前做预加载,这样通常第一次训练就能够命中缓存,而且可以更及时地淘汰掉不使用的缓存。不仅如此,我们还对缓存淘汰场景进行了评估,比如发现最近 1-2 天的笔记训练样本是非常重要的,我们会把这些数据用 Pin 机制固定在磁盘里,就不会被自动淘汰,从而实现重要数据的淘汰完全由智能缓存管理服务去控制。通过这两个措施的共同保障,我们的缓存命中率能跑到90%以上,给对象存储的带宽带来了非常好的节省。
同时,Alluxio 也提供了load任务的管理和执行能力,但目前还不完全符合我们的需求。我们需要监控到任务粒度的 load 进展,比如有 10 个任务(有几个是重要的),在按小时提前预加载数据,结果集群故障了,但故障时间也较长,第二天马上又要用新的样本去训练数据,那我们该如何止损呢?措施是通过实现 load 进度的可观测机制,能够观察到每个任务当前正在加载第几小时的数据。当在不及预期的时候,会马上发出告警并做止损。当止损的时候,会基于任务的优先级去判断优先补偿哪些任务,并提供一键补偿的能力,看看这些任务已经错过了哪些小时的缓存数据,然后全部加载进来,从而规避带宽全部透传到对象存储所造成的的稳定性风险。
第三是我们为稳定性实现了一个探针服务,它可以完全模拟业务的读写行为,是一个端到端的探活服务。探活实践下来非常好用,无论是我们代码本身有问题,还是机器磁盘、网络等出现问题,都能反映在探针里,方便我们快速介入。目前我们能达到3分钟发现和1分钟止损的效率。经过将近一年时间的运行,故障告警的准确率几乎达到了100%。
训练速度提升效果
上图展示的是一个非常典型的笔记训练大任务,其他业务训练效果都差不多。在迁移之前,训练时长达到9小时36分,单一任务就需要消耗2000核,非常消耗资源,而平均 CPU 利用率只有30%,只是到了最后面会有一些上升,这是因为这时候大部分任务已经训练完成,对象存储的带宽有些缓解了。在迁移到 Alluxio 之后,训练时长直接缩减到了 5 小时 42 分,从图中可以看到,CPU 利用率可以非常均匀的维持在75%,并且再也没有被限流影响到。整体训练速度在时长上优化了41%,虽然很多业务比这个效果更好,但这个例子是一个非常典型的大任务,更具有代表性。
推荐召回索引下载场景
索引对于推荐非常核心,稳定性是非常重要的问题。上图是未引入 Alluxio 之前的架构图。最上面是搜推平台,会对搜推的索引做一些生成或者更新,更新完了之后会存储到索引存储(一般是就近机房的对象存储)。存储索引之后,搜推平台会通知其他服务下发索引,下发索引的服务会把数据通过专线从原来索引存储的对象存储桶传输到另外一个多云机房的本地磁盘,也就是索引服务的本地磁盘上。以图中的架构为例,共有两个跨区域的机房,当把索引数据下载到本地盘后索引服务就能够把数据加载到内存中,对外提供一些索引的服务。
这样的架构带来的问题很大:
- 以推荐场景下的索引读取速度来说,通常发布一个机房的服务需要 3-4 个小时,因为是多活设计,发布完四个机房整整需要一整天,这是非常令人头疼的问题。同样,当单机房发生故障,止损时长同样也需要 3-4 小时,如果你要把很老的一个索引回滚,就需要重新走这个流程,四个机房就需要一天时间。
- 磁盘存储成本非常高。因为索引服务本身是一个主从架构,典型的场景是一主两从。同一个索引会有三副本的云盘存储,而云盘本身就是三副本冗余存储,那整体下来就是九副本,而云盘的单价通常比对象存储贵得多,这样计算下来整体成本是非常高的。
下图是我们引入 Alluxio 之后的架构。从搜推平台生成索引之后,我们会把这个事件发送到消息队列,智能缓存管理服务订阅了该消息队列,收到相应的加载缓存事件后,就会去加载索引。现在的加载流程和之前就不一样了,之前是通过专线传输过来存到本地磁盘,现在是加载到 Alluxio 集群里,然后再告知索引服务,去 Alluxio 集群把数据再加载到索引服务的内存,从而对外进行服务。这里的关键点是把本地磁盘变成了 Alluxio 集群,那为什么采用 Alluxio 之后解决了以上问题呢?
首先,我们把磁盘上浮到了一个远程的集群,实现了索引的存算分离,把原来来自云盘的存储瓶颈,转换成了宿主机的网络瓶颈。云盘的带宽通常在云厂商相对还比较好的规格是 350 MB/s,但是宿主机不一样,我们推荐用的一些机器至少都是 32 Gbps 以上,合4GB/s,这两者的差距超过10 倍,因此下载索引数据这个过程就能提速10倍以上。
第二,Alluxio 并不会把文件存储在固定的一台机器上(和本地盘不一样),一个文件会被切成 block 存储。比如一个集群有 100 台机器,一个文件能切 100 个block,那每个机器只会存1个block,这时候整个集群的带宽就是这个文件的吞吐极限。所以,对于一些热点的索引文件或者其他场景都是非常友好的。
但这样直接用 Alluxio 还是会遇到问题,所以我们还加入了一些增强的功能,这也是为什么引入了智能缓存管理服务的原因。比如 load 太快会把专线打爆,我们需要 Alluxio 支持限速,以保障核心服务的稳定性。现在已经支持了限速,当我们限制20-30Gbps的带宽从 UFS 下载数据,就不会把专线带宽占满。
这套架构主要有三点收益:
- Alluxio 将云盘带宽瓶颈变成了宿主机的网卡瓶颈,索引拉取速度带来 10 倍以上的提升。如果宿主机的核数越大,附带的带宽也会更大,带来的提升倍数还会增大。
- 索引下发的整体业务体感(含业务逻辑)达到 3 倍的提升。主要是因为除了下载,还有一些业务逻辑在这个索引下发的过程里。
- 高性能云盘替换成对象存储,节省80%成本。此优化是通过 Alluxio 把云盘全部省掉,变成了Alluxio 的集群本地盘,而这些本地盘可以选择一些更廉价的介质,比如单副本的 HDD 盘。对于小红书的推荐场景,由于索引数据量很大,云盘成本的节省量也是非常可观的。
大模型下载场景
我们也有大模型的场景,大模型下载的解决方案和推荐索引几乎一模一样,面临的同样是云盘带宽限制和冗余存储高成本以及跨云同步稳定性问题。3-4年前大家通常只做单机训练,现在已经演进到了几乎都是分布式训练的状态。那一定会需要通过远端的存储集群来解决本地数据存放不下的问题,这块架构与收益跟推荐索引一样,就不赘述了。
AI 训练场景面对的挑战:
- 有 60 亿+级别的小文件训练场景,如果把这些元信息存储在一个集中式的元信息服务里,成本非常高,本身技术挑战也很大。
- 对象存储作为很多存储的底座,最终数据会穿透到对象存储,会面临着存储带宽,或是 IOPS 比较有限的情况(尤其是小文件),云厂商通常一个桶提供几万 IOPS,每PB存储量的带宽配额也很低,尤其在小文件场景下,这点 IOPS 承载不了多少访问,因此 GPU 利用率就会很低。
解决办法:
- 引入 Alluxio 缓存训练需要的数据。Alluxio 3.0 版本提供了一个可扩展的元信息能力,让扩展性大幅提升。此外,Alluxio 本身支持 POSIX 协议,之前如果训练是在本地盘上,现在会把 Alluxio 集群 mount 到 GPU 机器上,由于 Alluxio 是透明嵌入,让业务方看其实还是访问的本地盘,背后其实是 Alluxio 集群。然后,通过 Alluxio 集群去对象存储里取数据,基于 Alluxio 的缓存机制,可以有效的避免数据穿透到对象存储。因为通常 AI 训练是多轮的 epoch,Alluxio 只会让第一轮 epoch 走对象存储,后面都可以命中这些缓存。甚至理想情况下,可以错峰把这些数据预加载到 Alluxio ,这样真正训练的时候,完全不需要走对象存储。
- 因为 GPU 机器上很多磁盘是闲置的,为了避免额外的支出成本,我们会把 Alluxio 和 GPU 机器混合部署,让这些磁盘都可以被充分使用,也不会有额外的成本支出。
- Alluxio 相对于别的产品打造的非常成熟的能力是ClusterCache。在同样的总磁盘容量,ClusterCache 相对于Local Cache的命中率可以做到更高,比如有两个训练任务读同样的文件,如果落在两个不同的机器上,对于 Local Cache 会把数据读两遍,而对于 ClusterCache 只需要读一遍,第二次可以通过网络来传输,而 GPU 机器之间的网络带宽也常非常高,通常有百Gbps,同时 Alluxio 也支持 LocalCache,组合起来使用更佳。
Alluxio 为什么能加速 AI 训练
- 可以在业务训练之前提前把数据加载到缓存中,突破IO限制,相比穿透对象存储读取性能更高;
- 读取数据时通过智能判定是随机读还是顺序读。如果是顺序读可以提前预读数据,从而达到更高的读吞吐;如果是随机读,可以精准地控制要读哪个位置,避免读的放大;
- 无集中式的元信息服务,全量元信息在对象存储,只有热数据及其元数据在缓存中,对海量小文件友好,相比于集中式元信息服务,成本极低;
- 可异步写 checkpoint 到本地磁盘,再异步上传至对象存储,通过有效缩短 IO 负载时长,从而大幅缩短GPU 等待时间,并显著提升 GPU 利用率。
技术问题及解法
在与 Alluxio 的合作过程中,我们也一起参与解决了非常多的技术问题,这里有几个比较典型的场景:
读放大问题解决:
主要表现在 S3 协议里会有一些 range 读,以及 Alluxio client、fuse 或者其他一些触发随机读的场景。放大主要存在两个环节:一个是 S3 proxy 到 worker 之间;另一个是worker 去对象存储(UFS)取数据的时候,都会有一个读放大的情况。比如一个数据是热读(指数据缓存已经在 worker里),原来的实现里 proxy 会直接去 worker 取,虽然S3协议里的 range 参数已经指明了截止位,但并没有透传过去。因为这里可能认为需要做一些预加载来加速后续的读取,实际上业务如果指定了一个 endOffset,后面的数据则是没必要读取的。虽然预读能帮助吞吐的提升,但在这种情况下后面的数据会被完全废掉,反而会转化成带宽的放大。——解决办法:worker 传输数据当传输到 endOffset,后面的字节不需要传输过来,这样是更经济,更高效的办法。
第二个放大的问题是因为 Alluxio 初衷在读数据时,会把需读取数据范围涉及的 block 全部缓存下来,好处是之后再读这些数据就不需要走带宽了。比如在一个随机读的场景,在一个block 里只需要 1-2M 数据,但Alluxio会缓存整个 block 的大小(默认为 64M)。——解决办法:如果是非缓存block的数据请求,因为协议中本身传了 endOffset,需要将其作为访问对象存储的参数,只需要读取必要的数据即可。endOffset 之后的数据本身也会被丢弃掉,读出来就会变成 worker 机器上网络带宽的开销,从而影响成本和效果。
第三个是冷读 NoCache 的场景。NoCache 是指经过 Alluxio读取但不缓存对应的数据,通常发生在对于延迟太久的任务或者读取大量冷数据的任务,我们不需要将其缓存下来,否则会将本身的热数据给挤掉。——解决办法:以前的实现里,通过 S3 proxy 直接 NoCache 读,它会通过 worker 去UFS取数据。修改和打磨之后,Proxy 会绕过 worker 直接去 UFS 取数据。这样可以节省 worker 传输到 proxy 的带宽,可以省一倍的带宽,对应到机器成本上就是省一倍的机器成本。
专线带宽限流:
我们在 UFS 这一层增加了流控能力,保护了专线带宽。在多云环境,业务通常会就近读取,一定不会跨专线访问 Alluxio集群,所有跨云专线的流量只有从 worker 到 UFS 这一层,所以我们只需要在访问 UFS 的地方加限流就可以控制住专线。而客户端和 S3 Proxy 的交互,以及 S3 Proxy 与 worker的交互都是同机房的一个流量,理论上也需要保护,但不影响专线。
读写性能优化:
在读性能优化方面,通常是识别了读的特征之后做预读,通过预读能够明显提升读的性能,尤其是在冷读数据的情况下。在Checkpoint写方面,一定不能阻塞训练,所以支持了WriteBack 能力,WriteBack 是指先异步写到磁盘甚至内存中,然后就可以开始后续训练,通过后台线程异步上传。同样也有一些线程模型的优化,整体对读写的性能也有非常大的提升。
未来规划
未来我们会把数据加速层做成什么样子以及还有哪些待解决的问题呢?
打造统一的多云数据存储产品
因为我们数据存储在多云里,业务需要关心数据到底在哪个云上,是不是会跨专线,到底要用哪个 SDK 去读取,报错了都是什么原因,该去找谁,业务感知的东西非常多。我们期望打造一个统一的多云数据存储产品,让业务方再也不需要在代码中关注数据到底在哪里,专线能否控制好等问题。
AI训练:多地域GPU利用率提升
在 AI 训练场景,因为 GPU 众所周知的供应问题,从各个厂商购买或租赁的 GPU 是分散在非常多的地域。这会造成一个非常严重的问题,有些地方有 GPU,但没数据,有些地方有数据,但没有 GPU,这会导致 GPU 的分配率不高。后面会探索如何基于 Alluxio 来提升 GPU 利用率,解决数据和 GPU 在不同地域如何充分利用 GPU 的问题。
大数据查询加速
大数据查询加速在 Alluxio 社区里的案例非常多,但我们需要探索出一个如何在极低成本的情况下去实现大数据查询的加速。同样的查询效率提升,需要增加的 Alluxio 的成本要小于直接扩容查询引擎节点的成本才行。
低效节点资源利用率提升
现在 Alluxio 集群规模比较大,与此同时, CPU 利用率是非常低的,每天平均大概3%的利用率,但磁盘容量和带宽全是占满的。如何将这些 CPU 去充分使用是一个非常大的问题,而公司出于成本考虑,也会要求这些低效节点能够被充分利用,从而发挥更多价值。