1. 背景
1.1 业务背景
A+流量分析平台是阿里集团统一的全域流量数据分析平台。从页面&小站&活动&APP等作为切入点,在经过埋点、采集、计算后,构建出宏观的概览数据、坑位效果、类目成交转化、路径分析、用户细分等,致力于打造流量数据分析闭环,快速帮助业务发现流量问题&提升流量转化。当前,流量采集团队每天需要的日志数据达到万亿级,在写入和查询面临着巨大挑战:
写入方面:千万级 rps 实时数据写入
- 高吞吐写入:每秒需要处理上千万条数据的写入,写入过程中需要解决数据分布不均(数据倾斜)的问题,并进行反作弊处理、关联维表等计算。
- 时效性要求:从用户日志生成,发送到消息队列,读取加工存储,到计算引擎可查询,期望将整个流程的延迟稳定控制在5分钟级别,供业务快速响应决策。
查询方面:高效高并发查询
- 高并发查询:在多用户同时访问的情况下,需要支持高并发的查询。在高并发查询下,要求查询服务稳定且查询效率不发生严重衰退。
- 查询效率:查询引擎需要支持低延迟的实时查询;查询性能受到数据规模、索引设计和计算资源的限制;查询结果需要缓存或预计算以减少响应时间。需要优化查询引擎(如列式存储、向量化计算)以提高性能。
1.2 技术背景
1.2.1 日志采集数据链路
目前阿里采集的日志数据存储在 MaxCompute 中,日志表单天数据量在万亿级别。离线日志最细以小时表粒度对外透出。现有的数据链路决定了日志查询的性能现状。
- MaxCompute 分区存储导致查询耗时长
MaxCompute 日志表以日期、日志类型、产品、事件类型进行四级分区,但是部分分区仍然有千亿级的数据,最大的分区数据量在5000亿级别。如果没有额外的下推逻辑,即便只是查询单个用户或设备的日志,依然需要扫描整个分区,读取千亿级的数据,这导致日志查询耗时长,效率低。
- 加工链路导致小时级延迟
MaxCompute不直接支持流式处理,无法承接实时数据。目前,所有业务的日志汇总在天表和小时表里,最细以小时表粒度对外透出。等待业务一整个小时的日志完整产出后,计算链路开始加工计算,导致日志的时效性为小时级。
1.2.2 StarRocks 数据湖分析
现有的基于 MaxCompute 的存储和计算方案既无法承接实时日志数据,也难以进一步优化查询速度。为此,需要选择新的存储方案和计算引擎。
综合考虑几种日志查询解决方案,包括 MaxCompute 15分钟批处理、StarRocks 内表存算一体方案、StarRocks 和 Paimon 方案,总结如下:
方案 | 数据存储 | 数据查询 | 延迟 | 查询速度 | 扩展性 |
---|---|---|---|---|---|
MaxCompute | 实时数据->15分钟周期批调度->15min分区MaxCompute日志表 | MaxCompute | 15min级 | 慢 | 低 |
StarRocks内表 | 实时数据->StarRocks内表 | StarRocks | 秒级✅ | 快✅ | 低 |
StarRocks+Paimon | 实时数据->Paimon数据湖 | StarRocks | 2分钟级✅ | 快✅ | 高✅ |
StarRocks 内表可以存储实时数据,且查询性能良好。但考虑到 Paimon 数据湖存储的扩展性,StarRocks 的高效数据湖分析能力,选择 Paimon 存储+ StarRocks 计算。
StarRocks 和 Paimon 方案优势如下:
(1)Paimon 存储复用实时公共层能力,高效承接实时日志数据
实时公共层采用 Paimon 存储来管理公共层的数据,支持多地多机房 flink 实时订阅。公共层表的存储格式为 Aliorc。
StarRocks+Paimon 方案,复用实时公共层的能力。实时公共层以 Aliorc 格式(基于 ORC 格式的内部优化版本)存储消息队列中的实时日志。进一步地,通过 flink 订阅 Aliorc 格式的实时公共层 paimon 表,写入到 parquet格式的 paimon 分桶表中,供计算引擎查询。目前 StarRocks 引擎对 Aliorc 格式数据正在支持中,等待支持后,日志查询直接通过 StarRocks 查询实时公共层数据,不需要额外的存储和实时数据订阅。
(2)StarRocks 高效数据湖分析能力实现秒级查询
StarRocks 可以作为计算引擎直接分析数据湖中的数据。用户可以通过 StarRocks 提供的 External Catalog,轻松查询存储在 Apache Hive、Apache Iceberg、Apache Hudi、Delta Lake、Apache Paimon 等数据湖上的数据。
在日志查询场景中,StarRocks 主要负责数据的计算分析,而 Paimon 数据湖则主要负责日志数据的存储、组织和维护,StarRocks 作为 Paimon 数据湖的计算引擎,可以大大提升日志查询的性能。StarRocks 对于 Paimon 湖格式的读取支持能力非常优秀,相对传统的 OLAP 查询引擎,主要有以下优势:
a. 高效的向量化执行引擎,硬件资源使用率最大化
b. 充分利用 Paimon 的表统计信息,高效完成最优的 CBO 查询规划
c. 支持 native 方式高效读取 Paimon Deletion Vector,降低实时更新场景查询时延
d. 优秀的谓词下推和分区裁剪能力,大大减少远端 IO 的请求成本
(3)Paimon 存储成本低,扩展性高
Paimon 作为一个新兴的数据湖格式,近些年发展十分迅猛,该格式的主要特点有:
a. 强大的流批一体数据处理能力,支持大规模更新和写入。
b. 创新性地引入 LSM 结构来处理更新数据,显著降低业务数据写入延迟。
c. 支持 Deletion Vector 和多种索引技术,大大提升 OLAP 查询引擎执行效率。
Paimon 的设计基于存储与计算分离的理念,阿里的 Paimon 数据存储在分布式文件系统 pangu 中,Pangu 是阿里云存储服务的底层存储系统。Paimon 支持多种计算引擎的接入。除了日志查询通过 StarRocks 读取 Paimon 数据,其他计算任务也可以通过 Spark 等引擎消费 Paimon 日志表。
出于 StarRocks 查询性能高、复用 Paimon 实时公共层、Paimon 存储成本低扩展性高这几方面的考虑,日志查询选择 Paimon 数据湖作为存储,StarRocks 引擎用于查询计算。
2. 技术方案
2.1 技术大图
基于 Flink、Paimon 和 StarRocks 计算引擎,实现万亿级实时数据的秒级查询。
入湖 Paimon 表基本链路:
数据来源于实时公共层的的 App 端日志汇总表和 Web 端日志汇总表,分别保存全量的实时App端日志和全量的Web端日志。Flink 消费实时公共层的汇总表实时数据,写入 parquet 格式的 Paimon 表作为日志查询的存储。额外地,用户-设备映射表存储用户id与设备id的映射关系。利用 StarRocks 作为计算引擎,先通过映射表查询用户id对应设备id,再通设备id查询日志表。
StarRocks 查询 Paimon 日志表:
StarRocks 加载 EXTERNAL CATALOG <paimon_catalog_name> ,先通过映射表查询用户id对应设备id,再通设备id查询日志表中对应的分桶。
日志查询的存储主要表说明:
(1)App端日志明细表
App端日志明细表,包括无线端的浏览点击曝光 dwd 层日志,parquet 格式存储,以设备id为分桶键,为了防止数据倾斜,写入时需要进行 null 检查、长度检查,空值和过段的用户id进行打散。UT日志明细表为5级分区,依次是日期ds、小时hh、日志来源类型 type、产品 product、事件类型 event_type。
(2)Web端日志明细表
Web端日志明细表,包括 pc、wap 的浏览点击曝光 dwd 层日志,parquet 格式存储,以设备id为分桶键。Web端日志明细表为4级分区,依次是日期ds、小时hh、日志来源类型 type、产品 company。
(3)用户-设备映射表
映射表,实现用户id -> 设备id的映射表,分桶键为应用id、用户id、设备id,主键为应用id、用户id、设备id、ds、type,其他字段包含设备信息,供根据用户id进行设备选择。配置 deletion-vectors 供 StarRocks 在读取端按索引过滤文件。映射表为2级分区,分别是日期ds、日志类型 type。
2.2 实现
2.2.1 基于分桶表和PK表的秒级查询
日志数据量在万亿级别,visitor 数量在亿级,为了优化查询效率,需要设计针对性的表结构,避免大范围扫描数据。针对日志表和映射表的查询场景,设计合理的表结构;并开启 Data Cache 优化查询速度。
分桶日志表
日志查询核心是对单设备、用户扫查日志,通过存储上 hash 分桶,降低扫描范围,只扫描单 bucket
将日志底表设计为分桶表,结合分区设计和分桶数量,单个 bucket 数据量在2000W条级别。
分桶id mapping 主键表
映射表的数据量在亿级,映射查询是对单个用户的点查。所以将映射表设计为分桶pk表。配置PK表 deletion-vectors 供 StarRocks 引擎查询。
开启 Data Cache 优化查询速度
StarRocks +Paimon 方案数据存储与计算分离,使两方都能够独立扩展,从而降低成本并提高系统弹性扩展能力。然而,这种架构会影响查询性能。
StarRocks 建立了包含内存、本地磁盘和远端存储的多层数据访问系统,以实现接近存算一体读取内表的查询性能。
对于针对热数据的日志查询,StarRocks 会扫描本地磁盘缓存。而针对冷数据的日志查询,需要先将数据从对象存储中加载到本地缓存中,加速后续查询。
StarRocks 缓存机制大幅优化了日志查询的性能,实现高查询性能和高性价比存储。
在分桶日志表和分桶主键映射表的设计下,在3000CU的 StarRocks 实例上,1.5w亿的日志数据,第一页查询耗时在4~8s,后续分页查询时间在2~5s。
2.2.2 控制文件大小
Paimon 存储中的文件大小,会对下游引擎(StarRocks)的查询性能造成以巨大影响:
小文件
- Scan File 开销增加:StarRocks 在扫描文件时需要逐一加载每个文件的元数据(如 Manifest 文件)。如果文件数量过多,元数据加载的时间会显著增加,导致查询延迟。
- I/O 瓶颈:小文件通常意味着更多的随机 I/O 操作,增加了磁盘读取的开销。
大文件
- 单个 Row Group 的限制:Flink 默认使用 Arrow Parquet 写文件,单个 Row Group 上限是按照 max_row_group_length 来控制,默认值 1Mi(1024*1024) Rows,阿里日志明细单行数据较大,平均有 500B,导致单个 RowGroup 会到 600M+。如果文件过大,Row Group 的大小也会随之增大,这可能导致读取效率下降,尤其是对于随机读取场景。
- 查询延迟增加:大文件在加载时需要一次性读取更多数据,可能会增加内存压力和查询延迟。
为了控制 paimon 存储文件大小,优化 StarRocks 查询性能,尝试了文件合并与调整 checkPoint 两种方法,最终选择通过 Checkpoin t控制文件大小。
Compaction 任务合并小文件
尝试通过流式 Compaction 任务和周期性 Compaction 任务合并文件,优化 StarRocks 查询。
流式 Compaction 是一种实时触发的文件合并机制,能够在数据写入过程中动态地合并小文件。然而,在实际生产环境中,我们发现流式 Compaction 专家模式无法生成执行计划,存在一些问题。
周期性 Compaction 是一种离线或定时触发的文件合并机制,通过定期运行 Compaction 任务来合并历史小文件。周期性 Compaction 在性能上不满足海量日志的需求。
Checkpoint 控制文件大小
对于日志查询的 paimon 表,通过调整 flink 任务的 checkpoint 时间间隔可以控制文件大小。针对不同的 Flink 任务,根据其数据流量和写入模式,设置不同的 Checkpoint 时间间隔,简介且有效。
通过对不同的 flink 作业设置不同的 Checkpoint 时间间隔,Paimon 中大多数文件的大小控制在100M到400M之间,既避免文件过小导致 Scan File 开销增加和 IO 瓶颈,也避免文件过大导致查询延迟增加。
2.2.3 Flink 作业与 StarRocks 查询调优
实时数据写入 Paimon 和合并小文件的过程中,可能导致写入冲突问题。尤其是在 compact 文件的过程中,删除文件很可能造成冲突,导致 Flink 作业频繁 fail 和实时数据延迟。尝试拆表和分区两种设计,综合考虑 compact作业、checkpoint 配置、StarRocks 查询性能,选择分区表方案。
表设计 | 文件大小控制 | Flink作业写入性能 | StarRocks查询性能 |
---|---|---|---|
分区表 | 写入时Compact | 执行计划生成故障 | 查询性能稳定✅ |
分区表 | 写入后周期Compact | 性能和稳定性不满足要求 | |
分区表 | CheckPoint控制 | 运行稳定✅ | |
多表+视图 | 写入时Compact | 执行计划生成故障 | 锁竞争+读Manifest 串行查询性能不满足需需求 |
多表+视图 | 写入后周期Compact | 性能不满足要求 | |
多表+视图 | CheckPoint控制 | 运行稳定✅ |
拆表方案
拆表可以避免冲突问题,并确保 Flink 任务的运行稳定性。通过这种设计,Flink 任务的运行状态得到了显著改善,写入吞吐量提升,且未出现明显的冲突问题。
为了统一对外提供查询接口,在 StarRocks 中创建视图,该视图通过 UNION ALL 将多张表的数据合并起来。这种方式理论上可以屏蔽底层拆表的复杂性,使用户无需关心数据存储的具体细节。
但是,拆表方案在查询过程有如下问题:
- 锁竞争:在高并发查询场景下,StarRocks 的视图查询出现了锁竞争问题,当多个查询同时访问视图时,由于视图底层涉及多个小表的联合查询,可能会导致某些表被锁定,进而阻塞其他查询。
- 读 Manifest 串行:StarRocks 在查询过程中需要读取元数据文件(即 Manifest 文件),以获取表的分区信息和数据分布情况。然而,在读视图数据时,这一过程存在串行化的瓶颈,StarRocks 以串行方式读取 Manifest 文件,导致查询时长增加。
分区方案
用一张表保存全量的UT日志,在写入过程是稳定的,但是 compact 文件的过程中,删除文件频繁发生冲突,造成 flink 作业中断,数据延迟不断累积。可以通过拆分多个 Flink 作业单独写入,针对性地配置 Checkpoint 大小,以此控制文件大小,提高查询性能。
采取单表多分区的形式来存储日志数据,同时通过 Checkpoint 控制文件大小,flink写入作业运行稳定,StarRocks 引擎查询日志不存在明显瓶颈。
总结与展望
3.1 日志查询性能
MaxCompute | StarRocks+Paimon | |
---|---|---|
查询时间 | 5min | 5s |
数据时效性 | 2h | 5-10min |
UT日志志单天数据在万亿级别,MaxCompute 和 Paimon 中保存相同的数据。
MaxCompute 单分区数据量最大可达5000亿,此时针对单设备、用户扫查日志耗时在分钟级别。MaxCompute 资源紧张时,查询耗时在10min以上。
Paimon 存储,在分区分桶的设计下,单个 bucket 的数据量稳定在2000w条左右,StarRocks 查询性能稳定在秒级。
目前产品体验上,查一天的数据,因为有多个FE,manifest 的如果没有命中缓存,耗时可能长点,可能在4~8s左右,有 manifest 缓存的查询会快一些,基本2~5s左右,查多天耗时会变大。
3.2 展望后续升级
- 更低存储成本:直接复用实时公共层的 Paimon 存储,省去了 Paimon aliorc->Paimon parquet 的 flink 计算成本、Paimon 冗余存储成本。
- 更高的计算性能:相比于 Parquet,aliorc 在写入和读取方面更加高效,例如基于内存的压缩、数据类型推断和多层次索引等,具有更快的数据写入速度和更低的存储空间占用。starrocks scan 算子与 ailorc 结合后会提供更高性能的文件 IO 操作。
- 更多的业务场景覆盖:A+流量分析有着丰富的产品功能,后续会在事件分析、留存分析、路径分析等场景进行探索和落地。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。