游乐游手机版
首页/AI热点日报/热点详情

Iceberg助力B站商业化模型样本行级更新实践

类型:热点整理2026-07-02
B站基于Iceberg提出“UpdateonColumn”方案,通过轻量化列级更新与分支独立Schema,解决行级更新中冗余存储和资源消耗问题。该方案将模型训练效率提升33%,显著优化特征调研与Label更新场景,实现高效数据管理。

B站如何利用Iceberg实现商业化模型的高效更新?深度解析其技术演进与实践效果。

先抛几个核心判断。在广告业务和特征调研这些场景里,数据更新效率往往是模型迭代的瓶颈。传统方案要么冗余存储严重,要么资源消耗巨大。B站基于Iceberg提出的“Update on Column”方案,通过轻量化的列级更新和分支独立Schema,把模型训练效率提升了33%。这事儿值得仔细拆解。

lceberg 助力 B 站商业化模型样本行级更新的实践

导读 本次分享重点讲解了 Iceberg 的业务应用,涵盖特征调研、更新场景支持及性能兼顾等实际应用,包括在 branch 上支持独立 schema 的实现,优化特征调研流程;通过 ColumnFile 实现灵活高效的更新场景;以及宽表治理与数据回刷方案,显著提升了数据管理和使用的灵活性,为复杂业务需求提供了高效解决方案。

主要介绍以下五个部分:

1. 从交互式分析到多场景探索的技术演进
2. 行级更新的业务背景
3. Update on Column 的技术设计与思考
4. 业务落地与实践效果
5. 未来规划

(原文中的嘉宾、编辑、社区等信息已按规则删除。)

从交互式分析到多场景探索的技术演进

先看看哔哩哔哩湖仓一体团队过去四年的关键技术路径。时间线上的蓝色节点显示,最初三年技术重心集中在交互式分析方向。围绕这个目标,做了几件关键的事:

  • Z-order 排序:通过优化文件布局,显著提升查询性能。
  • 索引构建:Bitmap 索引和 Bloom Filter 索引,精准命中高频查询。
  • 预计算:针对单表高频聚合查询做了优化。比如在 Trino 引擎中,某个高度聚合的查询通过预计算将单天的读取数据量减少了 316TB。星型模型的预计算建模在 star schema benchmark 上也有明显提升。

这三项工作的落地,标志着 Iceberg 在哔哩哔哩的初步探索取得了阶段性的成果。

从 2022 年开始,工作重心向智能化数据管理迈进,同时进一步优化交互式分析的基础能力。具体来说:

  • 智能数据管理平台:为复杂数据场景提供便捷管理,极大简化业务方对 Iceberg 的使用。

  • Bloom Range Filter 索引:解决了 Bitmap 索引在高基数场景下的存储问题,进一步优化了查询性能。

到了 2023 年,完成了哔哩哔哩日志数据的全面入湖,针对日志查询的特点做了针对性优化:

  • Token Index 和动态过滤器:针对日志查询场景的特点,结合 Iceberg 的统计信息,提升了 Top N 查询的效率。

  • CDC 入湖:支持日志数据的实时入湖操作,进一步拓展了 Iceberg 的适用场景。

这些技术的落地,让交互式分析的基础能力相对完善,也为后续非交互式分析领域的拓展奠定了坚实基础。

行级更新的业务背景

在大数据领域,行级更新一直是个复杂的技术挑战。但业务需求摆在那里,必须设计合理的技术解决方案。

1. 行级更新的业务背景

在讨论技术方案之前,先简单介绍一下业务背景。任何技术的诞生和发展都离不开业务驱动。行级更新方面主要有两个场景。

场景一:Label 更新

广告业务中,网页右下角的广告模块会生成两类核心数据:

  • 曝光数据:用户看到广告时记录的数据。
  • 点击数据:用户点击广告卡片时生成的数据。

