首页 > 编程知识 正文

快手 clickhouse,clickhouse projection

时间:2023-05-05 00:52:28 阅读:244813 作者:1605

ClickHouse的Projection原理解析 本文是ClickHouse的Projection特性在快手的实际应用

1.ClickHouse在快手OLAP的服务

ClickHouse在快手内部是作为OLAP引擎,提供多集群架构,对于不同业务有不同的集群保障,上层是查询代理层,进行统一的查询管控和接入路由以及统一的监控服务,这样可以把ClickHouse“手动挡”应用模式逐步变成一个可用的服务,提供给用户。同时最底层的数据写入,由于ClickHouse的数据写入需要一些相关的知识,所以把ETL服务抽取出来,用户只要告诉我们,你从哪个实时流进来,或者从哪个离线的数据源过来,我们就可以提供整套的ETL服务进行包装。

提供的服务包括:支撑留存计算、AB实验、音视频分析、风控预警等都在ClickHouse上有所应用。整体的规模大概是每天百万查询接入,上百TB存储,上千台ClickHouse节点。

2.开源竞品对比

从上图中可以看出,ClickHouse特性还是比较明显的,就是明细查询和实时写入速度特别快,分析功能很强,支持高阶函数,可以做很多传统SQL做不到的事情,开发起来也比较顺手。
ClickHouse管控能力比较弱,之前也提到了,它是一个“手动挡”应用模式,直接拿来用还是比较费劲的,因此我们通过完善的架构设计和外围设施,增强了它的管控能力,达到了生产可用的效果。

其次就是事务能力,事务往往伴随着性能开销,在互联网分析场景中不是一个高优的需求。
第三个是物化存储能力较弱,而物化是OLAP分析的核心,因此是亟需解决的。
ClickHouse物化存储有以下几个痛点问题:

仅支持一种列排序方式,虽然社区提供了一些解决方式如Z-Curve、Skip-index,但是都存在一些问题。OLAP预聚合模型需要手动操作,需要指定指标、维度,然后查询根据指定好的进行改写,这样才能命中物化的结果。同时明细数据也就不存在了,需要存多个维度表,才能达到预聚合模型的要求。ClickHouse物化视图无一致性保证,在明细表里做聚合查询和在物化表里做聚合查询出来的结果可能不一样。

3.快手的解决方案

快手提供了一个解决方案——Projections解决了前面提到的三个问题,其方法如下:

Projection按照不同列进行数据重排Projection使用聚合查询在源表上直接定义出预聚合模型Projection查询分析能自动选择最优Projection进行查询优化,无需改写查询Projection在任一时刻针对任一数据变换操作均提供一致性保证


这个功能目前已经合并到master,预计下个月会release,是ClickHouse开源5年来最高赞的PR,在快手内部已经使用了3个多月,也欢迎大家尝试使用。

4.Projection概念

Projection是一组列的集合,用建表语句可以定义,这个概念在C-Store提出后,他们成立了一个公司叫Vertica,做列存数据库,Projection在Vertica数据库中落地发展,不仅仅是按照不同的顺序做存储,同时还支持部分的聚合函数,比如sum、min、max以及一些上卷的优化,可以通过Projection的机制来完成。
我们借助了Projection的思想来进行了一些扩展,使其可以在ClickHouse上用任意的聚合函数、任意的自由组合参与数据上卷计算,意思就是只要你能写出来的SQL语句和分析型聚合函数,都可以进行预计算,并且保证前面提到的优势;第二点是我们支持一张表部分的数据挂载Projection,部分数据不挂载,这样的优势在于我们可以不丢失ClickHouse本身的特性如:实时写入的能力,同时查询也能够受益。

5.Projection用例介绍

这里我们使用视频分析日志表来进行演示,这个表每秒大概2万条记录数,存在两列,user_id和device_id,基数大概两亿的规模。同时还有一个domain的低基数列,表示域名,然后视频的属性包括bytes、duration两个指标维度。它的排序规则是按照user_id、device_id的顺序来进行排序。

