关于clickhouse:字节跳动基于ClickHouse优化实践之多表关联查询

11次阅读

共计 3511 个字符,预计需要花费 9 分钟才能阅读完成。

更多技术交换、求职机会、试用福利,欢送关注字节跳动数据平台微信公众号,回复【1】进入官网交换群

置信大家都对赫赫有名的 ClickHouse 有肯定的理解了,它弱小的数据分析性能让人印象粗浅。但在字节大量生产应用中,发现了 ClickHouse 仍然存在了肯定的限度。例如:

  • 短少残缺的 upsert 和 delete 操作
  • 多表关联查问能力弱
  • 集群规模较大时可用性降落(对字节尤其如此)
  • 没有资源隔离能力

因而,咱们决定将 ClickHouse 能力进行全方位增强,打造一款更弱小的数据分析平台。本篇将具体介绍咱们是如何增强 ClickHouse 多表关联查问能力。

大宽表的局限

数据分析的倒退历程,能够看作是一直谋求剖析效率和剖析灵便的过程。剖析效率是十分重要的,然而并不是须要有限晋升的。1 秒返回后果和 1 分钟返回后果的体验是天壤之别,然而 0.1 秒返回后果和 1 秒返回后果的差距就没那么大了。因而,在满足了肯定时效的状况下,剖析的灵活性就显得额定重要了。

起初,数据分析都采纳了固定报表的模式,格局更新频率低,依赖定制化的开发,查问逻辑是写死的。对于业务和数据需要绝对稳固、不会频繁变动的场景来说固定报表的确就足够了,然而以现在的视角来看,齐全固定的查问逻辑不能充分发挥数据的价值,只有通过灵便的数据分析,能力帮忙业务人员化被动为被动,摸索各数据间的相干关系,疾速找到问题背地的起因,极大地晋升工作效率。

前面,基于预计算思维的 cube 建模计划被提出。通过将数据 ETL 加工后存储在 cube 中,保障领导和业务人员可能疾速失去剖析后果根底上,取得了肯定的剖析灵活性。不过因为维度固定,以及数据聚合后根本无奈查问明细数据,仍然无奈满足 Adhoc 这类即席查问的场景需要。

近些年,以 ClickHouse 为代表的具备弱小单表性能的查问引擎,带来了大宽表剖析的风潮。所谓的大宽表,就是在数据加工的过程中,将多张表通过一些关联字段打平成一张宽表,通过一张表对外提供剖析能力。基于 ClickHouse 单表性能撑持的大宽表模式,既能晋升剖析时效性又能进步数据查问和剖析操作的灵活性,是目前十分风行的一种模式。

然而大宽表仍然有它的局限性,具体有:

  • 生成每一张大宽表都须要数据开发人员不小的工作量,而且生成过程也须要肯定的工夫
  • 生成宽表会产生大量的数据冗余

方才有提到,数据分析的倒退历程能够看作是一直谋求剖析效率和剖析灵便的过程,那么大宽表的下一个阶段呢?如果 ClickHouse 的多表关联查问能力足够强,是不是连“将数据打平成宽表”这个步骤也能够省略,只须要保护好对外服务的接口,任何业务人员的需要都现场间接关联查问就能够了呢?

如何强化多表关联查问能力的?ClickHouse 的执行模式绝对比较简单,其根本查问模式分为 2 个阶段:

ByteHouse 进行多表关联的简单查问时,采纳分 Stage 的形式,替换目前 ClickHouse 的 2 阶段执行形式。将一个简单的 Query 依照数据交换状况切分成多个 Stage,Stage 和 Stage 之间通过 exchange 实现数据的替换,单个 Stage 内不存在数据交换。Stage 间的数据交换次要有以下三种模式:

  • 依照单(多)个 key 进行 Shuffle
  • 由 1 个或者多个节点汇聚到一个节点 (咱们称为 gather)
  • 同一份数据复制到多个节点 (也称为 broadcast 或者说播送)
    单个 Stage 执行会持续复用 ClickHouse 的底层的执行形式。

依照不同的性能切分不同的模块,设计指标如下:

各个模块约定好接口,尽量减少彼此的依赖和耦合。一旦某个模块有变动不会影响别的模块,例如 Stage 生成逻辑的调整不影响调度的逻辑。

模块采纳插件的架构,容许模块依据配置灵便反对不同的策略。

依据数据的规模和散布,ByteHouse 反对了多种关联查问的实现,目前曾经反对的有:

  • Shuffle Join,最通用的 Join
  • Broadcast Join,针对大表 Join 小表的场景,通过把右表播送到左表的所有 worker 节点来缩小左表的传输
  • Colocate Join,针对左右表依照 Join key 放弃相通散布的场景,缩小左右表数据传输

Join 算子通常是 OLAP 引擎中最耗时的算子。如果想优化 Join 算子,能够有两种思路,一方面能够晋升 Join 算子的性能,例如更好的 Hash Table 实现和 Hash 算法,以及更好的并行。另一方面能够尽可能减少参加 Join 计算的数据。

