字节跳动基于Iceberg的海量特征存储实践
背景
字节跳动特征存储痛点
业务在线进行特征模块抽取; 抽取后的特征以行的格式存储在 HDFS,考虑到成本,此时不存储原始特征,只存抽取后的特征; 字节跳动自研的分布式框架会将存储的特征并发读取并解码发送给训练器; 训练器负责高速训练。
特征抽取周期长。在特征抽取上,当前采用的是在线抽取的方式。大量的算法工程师,每天都在进行大量的特征相关的试验。在当前的在线抽取模式下,如果有算法工程师想要调研一个新的特征,那么他首先需要定义特征的计算方式,等待在线模块的统一上线,然后需要等在线抽取的特征积累到一定的量级后才可以进行训练,从而判断这个特征是否有效果。这个过程通常需要2周甚至更长的时间。并且,如果发现特征的计算逻辑写错或想要更改计算逻辑,则需重复上述过程。在线特征抽取导致当前字节特征调研的效率非常低。基于当前的架构,离线特征调研的成本又非常高。
特征存储空间占用大。字节的特征存储当前是以行存的形式进行存储。如果基于当前的行存做特征调研,则需要基于原来的路径额外生成新的数据集。一方面需要额外的空间对新的数据集进行存储,另一方面还需要额外的计算资源去读取原来的全量数据生成新的数据,且很难做数据的管理和复用。行存对于特征存储来说,也很难进行优化,占用空间较大。
模型训练带宽大,数据读取有瓶颈。字节当前将每个业务线的绝大部分特征都存储在一个路径下,训练的时候会直接基于这个路径进行训练。对于每个模型,训练所需的特征是不一样的,每个业务线可能存有上万个特征,而大部分模型训练往往只需要几百个特征,但因为特征是以行存格式进行存储,所以训练时需要将上万特征全部读取后,再在内存中进行过滤,这就使得模型训练的带宽需求非常大,数据的读取成为了整个训练的瓶颈。
基于痛点的需求梳理
存储原始特征:由于在线特征抽取在特征调研上的低效率,我们期望能够存储原始特征; 离线调研能力:在原始特征的基础上,可以进行离线调研,从而提升特征调研效率; 支持特征回填:支持特征回填,在调研完成后,可以将历史数据全部刷上调研好的特征; 降低存储成本:充分利用数据分布的特殊性,降低存储成本,腾出资源来存储原始特征; 降低训练成本:训练时只读需要的特征,而非全量特征,降低训练成本; 提升训练速度:训练时尽量降低数据的拷贝和序列化反序列化开销。
字节跳动海量特征存储解决方案
在字节的整体架构中,最上层是业务层,包括抖音、头条、小说等字节绝大部分业务线;
其下我们通过平台层,给业务同学提供简单易用的 UI 和访问控制等功能;
在框架层,我们使用 Spark 作为特征处理框架(包括预处理和离线特征调研等),字节自研的 Primus 作为训练框架;
在格式层,我们选用 Parquet 作为文件格式,Iceberg 作为表格式;
最下层是调度器 Yarn & K8s 以及存储 HDFS。
技术选型
字节跳动海量特征存储解决方案
在并发写入方面,Iceberg 是采用乐观并发的方式,利用HDFS mv 的原子性语义保证只有一个能写入成功,而其他的并发写入会被检查是否有冲突,若没有冲突,则写入下一个 snapshot。
特征回填
写时复制
读时合并
MOR原理如上图,假设原来有一个逻辑 Dataframe 是由两个 Data File 构成, 现在需要回填一个 ColD 的内容。我们会写入一个包含 ColD 的 Update File,这样 Snapshot2 中的逻辑 Dataframe 就会包含ABCD 四列。
实现细节:
Data File 和 Update File 都需要一个主键,并且每个文件都需要按照主键排序,在这个例子中是 ID; 读取时,会根据用户选择的列,分析具体需要哪些 Update File 和 Data File; 根据 Data File 中主键的 min-max 值去选择与该 Data File 相对应的 Update File; MOR 整个过程是多个 Data File 和 Update File 多路归并的过程; 归并的顺序由 SEQ 来决定,SEQ 大的数据会覆盖 SEQ 小的数据。
COW 与 MOR 特性比较
相比于 COW 方式全量读取和写入所有列,MOR 的优势是只读取需要的列,也只写入更新的列,没有读写放大问题。在计算上节省了大量的资源,读写的 IO 也大大降低,相比 COW 方式每次 COW 都翻倍的情况, MOR 只需要存储新增列,也大大避免了存储资源浪费。
考虑到性能的开销,我们需要定期 Compaction,Compaction 是一个比较重的操作,和 COW 相当。但是 Compaction 是一个异步的过程,可以在多次 MOR 后进行一次 Compaction。那么一次 Compaction 的开销就可以摊销到多次 MOR 上,例如10次 COW 和10次 MOR + 1次 Compaction 相比,存储和读写成本都从原来的 10x 降到当前的 2x 。
MOR 的实现成本较高,但这可以通过良好的设计和大量的测试来解决。
而对于模型训练来说,由于大多数模型训练只需要自己的列,所以大量的线上模型都不需要走 MOR 的逻辑,可以说基本没有开销。而少数的调研模型,往往只需读自己的 Update File 而不用读其他的 Update File ,所以整体上读取的额外资源也并未增加太多。
训练优化
从行存改为 Iceberg 后,我们也在训练上也做了大量的优化。
在我们的原始架构中,分布式训练框架并不解析实际的数据内容,而是直接以行的形式把数据透传给训练器,训练器在内部进行反序列化、选列等操作。
原始架构
引入 Iceberg 后,我们要拿到选列带来的 CPU 和 IO 收益就需要将选列下推到存储层。最初为了保证下游训练器感知不到,我们在训练框架层面,将选列反序列化后,构造成原来的 ROW 格式,发送给下游训练器。相比原来,多了一层序列化反序列化的开销。
这就导致迁移到 Iceberg 后,整体训练速度反而变慢,资源也增加了。
向量化读取
为了达到最优效果,我们与训练器团队合作,直接修改了训练器内部,使训练器可以直接识别 Arrow 数据,这样我们就实现了从 Iceberg 到训练器端到端的 Arrow 格式打通,这样只需要在最开始反序列化为 Arrow ,后续的操作就完全基于 Arrow 进行,从而降低了序列化和反序列化开销,进一步提升训练速度,降低资源消耗。
优化收益
未来规划
未来,我们规划支持以下4种能力:
Upsert 的能力,支持用户的部分数据回流; 物化视图的能力,支持用户在常用的数据集上建立物化视图,提高读取效率; Data Skipping 能力,进一步优化数据排布,下推更多逻辑,进一步优化 IO 和计算资源; 基于 Arrow 的数据预处理能力,向用户提供良好的数据处理接口,同时将预处理提前预期,进一步加速后续的训练。
扫描下方二维码
抽开源中国周边啦~
往期推荐
VS Code带来全新代码编辑体验
英特尔推出 “按需购买” 计划,充钱可解锁更多CPU功能
“俄罗斯的谷歌”弃俄
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦~
微信扫码关注该文公众号作者