如果按照user_id来查,ClickHouse会很高效,因为能够快速的命中user_id=100的数据,在有序的结构中,通过二分查找或者其他的一些机制,快速的定位到数据之后,把数据取出来进行分析,实际上扫描的数据量是很少的,远比原来的十几亿行数据少。
但是如果我们是用device_id进行查询的话,由于优先的第一顺序不是按照device_id来排的,排序索引基本失去了意义,最终的扫描就是全表扫描,对于这样的一个查询,在ClickHouse 中,大概就要消耗8秒的时间,基本上是不可并发的,只能等待这个点查询返回。
我们尝试为这个表建一个Projection,建Projection的操作很简单,就是对这个表进行一个alter的操作。Projection里面定义了你需要查询的那些列,然后按照device_id进行存储,选出这些列之后,由于它是lazy的模式,可以手动进行物化。为了演示效果,我们手动对这个Projection:p_norm进行MATERIALIZE操作。
操作完成之后,重新执行上面的查询,查询不需要任何的修改,可以看出性能提升了153倍,就是说索引生效了,生效的就是刚刚的Projection。

6.Projection实际应用案例

在实际应用案例中,会有这样的场景:一个用户,在行为表中,可能会有多种口径的查询方式,比如user_id ,phone_num,device_id,不同的人会通过不同的方式去查,这样在取数的时候,通过一维的排序肯定是不够的,如果通过稀疏索引或者二级索引也会带来其他的问题。如果用Projection的话,可以根据不同的口径,选出需要的列,进行异构多序存储,这样就可以针对不同用户的取数口径,都能够达到提速的效果。
再来看一个Projection的用例,比如按照域名聚合(大概100个基数),来做当日视频流特点分析,这个其实在ClickHouse里面性能已经很不错了,17亿的数据,ClickHouse单机大概11秒就跑结束了,已经可以体现它的高效性,但是仍然不足以去渲染看板。看板是希望能够即席分析,点开就能看到。
我们现在为这个查询定义一个预聚合的Projection,只需要表明需要查询的加速是什么样的,比如就是group by以hour为粒度,以domain为维度,然后查看sum和avg的结果,进行MATERIALIZE的操作。
重新执行刚才的查询语句,可以发现提升非常明显,相当于把前面所有的IO操作都省了,只要读最后预聚合的中间结果,同时计算成本也省了,不需要额外预聚合的CPU开销。
当我们要做一个聚合统计分析提速,有一张底表,会有一系列的维度和指标,正常情况下,需要提速的过程是一个ETL流程,我们需要为每一个不同的Topic准备一些特殊的表如:实验表、标签表等,然后进行分析建模。而有了Projection之后,可以把这些步骤都省略了,针对底表,我们可以根据end to end去看报表,看看数仓需要哪些查询,我们做一些分析,把这些查询汇总、归一,建几个Projection,这样所有的报表看板就及时生效了,省去了之前的ETL流程和数仓建模的过程,使看板智能提速;并且由于我们只使用了底表,原始明细数据都在,保障了数据一致性。

7.Projection原理与实现

Projection的主要构件包含三个部分,分别是:Projection定义、Projection存储、Projection查询分析。
Projection定义:可以通过用户查询直接定义,也可以自动推断Projection类型和相关属性。
Projection存储:Projection存储主要就是解决一致性的问题,怎么样在ClickHouse物化视图不一致的情况下,通过存储的设计,把物化出来的结果和原始的结果进行强一致的绑定。
Projection查询分析:回溯分析查询计划,使用标准表达式名称进行匹配验证,使得可以命中Projection,得出正确的查询结果,并根据选出来的Projection重建执行计划,完备的实现一致性的查询效果。

8.Projection查询分析