业务处理架构基于 Flink 双流 join 窗口,窗口时间为 25 分钟,实时落地到一张数据表。用户点击广告卡片后,可能会触发后续行为(如激活或购买),这些数据生成时间往往超过 25 分钟。为了把后续行为数据关联到之前的曝光和点击表,需要通过 Spark 进行小时级的关联处理,对曝光和点击数据进行关联并更新写入。目前主流技术方案是采用 overwrite 方式更新目标表。但这种方式存在两个问题:

  • 冗余存储:每次更新都会重新覆盖整个目标表,导致存储冗余。
  • 资源耗费大:大规模数据写入消耗大量资源,更新过程耗时较长。
场景二:特征调研

特征调研的目标是分析新增特征对现有模型的影响。特征可以理解为表中的列字段。当需要新增特征时,通常的做法是:

  • 创建一张新表,并在其中添加新字段。
  • 将原表数据拷贝到新表,同时追加新特征字段。
  • 对历史数据进行回刷,通常要覆盖过去半年时间的数据。

这个方案虽然常见,问题也很明显:

  • 存储冗余:每新增一次特征,就创建一张新表,存储开销大幅增加。
  • 资源与稳定性问题:历史数据回刷耗时长、资源消耗高,大规模数据回刷容易失败。

2. Iceberg 原生技术方案的问题

(1)Branch 的局限性
Iceberg 中的 Branch 被定义为 snapshot reference,即指向某个可引用的快照。由于 Branch 没有独立的 Schema 配置能力,在特征调研场景中存在一个关键问题:如果需要新增特征字段,必须直接在表的 Schema 上修改,这会影响表的读写。Branch 不支持独立 Schema 配置,导致特征调研场景下使用 Branch 不够安全且难以操作。Iceberg 原生的 Branch 功能在特征调研中并不能很好地满足需求。

(2)Merge Into 的局限性
在大宽表更新少数列的场景中,Iceberg 原生的两种更新模式——Copy-on-Write 和 Merge-on-Read,在 B 站的业务中都存在明显短板。

  • Copy-on-Write 模式:更新大宽表的一列或少数列时,需要重新写入表中所有未更新的列。例如一张 3000 列的大宽表,更新一列就需要重写 3000 列的数据,造成大量冗余写操作,IO 和计算资源浪费严重。

  • Merge-on-Read 模式:虽然只写入被修改的行,但仍需重复记录未更新列的数据。比如大宽表中一行数据,只更新 1 列,其余 2999 列仍会被记录下来,冗余存储依然存在。

这两种更新模式虽被广泛应用,但在大宽表更新少数列的场景中,无法满足 B 站的性能需求。需要设计更高效的方案。

3. Update On Column 方案

针对上述问题,我们提出了一种新的解决方案:Update On Column。

(1)ColumnFile 的定义
ColumnFile 记录了 Iceberg 表中若干个列的值,以及这些列在原始 DataFile 中的行号。行号在 ColumnFile 中为主键。一个 ColumnFile 需要与一个 DataFile 关联,一个 DataFile 可以关联多个 ColumnFile。每个 ColumnFile 存储的记录行数小于等于对应的 DataFile。读取 DataFile 时,DataFile 中的数据与 ColumnFile 中的数据按行号执行 inner join 操作,得到完整的一行数据。没有 join 上的数据认为已被删除。

例如,一张包含字段 F1、F2 和 F3 的表。假设执行 SET 语句修改了 F2 和 F3,ColumnFile 仅记录这两列的更新数据,同时保留原始行号 Position 信息。

  • ColumnFile 与 Copy-on-Write 的区别:在 Copy-on-Write 模式中,所有字段(F1、F2、F3)都会被重新写入,无论是否被修改。但在 ColumnFile 模式中,仅将更新的列(F2、F3)写入,未被修改的 F1 完全不需要重写。通过减少冗余写操作,ColumnFile 显著降低了 IO 开销和资源浪费。

(2)ColumnFile 元数据