Runtime Filter 在一些场景特地是事实表 join 维度表的星型模型场景下会有比拟大的成果。因为这种状况下通常事实表规模比拟大,而大部分过滤条件都在维度表上,事实表可能要全量 join 维度表。Runtime Filter 的作用是通过在 Join 的 probe 端(就是左表)提前过滤掉那些不会命中 Join 的输出数据来大幅缩小 Join 中的数据传输和计算,从而缩小整体的执行工夫。以下图为例:

改善后的成果以 SSB 100G 测试集为例,不把数据打成大宽表的状况下,别离应用 ClickHouse 22.2.3.1 版本和 ByteHouse 2.0.1 版本,在雷同硬件环境下进行测试。(无数据表示无奈返回后果或超过 60s)

能够看到大多数测试中,ClickHouse 都会产生报错无奈返回后果的状况,而 ByteHouse 可能稳固的在 1s 内跑出后果。只看 SSB 的多表测试有些形象,上面从两个具体的 case 来看一下优化后的成果:

Case1:Hash Join 右表为大表

通过优化后,query 执行工夫从 17.210s 升高至 1.749s。

lineorder 是一张大表,通过 shuffle 能够将大表数据依照 join key shuffle 到每个 worker 节点,缩小了右表构建的压力。

SELECT
    sum(LO_REVENUE) - sum(LO_SUPPLYCOST) AS profit
FROM 
    customer
INNER JOIN
(
    SELECT
    LO_REVENUE,
    LO_SUPPLYCOST,
    LO_CUSTKEY
    from
    lineorder
    WHERE toYear(LO_ORDERDATE) = 1997 and toMonth(LO_ORDERDATE) = 1
) as lineorder
ON LO_CUSTKEY = C_CUSTKEY
WHERE C_REGION = 'AMERICA'

Case 2:5 张表 Join(未开启 runtime filter)

经优化后,query 执行工夫从 8.583s 升高至 4.464s。

所有的右表可同时开始数据读取和构建。为了和现有模式做比照,ByteHouse 这里并没有开启 runtime filter,开启 runtime filter 后成果会更快。

SELECT                                                                                                                                              
    D_YEAR,                                                                                                                                         
    S_CITY,                                                                                                                                         
    P_BRAND,                                                                                                                                        
    sum(LO_REVENUE) - sum(LO_SUPPLYCOST) AS profit                                                                                                  
FROM ssb1000.lineorder                                                                                                                              
INNER JOIN                                                                                                                                   
(                                                                                                                                                   
    SELECT C_CUSTKEY                                                                                                                                
    FROM ssb1000.customer                                                                                                                           
    WHERE C_REGION = 'AMERICA'                                                                                                                      
) AS customer ON LO_CUSTKEY = C_CUSTKEY                                                                                                             
INNER JOIN                                                                                                                                   
(                                                                                                                                                   
    SELECT                                                                                                                                          
        D_DATEKEY,                                                                                                                                  
        D_YEAR                                                                                                                                      
    FROM date                                                                                                                             
    WHERE (D_YEAR = 1997) OR (D_YEAR = 1998)                                                                                                        
) AS dates ON LO_ORDERDATE = toDate(D_DATEKEY)                                                                                                      
INNER JOIN                                                                                                                                   
(                                                                                                                                                   
    SELECT                                                                                                                                          
        S_SUPPKEY,                                                                                                                                  
        S_CITY                                                                                                                                      
    FROM ssb1000.supplier                                                                                                                           
    WHERE S_NATION = 'UNITED STATES'                                                                                                                
) AS supplier ON LO_SUPPKEY = S_SUPPKEY                                                                                                             
INNER JOIN                                                                                                                                   
(                                                                                                                                                   
    SELECT                                                                                                                                          
        P_PARTKEY,                                                                                                                                  
        P_BRAND                                                                                                                                     
    FROM ssb1000.part                                                                                                                               
    WHERE P_CATEGORY = 'MFGR#14'                                                                                                                    
) AS part ON LO_PARTKEY = P_PARTKEY                                                                                                                 
GROUP BY                                                                                                                                            
    D_YEAR,                                                                                                                                         
    S_CITY,                                                                                                                                         
    P_BRAND                                                                                                                                         
ORDER BY                                                                                                                                            
    D_YEAR ASC,                                                                                                                                     
    S_CITY ASC,                                                                                                                                     
    P_BRAND ASC                                                                                                                                     
SETTINGS enable_distributed_stages = 1, exchange_source_pipeline_threads = 32      

通过多表关联查问能力的加强,ByteHouse 可能更加全面的撑持各类业务,用户能够依据场景抉择是否将数据打成大宽表,均能取得十分良好的剖析体验。
之所以 ByteHouse 在多表关联场景体现如此杰出,其中一大起因就是因为字节自研了查问优化器,补救了社区 ClickHouse 的一大有余。

立刻跳转火山引擎 BytHouse 官网理解详情!

正文完
 0