还是上面的video_log表,我们刚才也创建了Projection,那怎么知道下面的查询能够命中Aggregate Projection呢?事实上,它肯定是能命中原表的,否则查询就报错了。我们现在知道,它是能够命中p_agg这个Projection的,在这个过程中,首先把这个查询计划展开一下。
首先查询需要从存储里把列需要的数据读出来,然后进行有效的过滤,最后准备好要聚合的参数,比如bytes、duration,最后完成聚合的动作。我们需要看每一步Projection的数据能不能提供他们所需要的输入,这样的话就能知道这个Projection是否命中。接下来详细的看看:
Aggregation这一层涉及到参数准备的过程,就是hour、domain、sum(bytes)、avg(duration)这几个环节。需要提供的数据是domain、bytes、duration。
而这些数据,我们可以直接从Projection中获取到,因为在查询定义的过程中,就已经知道这个查询会分解出什么样的数据输出的需求,于是就可以通过这样的方式去命中Projection所产生的输入。
这个模式还可以继续推导,再看看上一个环节:
这里有一个WHERE执行树,WHERE执行树本质上是一个DAG表达式,它表达的意思是:现在有一组列,这一组列怎么样通过不断的变换形成新的列,最后产生一个谓词equals,判断这个谓词是真还是假。
Projection也是可以满足这个谓词的。从后往前推,它支持toStartOfHour,然后把最底下的输入截断,实际上,Projection里面没有datetime这一列,当然,它也不需要这一列,因为上面的那些列就可以满足整个DAG的计算,因此,在WHERE表达式这一级上,Projection也是可以满足的。
同理,不管是Prewhere还是Row Policy等一直到起点,Projection都可以满足之后,这个Projection就是一个有效的命中。
找到有效命中的Projection之后,要在所有合法的Projection候选中,找到其中最优的方式。因为Projection涉及到很多方面:一个是类型多,有Normal Projection、Aggregate Projection,在同一个查询里都有可能命中;同时Projection是个lazy的概念,可能不同的Projection物化程度并不一样,怎么样能够有效地发现哪一个Projection是最好的?这时候我们可以对每一个候选进行索引分析,得出其预期数据扫描量,并缓存结果,这样就能有效的判断这个Projection是好还是不好。这样只需要选择预期扫描数据最少的Projection,不用区分Projection类型是normal还是aggregate,只要读取的数据量少,计算量肯定也少,那么一定是最优的。预期的扫描量包含了Projection的物化程度。然后再把整体的索引分析的结果进行复用,避免重复的进行索引分析。当最终选择某个Projection之后,将利用前述的回溯分析过程倒推Projection所需要的执行管道,再把Projection的执行逻辑和normal的执行逻辑进行合并,得到一个最终的管道,使得查询在任何时刻、Projection的任何物化程度下,执行结果都是一致的,这样就实现了前述的一致性的问题。

9.一致性保障

一致性保障从以下三个方面讨论:

INSERT:当数据块写入时,其作为数据源向所有定义的Projections提供输入,形成Projection Parts,最终和原始数据合并构建出带有Projection的Part目录。SELECT:当查询命中某一Projection时,形成的查询计划将确保所有数据产生符合预期的结果。针对Projection Parts的数据,将在运行时动态构建并在不引入额外计算开销的前提下与其余数据合并。MUTATION:Projection在定义时记录了其关联依赖的原始列信息。当对应的列发生变化时,所有相关的Projection将被重新物化,形成新的Part将包含一致的Projection Part进行原子提交。

10.Projection优缺点

Projection优点:

SELECT,INSERT,UPDATE,DELETE等操作的一致性保障查询无需任何改动就能命中Projection,并自动匹配最优Projection进行计算可直接通过待优化的查询进行定义,用户要优化一个查询,可以把查询丢给Projection,做一些中间阶段的预聚合,并且预聚合的过程能够自动泛化匹配其他查询

Projection缺点:

现在的Projection是基于Part级别的,无法跨Part聚合,预聚合的粒度还是有一些限制无法脱离原始表存储,无法使用不同的生命周期与存储介质配置不支持join,还是一个大宽表的模型,不过对于物化来说,用join的还是比较少

11.生产实测结果
接下来说一下生产实测效果。首先,这个Projection包括物化的效果基本都是跟数据集和查询紧相关的。数据集大小:每天350亿条记录,按照10分钟为粒度,取某一个维度列进行预聚合,聚合比大概是十万分之四。这个效果分两部分:一个是聚合函数的使用量,比如说一个、两个或者三个的使用量,这时候提升的效果基本上是根据聚合函数的使用多少来体现的。比如最后这个用了三个聚合函数,可能提升的效果就更明显,因为你的计算量减少的更多;第二个是并发层次,Projection基本上不需要什么计算资源,所以在并发很高的情况下,并不会有太多提升,但是它减少了很多的计算资源开销,因此Projection做看板渲染是非常友好的,比如说多个人同时打开看板,执行很多个聚合查询,查询并发可以提的非常高,可以同时把看板渲染出来,原始表的话可以就一个看板,打开之后,别人就刷不出来了。
ClickHouse所有的查询信息都是白盒的,所有东西都提供规范化的查询分析,每个查询带有一种去掉所有的literal,就是字面量。去掉literal之后,我们可以做规范化的查询,可以每天去看一下查询日志里面聚合函数分别有哪些能够命中,然后我们得出值得建哪些查询的物化,建出来之后,去找一些看板来看,基本上一个看板,比如12张图表,原来渲染时间要30秒,等半天才出来4张图表。加了Projection之后,1秒就出来了,体验感非常好。统计Projection所有的开销,其实存储开销还是比较少的,刚才说的,聚合效果足够的话,存储开销不多,大概20%。同时,写入还有MERGE并没有观测到可见的影响,同样能够保持百万级别的每秒写入数量。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。