一、从实际场景看StarRocks查Iceberg的痛点
1.1 常见应用场景
现在很多公司搞湖仓一体,不用单独建数仓,直接用StarRocks连已经存在的Iceberg格式数据湖,比如电商平台的用户行为日志、线下门店的交易数据,都存在Iceberg里,StarRocks拿过来就能直接做报表、跑分析,不用再迁数据,省了好多中间环节的资源和时间。比如某电商的用户行为数据,每天都会生成Iceberg格式的日志文件,存在HDFS上,业务人员要查5月份的购买用户数,直接用StarRocks写SQL就能出来,不用等到数仓同步,很方便。
1.2 StarRocks+Iceberg的优缺点
优点很明显:一是无中间层,省了建数仓的成本,比如不用建Hive、不用导数据,直接对接;二是查询性能比直接用Spark查快很多,因为StarRocks是MPP架构,适合做大吞吐量的查询;三是支持实时增量查询,Iceberg的增量数据可以直接被StarRocks感知到,不用做复杂的同步。缺点也很突出:如果没做优化,大查询会慢到离谱,甚至卡壳,比如之前有业务同事查全量用户行为,等了10分钟都没出结果,最后发现是全量扫了所有Iceberg文件,根本没剪枝。
1.3 第一次踩坑的真实案例
我之前碰到过一个情况:业务要统计2024年5月的电商用户购买量,写的SQL是直接查Iceberg表,没有加日期过滤,结果执行了5分12秒才出数据。查StarRocks的FE日志,发现这个查询读了2800多个Iceberg小文件,总数据量120G,相当于把所有存在Iceberg里的用户日志都扫了一遍,自然慢。问题就出在没做分区剪枝,把所有日期的文件都读了,浪费了大量IO。
二、拆解StarRocks查Iceberg的性能瓶颈
2.1 没做分区剪枝的全文件扫描
这个是最常见的瓶颈,Iceberg的数据是按分区存的,比如按dt(日期)分,每天的数据存在对应的分区文件里,如果查询的时候没加分区字段的过滤条件,或者过滤条件没用到分区,StarRocks就会把所有分区的文件都读一遍,不管有没有用。就像你找5月的书,却把整个图书馆的书都翻了,速度肯定慢。举个坏的SQL例子:
SELECT user_id, action FROM starrocks.iceberg_db.user_behavior WHERE action = 'buy';
这个SQL里的action不是分区字段,没有加dt过滤,所以StarRocks会扫所有dt的文件,这就是全文件扫描,数据量大的时候必卡。
2.2 Iceberg小文件太多导致的元数据开销
Iceberg的写操作如果是小批量写,会生成很多小文件,比如每天写100个小文件,一个月就是3000个。StarRocks读这些小文件的时候,每个文件都要读元数据(比如文件大小、分区信息),元数据多了,读取时间就花在处理元数据上了,不是读数据,是处理元数据的开销太大。比如之前的案例里,2800个小文件,光是处理元数据就花了2分钟,剩下的才是读数据的时间。
2.3 没做列裁剪的冗余IO
列裁剪就是只查需要的字段,不读没用的字段,比如刚才的坏SQL里只查user_id和action,但是Iceberg文件里还有设备号、IP、时间戳这些没用的字段,StarRocks会把所有字段都读出来,再过滤,等于多读了几倍的没用数据,IO自然大。就像你拿快递,只需要拿手机,却把整个快递箱都拎回来,白跑一趟。
三、分区剪枝优化的实操步骤与案例
3.1 核心原理通俗讲
分区剪枝的核心就是:Iceberg是按分区存数据,StarRocks拿到查询条件后,会先看条件能不能对应到分区,只扫对应分区的文件,其他分区的文件直接跳过,相当于你去图书馆只去5月的书架找书,不用动其他书架的,速度快很多。前提是你的查询条件必须用到Iceberg的分区字段,而且是直接用分区字段的范围或等值过滤,不能用函数套。
3.2 具体优化操作(单一技术栈:StarRocks SQL)
我们继续用之前的电商用户行为表,分区字段是dt(日期,格式为YYYY-MM-DD),要优化刚才的坏SQL,只需要加上dt的范围过滤,把查询限制在5月份,这样就能触发分区剪枝。优化后的SQL:
SELECT user_id, action FROM starrocks.iceberg_db.user_behavior
WHERE dt >= '2024-05-01' AND dt <= '2024-05-31' AND action = 'buy';
这个SQL里的dt是分区字段,StarRocks会自动识别,只扫2024年5月的分区文件,其他月份的直接跳过。
3.3 验证剪枝是否生效的方法
怎么确认优化有用?用StarRocks的EXPLAIN命令,看查询计划里有没有PARTITION PRUNING的字样。比如执行:
EXPLAIN SELECT user_id, action FROM starrocks.iceberg_db.user_behavior WHERE dt >= '2024-05-01' AND dt <= '2024-05-31' AND action = 'buy';
在输出的计划里,如果看到"Partition Prune Predicates: [dt >= '2024-05-01', dt <= '2024-05-31']",就说明剪枝生效了,StarRocks只扫对应分区的文件,优化有效。
3.4 其他辅助优化实操
除了分区剪枝,还要处理小文件和列裁剪。小文件合并可以用Iceberg本身的命令,比如合并user_behavior表的小文件,命令如下:
# 合并Iceberg表的小文件,生成大文件提升读取效率,减少元数据处理开销
iceberg-cli rewrite-data-files --table iceberg_db.user_behavior --input-format parquet --output-format parquet
这个命令的作用是把分散的小文件合并成更大的文件,减少元数据处理的开销,提高读取效率。另外,列裁剪的话,只要确保SELECT后面只写需要的字段就行,比如刚才的优化SQL已经只选了user_id和action,就是列裁剪。
四、优化后的效果验证与注意事项
4.1 优化前后的效果对比
之前的坏SQL执行时间是5分12秒,读了2800个文件,120G数据;优化后的SQL执行时间是11秒,只扫了300个5月的文件,1.2G数据,速度提升了快30倍,效果非常明显。
4.2 优化的注意事项
第一,分区剪枝必须直接用分区字段,不能用函数套,比如不能写WHERE YEAR(dt) = 2024,这样StarRocks没法反推dt的范围,剪枝会失效,必须直接用dt的范围或等值条件;第二,分区字段的选择要合适,不能太细(比如按小时,文件太多)也不能太粗(比如按年,剪枝效果差),一般按天或周比较合适;第三,Iceberg的分区必须提前建好,不能随便改分区规则,不然剪枝会失效;第四,小文件要定期合并,比如每天下班的时候跑一次合并命令,避免小文件堆积,影响查询效率。
五、总结
StarRocks直接查Iceberg的核心瓶颈就是没做分区剪枝导致的全文件扫描,加上小文件和冗余字段的开销,优化的核心就是用好分区剪枝,只扫必要的分区,同时配合小文件合并和列裁剪,就能大幅提升查询速度。这个方案不需要复杂的配置,只需要写SQL的时候注意加分区过滤,再定期合并小文件,就能解决大部分性能问题,适合不同基础的开发者上手,不管是新手还是有经验的技术人员,都能快速落地。
评论
围绕“手把手使用StarRocks直接查询数据湖Iceberg格式文件时的性能瓶颈分析与分区剪枝优化策略深度探究实操方案与案例完整指南”参与讨论