ColumnFile 和 DataFile 绑定在同一个 manifest entry 中,一个 DataFile 可以绑定多个 ColumnFile。元数据具体包括:

  • 路径:用于读取文件时指定文件位置。
  • 文件大小:指定文件的大小,读取时依赖此信息。
  • 记录行数:标记数据范围的行数,用于操作时的控制。
  • 添加的 Snapshot ID:记录更新所处的快照版本。
  • Sequence Number:解决与 DeleteFile 关联的问题,保证数据正确性。

(3)ColumnFile 的生成

ColumnFile 的生成是整个流程的核心。具体过程如下:

  • SQL 语句:上图中的 SQL 示例中,更新的字段包括 conv_type、status、update_time 等,最终这些字段会被写入到 ColumnFile 中。该 SQL 最终 spark 执行时会产生 ColumnFile。
  • 逻辑计划:SQL 里面只有一次 join,为什么执行计划里会出现 2 次 join 节点?第一次 Join 是筛选出需要更新的文件路径(注意:期望该 runtime filter 具备 high selectivity,后面会进一步优化);第二次 Join 参与的 target 表利用第一次 join 得出的文件路径来过滤不需要的文件,以减少需要处理的数据量。
  • 数据处理流程:完成 Join 后,数据按以下流程进一步处理:
    • MergeRows 算子:在逻辑计划中,将更新逻辑应用于数据。
    • Projection Push Down:只处理 SQL 中涉及的 target 表字段,避免不必要的字段读取以降低 IO 开销。涉及的字段来源包括:Join Key、Match 条件、赋值语句中的左侧和右侧字段。
    • Repartition 和 Sort:由于一个 ColumnFile 只对应一个 DataFile,需要按输入数据的文件路径对数据重分区以聚合,以便一个 DataFile 只生成一个 ColumnFile。Sort 对更新记录按行号排序,以便读取时可以按行号关联。
  • ColumnFile 的最终写入:经过上述步骤,最终生成的 ColumnFile 中包含更新后的字段信息(如 conv_type、status 等)以及额外字段 position,用于后续操作时的关联。

(4)ColumnFile 的读取

ColumnFile 的读取逻辑相对生成过程更直观。核心在于通过行号(position)实现对 DataFile 和多个 ColumnFile 的多路归并,从而还原完整数据。假设一张表包含 a、b、c 三列。如果用户对 c 列的数据进行了更新(例如将 apple 替换为 pig,将 water 替换为 oil),更新后会生成一个新的 ColumnFile,仅记录 c 列被修改的部分。读取时需要将 DataFile 和该 ColumnFile 归并在一起,通过对应行号匹配输出最终数据。值得注意的是,DataFile 的读取支持切分(Split),可以将文件分成多个片段并行读取以提升效率。读取时需要注意与 ColumnFile 归并的技术细节(Split of a DataFile merge a ColumnFile),这里不展开。

(5)与 DeleteFile 的关系

接下来讨论 DeleteFile 的应用逻辑。DeleteFile 是用来记录删除操作的文件,其 sequence number 标记删除操作的版本号,可以精确追踪数据删除历史。假设表中有 3 个字段 F1、F2、F3,初始所有数据存储在一个 DataFile 中,sequence number = 1。随后用户执行了一次删除操作,删除了 F1=4 和 F1=13 的两条数据,生成了一个新的 DeleteFile,sequence number = 2。紧接着用户生成了一个 ColumnFile,只保留了未被删除的数据(第 1 行、第 3 行、第 4 行),第 2 行和第 5 行数据已被删除。读取时,DeleteFile 配合 ColumnFile 和 DataFile 一起确定最终数据输出。为了避免冗余应用 DeleteFile,读取逻辑需要判断其 sequence number:如果 DeleteFile 的 sequence number 小于 ColumnFile 的最大 sequence number,意味着该 DeleteFile 删除的语义已被后续 ColumnFile 处理过,无需再次应用;否则需要在读取时额外处理 DeleteFile,确保删除操作的正确性。

