背景介绍
天玑买量平台服务于 UG 买量业务,由于业务特性,优化师需要丰富且准确的端内+端外数据来辅助投放决策。目前买量的数据有来自 UG 数仓的,有来自媒体 MAPI 拉取的,形式上有离线的,也有实时的。并且数据存储形式多样,业务数据主要使用 MySQL,离线数据基于数据湖写入 ClickHouse,实时数据基于 TiDB,在如此多异构数据的前提下,又有联合分析的场景,准确性和时效性都面临考验,因此我们决定基于 StarRocks 构建统一数仓,来消减异构数据带来的复杂性,同时支持业务对数据准确性和实时性的要求。
痛点分析:传统多存储架构的困境
天玑买量数据架构主要包含以下数据类型:
- 业务数据: 包括多个渠道的的广告主数据、广告计划数据、广告组数据、广告创意数据,以及极速搭建、资产、任务等相关数据,主要存储于 MySQL 中。
- 指标数据: 涵盖媒体、启动归因、点留存、投放成本、收入、播放等数据,在时间维度上包括分天离线数据和分时准实时数据,主要存储于 ClickHouse 中,实时数据同时也会存至 TiDB。
引入 StarRocks 之前架构存在以下痛点:
1.数据孤岛与跨源分析困难
业务数据与指标数据存储于不同类型的数据库中,导致数据天然隔离。在投放列表等典型场景中,需同时对业务 MySQL、离线 ClickHouse 及实时 TiDB 进行联合查询。跨源查询能力较弱,通常需借助内存进行聚合计算,资源消耗大,技术实现复杂。
2.实时性与查询性能难以兼顾
为满足实时数据分析,我们曾尝试两种方案:
- 使用 TiDB + TiFlash: 虽具备实时更新能力,但查询在部分场景下性能不佳,整体查询效率低于 ClickHouse。
- 使用 ClickHouse: 技术栈与离线统一,但不支持实时更新,数据时效差。
3.架构复杂导致应用开发成本高
在天玑自定义数据大盘和 ChatBI 等产品中,因需对接多种数据源,语法差异大、实现复杂度高,扩展及维护成本显著。
StarRocks 的核心优势
基于上述业务诉求,我们需要一个支持原子更新、多表 JOIN、海量数据存储及快速响应的 OLAP 数据库。由于 TiDB 在查询性能上无法满足,因此我们最终对比 ClickHouse 与 StarRocks:
在数据写入效率、查询性能、并发能力、数据一致性等多维度对比上 StarRocks 展现出更契合买量业务场景的能力。
统一数仓架构设计
新架构核心目标为简化存储层、消除冗余存储类型,从根本上解决跨数据源查询与时效性问题,具体设计如下:
- 业务数据同步:
基于大数据团队提供的 BabelX 实时数据集成,将业务 DB 直接实时同步到 StarRocks 集群中,在数据 查询中应用存算一体的方式,提升查询性能,简化查询语法。
- 实时+离线 OLAP 数据:
端外拉取的实时数据基于 RCP 直接写入 StarRocks ,第一时间可用。对于离线的需要与 UG 数仓联合 计算的数据,在数据湖计算完成后同步到 StarRocks 集群。
- 数据查询应用:
所有数据查询应用,全部从统一实时数仓 StarRocks 出数,保证了结构简单,语法统一,同时如数据大 盘,ChatBI 等应用,只基于一种 DB 处理,降低了复杂性,同时提升查询准确性。
场景化实践案例
实时数据报表
实时数据报表主要针对的是广告在媒体侧产生的实时数据,这些数据有两种用法,一是直接查看广告的点击花费等效果,二是结合 UG 数仓端内数据做计算,在体现实时花费的同时能够结合端内收入数据,查看成本率。
关于实时数据报表,天玑采用过不同的方案。
第一阶段
采用 TiDB+TiFlash 方案,此阶段是为了满足优化师实时看数的需求,在第一阶段只有端外的数据,还没有端内的数据,因此本阶段的目的是要第一时间看到端外的数据情况,但是媒体一般没有实时流数据,因此采用定时抓取的方式,尽量在有效利用媒体 QPS 资源的同时,快速写入供优化师参考。
这个阶段采用 TiDB 的原因是我们想要实时更新的特性,但是在查询上需要做的工作比较多。
- 耗时: 数据量上每天约产生几百万条,存量上亿条,使用 TiDB 的查询耗时平均约为 4~5 秒,由于我们一次页面加载需要获取多个数据块,在并发查询的时候整体响应时间会变得更长。
-
查询优化: 在使用 TiDB 的时候,我们也要考虑查询是走 tikv 还是 tiflash,这时候的选择与查询的过滤条件有关,如果能通过一个条件过滤成几百万以内的聚合查询走 tikv,如果按一个条件过滤之后还是几千万的数据则需要走 tiflash,通过这种策略来尽量提高查询性能,但是因为判断条件较多,导致查询逻辑复杂且判断难度大,例如下面 SQL ,我们需要在部分查询中指定引擎。
第二阶段
第二版方案的升级有两个因素驱动:
- UG 数仓的端内数据建设完成,优化师需要结合端内+端外的数据进行联合分析。
- 考虑到 TiDB 在查询上的复杂性以及耗时问题。
基于这两个原因我们舍弃了一部分实时性,将分时数据与分天数据的架构进行整合,即全部采用离线形式,计算放在 iceberg 中,然后同步到 ClickHouse 中进行 OLAP 查询使用。
采用这个方案我们每隔 30 分钟做一次计算,简化了数据链路,查询语法做了统一,借助于 ClickHouse 的查询性能,并发查询耗时也下降到 1~2s。
第三阶段(StarRocks 方案)
第二个阶段存在一个问题,就是在拿到数据之后不能直接提供查询,因为要经过离线计算和同步的过程,而且还有列表异构查询,ChatBI 数据源统一的其他问题,因此实时报表也进行了 StarRocks 统一数仓的升级。
进行升级之后,可以实现拿到数据的第一时间即可提供数据查询能力,缩短了优化师分析路径,其次由于所有查询场景都是用 StarRocks 集群,查询语法也进行统一。
升级前: 主要是 Hive 节点计算+离线同步至 ClickHouse ,写入 ClickHouse 的平均耗时为 3.5 分钟。
升级后: 端外数据近实时写入 StarRocks ,得益于 StarRocks 的高吞吐写入+支持部分列更新,端内数据在数仓计算好之后,做部分列更新,时效性得到了很大的提升。同时基于 StarRocks 提供的物化视图技术,对外暴露统一视图,避免使用方感知当前最新 ready 数据的细节。写入 StarRocks 的平均耗时为 1 分钟。
投放列表
投放列表是面向优化师展示广告维度信息和数据指标信息的主要列表。广告维度本身保存在 MySQL 中,数据指标保存在 OLAP 中。主要经历过三个阶段:
仅查询 ClickHouse
在项目初期,投放列表只有查看单一广告账户下的广告计划、广告组、广告创意的标签页,所以最开始我们采取先查询 MySQL 获得筛选后的广告 ID,然后再查询 ClickHouse ,并筛选数据指标。
这样的好处是开发效率高,影响范围小,缺点就是每次查询都要传递大量广告 ID(百度信息流一个账号下的创意数量最多达到 2 万个,已经快接近一个 SQL 语句 2MB 的极限),而且不能根据广告维度进行排序,性能差,但项目初期这样的架构方案已经足够了。
ClickHouse 外表联表查询
随着业务的扩张,优化师们已经不满足于只查看一个广告账户的广告数据了,投放列表增加投放账号层级的展示,而且也允许同时展示多个账号下的广告了。随之而来的问题就是每次传递的大量广告 ID 已经超出了 SQL 语句最大 2MB 的承载极限,而且 IN 匹配的值数量越多导致 SQL 查询的性能越来越差了。
为此我们不得不使用新的方案:将 MySQL 作为 ClickHouse 的外表进行联表查询;新方案解决了传递大量广告 ID 的问题,性能更好了,但外表的方案不但影响 MySQL 的性能,也影响联表查询的性能。
StarRocks 内表联表查询
由于 ClickHouse 的联表查询本身性能孱弱,我们将 OLAP 的方案转向了 StarRocks,而且碰巧 BabelX 实时数据同步的发布能够解决大量表的数据同步难以维护的问题,我们将 ClickHouse 替换为 StarRocks,将 MySQL 外表替换为 StarRocks 内表,这样我们就可以在 StarRocks 里完成全部的查询,基于其强大的联表查询性能,我们的投放列表性能也有了新的突破。
以下是不同阶段的耗时对比
以百度信息流查询 164 个账号的广告计划(16 万+)、广告组(100 万+)、广告创意(600 万+)为例,单位毫秒。
由于仅查询 ClickHouse 的方案已经不满足需求,则不在本次耗时测试之内。
可以看到随着数量级的膨胀,ClickHouse 外表查询的耗时会显著的增加,而 StarRocks 存算一体的查询则基本不受影响。
买量助手数据查询
目前天玑虽然提供了数据报表、自定义数据大盘等多种取数方式,但广告优化师还有更加灵活取数的场景,例如基于手机快速取数的场景,或者提供了新数据源,但报表还没有建设完成的阶段,场景 case 如下:
为了以上场景,我们提供了基于 LLM 的智能化 ChatBI 买量助手工具,优化师可以使用自然语言获取相关数据,但是在建设过程中,由于异构数据源的存在,会使买量助手的构建成本增加。
如下为不使用 StarRocks 统一数仓的简化工作流:
注意图中红色部分,如果优化师的问题,需要多种数据源的结果,如需要 MySQL 和 ClickHouse 中的数据,那我们需要召回 MySQL、ClickHouse 两种不同数据库的函数,这会使上下文增加,如果涉及更复杂的查询上下文还会继续增加。其次整个工作流中,我们需要根据数据库类型的不同进行多次查询,然后将查询结果在内存中进行汇总,这里有一个缺点是只能处理一定范围内的数据,如果数据量过大,内存计算无法满足,此方案无法充分利用数据库的性能。
如下为使用 StarRocks 统一数仓的简化工作流:
可以看到,使用 StarRocks 统一数仓之后,工作流的红色部分从 4 个变成 2 个了,同时知识召回的上下文长度变少,而且查询环节仅操作一个数据库,可以实现多表联合查询等场景。
迁移实施
为了保证业务能够无感知的切换到 StarRocks 集群,我们从不同集群之间数据一致性、迁移过程、不同数据库之间查询语法差异等几个角度进行验证。
数据一致性
由于需要将所有业务数据写入 StarRocks ,我们需要保证不同数据库之间的数据一致性,这里主要区分实时与离线两种类型。
实时数据同步主要通过 BabelX 实时数据集成来实现,目前同步的延迟在秒级以内,一致性校验方面,BabelX 平台也提供了全量校验、定时全量校验、增量校验三种校验模式,通过配置不同的校验任务,来保证数据的一致性。
离线数据的校验更为简单一些,由于是每天跑一次任务,我们是直接通过定时脚本进行验证,除了部分字段精度处理有不一致之外,没有发现其他的异常 case。
迁移过程
数据库或者系统迁移过程中,为了保证对用户无感,通常会经历双写、双读(灰度放量)、切新等几个阶段,在整个迁移的过程中,我们大致也遵循这几个步骤。
第一阶段-双写: 离线数据通过 babel 同步至 ClickHouse 与 StarRocks ,StarRocks 的写入通过服务云团队提供的 Pilot 工具,支持 Hive 节点直接写入。
第二阶段-双读: 通过开发灰度以及接口数据对比逻辑,在代码中对相关指标的聚合逻辑进行验证。主要流程如下图,由于这一步返回的结果与具体的实现方式有关系,所以出现不一致情况的可能性也更大,后边列出了我们所发现的主要问题。
第三阶段-切新: 在完成前两步的验证之后,就可以将读流量全部切换到 StarRocks 之上了。
语法差异
1.聚合后的字段处理方式不同
在 ClickHouse 中,不允许对一个字段做多次的聚合,聚合后的字段如果被命名为与原字段相同的名字,则以后在引用该字段名时,引用的是聚合后的结果。而 StarRocks 则要求对于分组字段以外的其他字段,都要有聚合函数,即使前边有聚合重命名为本名的字段,本次重新引用的也是表中的原始值。
2.使用 union all 的语句 order by/limit 生效范围不同
在我们的一个服务中,有通过 union all 合并两部分查询结果的使用场景。 sql 如下:
在 ClickHouse 中,order by 和 limit 都是对第二部分的查询结果生效,最终返回的结果是确定的。而在 StarRocks 中,order by 和 limit 的生效范围都是 union 后的结果,跟我们的场景是不符合预期的。解决方式就是将这两个 sql 同时发起请求,然后在内存中对结果进行合并,这样就不用去改造整体的 sql 配置以及担心两部分结果会互相影响。
3.StarRocks 临时字段不能直接放在 where 里边使用
在 StarRocks 里边如果一个字段是通过其他字段计算出来的,那么是不允许将这个临时字段直接放到 where 里边去进行条件判断,这也会带来一些不方便的地方,解决方案就是通过加一层子查询来实现。
除上述几点之外,还有一些诸如日期函数、聚合默认值等比较小的点,在此不再赘述,如果使用过程中遇到直接参考官方文档即可。
致谢
技术选型和迁移过程特别感谢大数据团队给予的帮助,以及在集群稳定性和一些兼容问题等方面的支持。
未来规划
在基于 StarRocks 构建统一数仓之后,消除了技术架构的复杂性,同时满足天玑平台的复杂 OLAP 需求,也大大降低了开发同学的运维成本。未来会在释放买量平台数据价值上做更多的尝试,助力业务发展。
- 性能优化专项,基于业务特征进一步优化 StarRocks 分区分桶策略。建立多维度物化视图体系,为高频查询场景提供毫秒级响应。
- 建立统一的指标管理体系,达到一次定义多处复用的目的,解决现有系统多模块各自管理的成本增加以及标准化问题。
- 基于 StarRocks 统一数仓,扩展智能化数据分析场景,协助优化师进行广告优化,提升买量平台效率与质量。