一、背景
OLAP引擎在得物的客服、风控、供应链、投放、运营、ab实验等大量业务场景发挥重要作用,在报表、日志、实时数仓等应用场景都有广泛的应用。
得物引入和使用OLAP引擎的过程中,每个业务都基于自己的需求选择当时最适合自己的引擎。现在得物内部同时使用阿里云上的Hologres、ADB、Clickhouse与ECS自建的开源Clickhouse与StarRocks 5种引擎产品,从业务使用成本和运维维护的角度长远看都是不利的。
2024年,我们开始向只保留1到2个引擎的方向努力。最近,我们把一套4000+核的业务,最复杂的Clickhouse集群迁移到了存算分离的自建StarRocks上,成本节约40%,查询耗时降低一半,单节点不可用导致的集群不可用耗时从15分钟减少到了30s。
二、Clickhouse在大数据量下的困境
Clickhouse虽然单机性能首屈一指,但分布式架构存在水平扩展性、元数据管理、数据一致性、join查询性能等一系列问题。被迁移的这个Clickhouse集群服务于得物的智能运营平台,使用了超过4000核,存储超过500TB。随着业务增长,集群面临一些实际问题。
物化视图缺乏透明改写能力
智能运营平台日常需要查询周/月/季/年 环比/同比,查询时间跨度长,并且为了加速查询建立了40+物化视图。Clickhouse没有自动的透明物化视图改写能力,需要由外部代码主动管理、创建物化视图和改写SQL去查询物化视图。在需要重刷历史数据时,由外部代码管理的物化视图的重刷尤为复杂。
缺乏离线导入功能
开源Clickhouse缺少bulkload能力,智能运营需要每天从离线平台导入大量数据到Clickhouse,导入链路存在格式转换,效率低的问题,并且会占用大量Clickhouse集群资源,导入链路的数据产出有时会晚于基线。
扩容困难
虽然使用了很多物化视图,但集群负载仍然开始接近硬件能力上限。业务想要继续扩容,但面临了两个问题:
- 垂直扩容没有空间:集群使用的Clickhouse单机规格已经是顶配,没有升配空间。
- 水平扩容停机需停机一周:因为Clickhouse水平扩容后为了查询正确性考虑,已经按指定字段分桶的表,需要重新导入数据才能正确的resharding,这个过程需要一周的停服扩容和导入。
业务方只能搭建了一个小一些的备份集群,存储最核心的数据,与主集群构成双活来分流部分查询,来减小主集群负载,这样增加了50%+成本。
在得物增加了对StarRocks的研发投入后,智能运营下定决心基于StarRocks存算分离做POC,并最终顺利迁移到自建的StarRocks上。
三、基于StarRocks降本增效
存算分离带来成本下降
StarRocks 3.0起支持存算分离,在3.3版本已经比较成熟了。
StarRocks存算分离架构:
(cn是compute node,是无状态计算节点+本地缓存盘)
安全可靠使用的单副本
对比存算一体的3副本模式,存算分离使用单副本。存算分离的全量数据数据存储在远端对象存储上(上图的Distributed Storage,我们使用的是阿里云的OSS),即使CN节点挂了,其他CN节点也仍然可以查询到数据(虽然需要重新拉取缓存数据,查询耗时会增加),所以是可以安全可靠使用的单副本模式。这带来2/3的存储成本下降(本地盘只作为data cache之用)。
只缓存必要数据
并且存算分离不需要把所有的数据都存储在本地盘,而只需要缓存常用数据即可,在单副本之上又节省大笔存储成本,并且查询性能在使用本地缓存后能做性能一致。
并且大量使用物化视图,减少基表实际需要存储在data cache中的数据量。
上图说明506TB的数据实际只在缓存中存储了342TB经过评估存算分离部署模式能带来40%+的成本下降,存储成本下降1 - (1/3*3/5)=4/5。
扩缩容无需搬迁数据
StarRocks存算一体可以任意水平扩容,无需停机,扩容后自动均衡搬迁数据。StarRocks存算分离更做到了扩缩容无需搬迁数据,扩容的新节点马上就可以被利用,这在使用容器方案部署StarRocks时,尤为方便。
在复杂SQL join能力上大幅领先Clickhouse
https://docs.starrocks.io/zh/docs/benchmarking/SSB_Benchmarking/
StarRocks更适合星型模型,并且StarRocks与Clickhouse单表查询性能不相伯仲(数据来源:clickbench 单机性能测试 https://benchmark.clickhouse.com/)
Clickhouse支持的join类型非常有限且使用复杂,StarRocks支持各种Join类型且符合标准SQL语法的预期,这让用户无需特别关注Join的特殊写法带来的性能问题(指Clickhouse的分布式和local表join问题),专注于业务逻辑即可。
高效的离线导入
从Clickhouse换成StarRocks后,离线导入的流程发生了变化。
首先在离线平台新建一个OSS外表,然后执行离线平台的insert sql select语句从离线平台读取数据以parquet格式写入到OSS外表中,然后StarRocks从对象存储(OSS)直接broker load导入数据。对比Clickhouse只能insert导入,虽然还是使用StarRocks集群资源,但通过控制broker load并发可以把集群资源的消耗控制在可接受范围内。压测显示离线导入耗时比Clickhouse方案减少2/3。
重度使用物化视图进行提效
透明物化视图改写
查询可以自动透明改写到物化视图,用户无需改SQL,并且保证数据的正确性。以往需要用户在外围主动管理Clickhouse物化视图构建,数据重刷和查询改写,这引入了额外的复杂性。现在物化视图由StarRocks自管理,不再需要外部代码主动改写,减少了出错的可能,也提供了不侵入用户逻辑即可提升性能的方式。
使用技巧
1、不命中物化视图时,在资源组中限制大表时间跨度超过8天就不允许查询。
2、approx_count_distinct改写成hll_union_agg(hll_hash(col))让查基表与查物化视图的结果完全一致
3、使用明细物化视图减少数据读取量
4、物化视图只基于单表,方便在各种join sql语句中复用
5、一个物化视图包括尽量多的指标,查询时一次性查多个指标
6、materialized_view_rewrite_mode=‘force‘让一个查询中多个countdistinct也能命中物化视图
物化视图
推荐手工对SQL分析和创建物化视图毕竟效率有限,所以我们开发了物化视图推荐功能。
1、通过在fe中记录SQL结构,在外部实现基于单表的物化视图推荐程序
2、能做到对表/物化视图字段的在过滤条件中的命中次数进行统计,用来判断哪些字段做排序键能适配更多的查询
3、能做到对单表的子语句用到的指标和维度列进行分析,找到有高收益的潜在物化视图,并且排除已经存在的物化视图。
4、物化视图启用collocate group现在物化视图推荐功能已经初步上线,并还在持续优化中。
优化选择策略和性能
1、优化选择的性能,跳过物化视图与无关表的匹配(https://github.com/StarRocks/starrocks/pull/51010)。
2、提早剔除一些不包含查询需要的列的物化视图,减少了后续匹配测试的开销(https://github.com/StarRocks/starrocks/pull/51044)。
3、优先选中排序键匹配查询过滤条件的物化视图,再优先选行数最少的物化视图(https://github.com/StarRocks/starrocks/pull/51511)。
扩展物化视图可用场景
物化视图作为提升查询性能的最关键也是最有效的手段,在3.3刚发布时存在一些BUG,导致很多场景下物化视图不能被命中。其中社区帮助修复一些问题,我们也修复8个命中问题,并且都已经提了PR并合并。(https://github.com/StarRocks/starrocks/pull/46472
https://github.com/StarRocks/starrocks/pull/47648
https://github.com/StarRocks/starrocks/pull/48001
https://github.com/StarRocks/starrocks/pull/48092
https://github.com/StarRocks/starrocks/pull/48142
https://github.com/StarRocks/starrocks/pull/48773
https://github.com/StarRocks/starrocks/pull/48984
https://github.com/StarRocks/starrocks/pull/49168)
修复物化视图刷新问题
1、支持刷新顺序为从新分区到老分区,这样最近的数据能最快得到加速(https://github.com/StarRocks/starrocks/pull/46691)。
2、修复了force刷新不生效的问题(https://github.com/StarRocks/starrocks/pull/52081)
3、修复物化视图在image创建后被非预期inactive,重启后被强制刷新的问题(https://github.com/StarRocks/starrocks/pull/51388)。
4、反馈给社区修复了fast schema evolution导致的mv非预期刷新问题
四、具体迁移过程
Clickhouse灰度迁移StarRocks
智能运营并不直接查询StarRocks,而是经过中间的ONE DSL平台翻译DSL(一种类SQL)成Clickhouse/StarRocks SQL后发给后端引擎。通过查询时指定DSL目标翻译类型,决定发到Clickhouse或者StarRocks,这样在迁移过程中可以按接口灰度,然后逐个的迁移,有问题可以随时单独回滚某个接口。
语法兼容
兼容Clickhouse函数
- 我们仿造StarRocks中已有的Trino的AstBuilder实现了Clickhouse的AstBuilder,通过新增sql_dialect=clickhouse选项,让兼容改动不污染StarRocks原来的行为。
- 我们兼容了业务方用到的72个函数,覆盖了最常用Clickhouse函数,业务方大部分情况下不需要修改查询SQL,从而减小了迁移需要的工时。可以做到一个Clickhouse函数对应一个StarRocks函数或者多个函数的组合,并且将来新增一个Clickhouse函数映射是非常容易的。
- 我们还实现了Clickhouse的ArrayJoin函数,能在StarRocks中使用一样的方式进行列转行:-- StarRocks原生写法select u from tbl, unnest(array_column)-- ArrayJoin的写法select arrayJoin(array_column) from tbl
不过Clickhouse的arrayJoin语法仍然是不支持的,需要手工改写成StarRocks的unnest语法。
此外,我们还实现了Clickhouse式的别名引用,可以在select的后段引用前段定义的别名(https://github.com/StarRocks/starrocks/pull/43919):Select id as id_alias, sum(a) as b, concat(b, ',', '_') from xx where id_alias > 100 group by id_alias
Clickhouse表/物化视图转换工具
在引擎之外做了转换工具,能够以程序的方式将Clickhouse的建表和建物化视图的DDL转为StarRocks的DDL,节约了手工转换的时间的同时也避免手工转换时可能发生的改写错误。
优化查询性能
修复特定性能问题
- 前面提到的修复多个场景的物化视图命中问题和优化物化视图选择策略性能
- 分区字段查询带函数导致物化视图分区裁剪失败(https://github.com/StarRocks/starrocks/pull/46786)
- 优化数值与字符串类型隐式转换(https://github.com/StarRocks/starrocks/pull/50168):对应sql性能提升20倍。
- 优化date/datetime被当做字符串使用时的规则,从而命中稀疏索引命中(https://github.com/StarRocks/starrocks/pull/50643):对应sql性能提升30倍。
- 减少查询时存算分离staros的rpc调用次数(https://github.com/StarRocks/starrocks/pull/46913) + 简易rpc结果缓存:查询耗时普遍减少3s+。
创建大量物化视图
通过持续对io高/cpu高的查询针对性优化,发现有大量查询是可以用物化视图加速的。在原有Clickhouse迁移过来的物化视图之外,又增加了许多物化视图。目前为止已经有140+物化视图。最大的3张表占总存储的50%,对应了60+物化视图。
表结构优化
StarRocks所有的表结构都继承自Clickhouse的表结构,在实际线上运行过程中,发现了很多schema不合理的地方。通过对表和物化视图的排序键、分桶键、分桶数量的优化让其符合最常用的过滤条件中的字段的顺序,性能得到极大提升(如何设计合理排序键,以便查询利用前缀索引加速https://docs.starrocks.io/zh/docs/table_design/indexes/Prefix...)。比如某张表的排序键优化后,查询性能提升了150倍。
找到问题SQL让用户修改写法
通过持续对io高/cpu高的查询针对性优化,发现有很多StarRocks rule不能等价转换,但从业务角度看是等价的小改动,可以显著提升性能的场景。
1、换种写法改进plan
比如下面的极端case的改动提升了250倍性能,因为它导致估算的行数变少,从而优化了join order的顺序,生成了更好的plan。
brand_id IN (
SELECT brand_id FROM olap_znyy.st_itg_spu_info_all
WHERE brand_level_name IN ('A级品牌', 'B级品牌', 'C级品牌'))
改成
brand_id IN (
SELECT distinct brand_id FROM olap_znyy.st_itg_spu_info_all
WHERE brand_level_name IN ('A级品牌', 'B级品牌', 'C级品牌'))
2、把limit提前到join语句里
业务上会先join维度表取得更多字段,最后再order by join左分支中的字段再limit。改成把limit放到join左分支中,然后再join维度表。这样减少了join的行数,降低70%耗时。
3、去除join key上不必要的函数调用
业务方join on upper(query)上,但所有join到的表的数据其实都是小写(这个信息只有业务方自己才知道),根本不需要转成大写后再join。去除upper函数再join提升至少一倍性能。
4、去除不必要的toString
用户喜欢join时把数值字段toString之后join,这样去掉toString也可以减少计算量提升性能。除了join key,还有很多select地方也喜欢用toString,也都是非必要的。
5、子查询合并,消除join。收益:性能提升约50%
6、维度表过滤条件下推,收益:查询超时->0.1s
运维和可观测性增强
除了上面引擎方面的改动,还配合业务方的管理需求,支持了一些特定的Api,比如:
- 资源水位API
- SQL复杂度因子API
- 根据表名获取物化视图列表和物化视图的具体列信息API
- 指定custom_query_id,可以异步kill query的能力(https://github.com/StarRocks/starrocks/pull/47632;https://github.com/StarRocks/starrocks/pull/48114)
五、成果展示
得到社区的给力支持,以及与智能运营团队紧密的合作,我们最终顺利从Clickhouse迁移到StarRocks,并且在查询性能耗时减少为Clickhouse查询耗时的一半。
以下是获得的收益:
- 成本收益:智能运营底层AP引擎费用下降40%,存储费用下降5/6。
- 性能和体验收益:智能运营的大盘的P95耗时从最初的8.5s降低到当前的4.3s;P0页面低基维度的耗时从最初的9.07s降低到4.77s,高基维的P95耗时从24.38s降低到11.94s; 查询成功率从98.57%提升到99.44%。
- 数据时效性收益:在双跑期间clickhouse的基线SLA达成率是99.42%,StarRocks的基线SLA达成率是100%。
- 稳定性收益:集群因为单节点异常导致集群不可用的时间从15min缩短到30s内。
我们与社区保持了密切的接触,会提交一些重大issue,同时也贡献我们的修复给社区。迁移过程中共提交PR 28个,已merge 24个(https://github.com/StarRocks/starrocks/pulls?q=is%3Apr+author%3Akaijianding)。在迁移过程中,开发了Clickhouse函数兼容等10+功能,修复40+个问题(包括反馈给社区修复的)。
六、总结
此次迁移达成了预期的成本和性能的收益目标,也拓展了集群未来的成长空间,也让业务团队和引擎团队都更加的了解StarRocks,收获大量迁移经验,为将来迁移其他业务提供了有说服力的范例。
在迁移过程中,我们与社区保持了紧密的联系,获得了社区大量帮助,也贡献了大量patch给社区,减少社区其他人需要踩的坑。在我们得物内部StarRocks的未来规划中,我们也将继续深度参与社区。
往期回顾
1.基于Redis内核的热key统计实现方案|得物技术
2.盘点这些年搭建器在用户体验优化的实践|得物技术
3.Java性能测试利器:JMH入门与实践|得物技术
4.彩虹桥架构演进之路-负载均衡篇|得物技术
5.解析Go切片:为何按值传递时会发生改变?|得物技术
文 / Kaijian
关注得物技术,每周更新技术干货,要是觉得文章对你有帮助的话,欢迎评论转发点赞~未经得物技术许可严禁转载,否则依法追究法律责任。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。