显然,ColumnFile 还具备删除语义。这种基于 sequence number 的判断逻辑在更复杂场景下尤为重要。例如,数据生成顺序如下:第一次生成 DataFile,sequence number = 1;第二次生成 ColumnFile,sequence number = 2;第三次生成另一个 ColumnFile,sequence number = 3;第四次生成 DeleteFile,sequence number = 4。读取时需要先对 DataFile 和 ColumnFile 进行多路归并得到初步结果,然后将归并结果中 ColumnFile 最大的 sequence number 与 DeleteFile 的 sequence number 比较。如果 DeleteFile 的 sequence number 小于归并结果中的最大值,则其删除语义已被覆盖;否则需要将删除语义作用到归并结果上。

Update on Column 的技术设计与思考

在满足业务需求的基础上,进一步探索能否优化更新流程以缩短处理时间。当前完成一次更新需要 15 分钟,基本满足业务需求,但优化潜力仍然存在。特别是首次 Join 操作的必要性值得重新审视,因为在实际执行中,第一次 Join 的代价占比比较大。

(1)首次 Join 操作的必要性
从执行计划看,数据处理涉及两次 Join,第一次 Join 用于筛选需要更新的文件,通过 Runtime Filtering 确定更新文件路径集合(类似倒排索引)。但如果业务场景中 99% 的数据都需要更新,首次 Join 的必要性就大幅降低。比如特征调研场景下,几乎每一行数据都需要变更,Runtime Filtering 的 selectivity 非常低。这种情况下,可以尝试直接跳过首次 Join,将 Runtime Filtering 设置为 None(全表扫描)。这和关系型数据库中 B+树索引没有过滤效果时建议全表扫是本质一回事。这种优化下,默认所有文件都需要被更新,写入时间大幅缩短。在特征调研场景中,处理时长从原来的三小时减少到十几分钟。值得注意的是,Iceberg 原生行级更新的论文(VLDB - Petabyte-Scale Row-Level Operations in Data Lakehouses)只针对 Runtime Filtering 做了 high selectivity 场景的测试,没有给出 low selectivity 场景下的性能报告。而 low selectivity 场景正是业务应用很重要的一块,尤其是特征调研场景——Runtime Selectivity 几乎为 0,Runtime Filtering 反而起副作用。

(2)Writer 优化
我们发现更新 Label 场景(大约一半文件被更新)在应用 None 模式后性能存在回退。经过观察,回退主要由 IO 引起——每个 write task 写入几百 KB 级别的文件。进一步优化集中在 Writer 层。跳过首次 Join 后,虽然 stage 数减少了一半,但同时也引入新问题:系统可能写出大量无效文件(未被修改但仍然写出的文件),这些文件往往体积较小(几十 KB),占用大量 I/O 资源。为解决这个问题,我们在 writer 中引入了一种基于缓存的判断机制:在 project 阶段新增一个字段 row_from_source(见执行计划中红色字段),用于标记数据来源。如果 writer 检测到 row_from_source 字段,说明该文件需要被修改,触发 flush;否则在文件关闭时直接清理缓存,避免生成无效文件并释放内存空间。该优化上线后,更新 Label 场景性能提升约 33%。

(3)Update on Column 模式的优势
上述优化在 Copy on Write 模式下难以实现,因为 CoW 需要更新所有字段,容易导致内存 OOM。而基于 Update on Column 模式,由于每次更新的列数非常少(单个 ColumnFile 通常只有几十 KB),在内存中操作完全可行。这种轻量化操作方式使得 Writer 层优化成为可能,进一步提升性能。

RuntimeFiltering 的优化效果:

  • 运行时间:从原来的 12 分钟缩短至 8 分钟。
  • Stage 数量:由 Copy on Write 模式下的多个复杂阶段减少为仅 4 个 Stage。
  • 业务价值:数据更新和模型训练效率提高 33%,显著缩短算法迭代时间。

业务落地与性能优化效果

在实际业务中,进一步优化了 Iceberg 的功能支持,尤其是在分支(Branch)上的 Schema 独立性和列级更新(Update on Column)模式的使用,为特定场景下的数据处理带来了显著的灵活性和性能提升。

1. Branch 上的 Schema 独立性支持

在 Iceberg 社区原生实现中,分支并不支持独立的 Schema 配置。B 站的业务需求提出了更高要求,特定场景下需要直接在分支上进行特征调研和列操作,而不依赖创建新表来处理数据。为此,为分支提供了独立的 Schema 支持,大幅优化了以下工作流程:

  • 减少新表创建的开销:通过在分支上直接操作数据和验证新增列的效果,避免创建冗余的新表。当主表需要时(例如分支验证某特征有效),再将分支的更改在主表中重做。
  • 适应特征调研场景:所有调研操作都可以在分支上完成,分支自带生命周期管理,支持新增列、修改列等操作,显著提升开发效率。

为了避免过度复杂化和潜在的元数据膨胀问题,实现仅支持独立 Schema,未扩展到支持更多类型的元数据。这种设计选择在满足业务需求的同时,保持了系统的稳定性和可维护性。

2. Update on Column 模式

列级更新是 Iceberg 中的一种轻量化写入模式,通过配置简单参数即可实现:用户只需将 Merge Model 设置为 Update on Column,即可在执行 MERGE INTO 时以列为单位更新数据。如果一个 DataFile 绑定了非常多的 ColumnFile,读取效率可能会下降。这种情况下,需要做一次或按需做 Compaction,对 ColumnFile 和 DataFile 进行合并。这种手动触发的设计是出于作业稳定性考虑,因为类似自动压缩的系统功能可能引发并发问题。手动压缩既保证了灵活性,也能有效规避潜在风险。

在大数据系统中,写入性能和读取性能通常难以兼顾。但通过优化分支 Schema 和 Update on Column,在实际测试中取得了良好的读写平衡。基于 Update on Column 模式的优化,使得写入过程更加轻量化,特别是对于更新文件比例较大的场景,可以通过跳过首次动态过滤(Dynamic Filter)进一步降低开销。尽管优化主要集中在写入端,但读取性能并未因优化而显著退化。测试结果表明,该方案在读写两端都能达到令人满意的效果。

未来规划

未来,工作将围绕以下几个关键方向展开,以进一步提升数据处理能力和业务支持效率:

  • 宽表治理
    针对企业内部常见的宽表问题,提出了一种新的治理方案。宽表通常由多张表通过复杂的 LEFT JOIN 组合而成,维护十分繁琐。增加新字段或重新计算时,任务调度必须等待所有参与的表准备完毕,导致调度粒度大、效率低。通过按列级别的表产出方案,避免全表重复计算。这不仅能提升任务调度的灵活性,还会改变宽表在数仓链路中的依赖模式,减少对下游数百个 ETL 作业的影响。ColumnFile 的通用性不仅限于更新场景,也为宽表设计提供了一种高效替代方案。

  • 回刷数据
    现有计算框架中,回刷数据通常以分区为单位。当计算口径变化或需要回刷部分数据时,效率极低。提出一种细粒度的回刷方案:支持按文件级别进行回刷,而非整个分区,从而降低资源和时间成本。

  • 多流拼接与增量读语义
    当前流计算领域的多流 JOIN 操作往往面临大状态管理的问题。计划在 Iceberg 层面定义并支持多流拼接的增量读语义,简化状态管理。

  • PyIceberg 的扩展
    计划在 PyIceberg 项目中增加更多支持,比如 Distribution 和 Index,以进一步优化与 Python 生态的兼容性,为开发者提供更友好的工具和接口,并结合现有的 Iceberg 能力扩展其在数据处理与分析领域的应用场景。

以上就是本次分享的核心内容,希望能给做类似场景的技术团队一些参考。

来源:https://www.53ai.com/news/finetuning/2025030995317.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。