源码分析 | ClickHouse和他的朋友们(15)Group By 为什么这么快

Clickhouse系列六:源码解读GroupBy 单机Clickhouse GroupBy的功能是被业界津津乐道的。那么它又是怎么能达到这么好的性能的呢?优化点在哪里呢。这里我们来去探索下Clickhouse关于GroupBy的秘密。 说到GroupBy那就不能不提聚合函数了,在Clickhouse里面关于GroupBy的设计是非常优秀的,Clickhouse计算引擎是通过火山模型来实现的,但是火山模型有一个非常大的性能损耗就是虚函数调用的开销 阅读详情

本文首发于 2021-01-26 21:31:12

《ClickHouse和他的朋友们》系列文章转载自圈内好友 BohuTANG 的博客,原文链接:
https://bohutang.me/2021/01/21/clickhouse-and-friends-groupby/
以下为正文。

在揭秘 ClickHouse Group By 之前,先聊聊数据库的性能对比测试问题。

在虎哥看来,一个“讲武德”的性能对比测试应该提供什么信息呢?

首先要尊重客观事实,在什么场景下,x 比 y 快?
其次是为什么 x 会比 y 快?

如果以上两条都做到了,还有一点也比较重要: x 的优势可以支撑多久? 是架构等带来的长期优势,还是一袋烟的优化所得,是否能持续跟上自己的灵魂。

如果只是贴几个妖艳的数字,算不上是 benchmark,而是 benchmarket。

好了,回到 Group By 正题。

相信很多同学已经体验到 ClickHouse Group By 的出色性能,本篇就来分析下快的原因。

首先安慰一下,ClickHouse 的 Group By 并没有使用高大上的黑科技,只是摸索了一条相对较优的方案。

一条 SQL

SELECT sum(number) FROM numbers(10) GROUP BY number % 3

我们就以这条简单的 SQL 作为线索,看看 ClickHouse 怎么实现 Group By 聚合。

1. 生成 AST

EXPLAIN AST
SELECT sum(number)
FROM numbers(10)
GROUP BY number % 3

┌─explain─────────────────────────────────────┐
│ SelectWithUnionQuery (children 1)           │
│  ExpressionList (children 1)                │
│   SelectQuery (children 3)                  │
│    ExpressionList (children 1)              │
│     Function sum (children 1)               │  // sum 聚合
│      ExpressionList (children 1)            │
│       Identifier number                     │
│    TablesInSelectQuery (children 1)         │
│     TablesInSelectQueryElement (children 1) │
│      TableExpression (children 1)           │
│       Function numbers (children 1)         │
│        ExpressionList (children 1)          │
│         Literal UInt64_10                   │
│    ExpressionList (children 1)              │
│     Function modulo (children 1)            │  // number % 3 函数
│      ExpressionList (children 2)            │
│       Identifier number                     │
│       Literal UInt64_3                      │
└─────────────────────────────────────────────┘

2. 生成 Query Plan

EXPLAIN
SELECT sum(number)
FROM numbers(10)
GROUP BY number % 3

┌─explain───────────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY))                                   │ 
│   Aggregating                                                                 │ // sum 聚合
│     Expression (Before GROUP BY)                                              │ // number % 3
│       SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│         ReadFromStorage (SystemNumbers)                                       │
└───────────────────────────────────────────────────────────────────────────────┘

代码主要在 InterpreterSelectQuery::executeImpl@Interpreters/InterpreterSelectQuery.cpp

3. 生成 Pipeline

EXPLAIN PIPELINE
SELECT sum(number)
FROM numbers(10)
GROUP BY number % 3

┌─explain───────────────────────┐
│ (Expression)                  │
│ ExpressionTransform           │
│   (Aggregating)               │
│   AggregatingTransform        │  // sum 计算
│     (Expression)              │
│     ExpressionTransform       │  // number % 3 计算
│       (SettingQuotaAndLimits) │
│         (ReadFromStorage)     │
└───────────────────────────────┘

4. 执行 Pipeline

Pipeline 是从底部往上逐一执行。

4.1 ReadFromStorage

首先从 ReadFromStorage 执行,生成一个 block1, 数据如下:

┌─number─┐
│      0 │
│      1 │
│      2 │
│      3 │
│      4 │
│      5 │
│      6 │
│      7 │
│      8 │
│      9 │
└────────┘
number类型为 UInt64

4.2 ExpressionTransform

ExpressionTransform 包含了 2 个 action:

  1. 名字为 number,type 为 INPUT
  2. 名字为 modulo(number, 3), type 为 FUNCTION

经过 ExpressionTransform 运行处理后生成一个新的 block2, 数据如下:

┌─number─┬─modulo(number, 3)─┐
│      0 │                 0 │
│      1 │                 1 │
│      2 │                 2 │
│      3 │                 0 │
│      4 │                 1 │
│      5 │                 2 │
│      6 │                 0 │
│      7 │                 1 │
│      8 │                 2 │
│      9 │                 0 │
└────────┴───────────────────┘
number 类型为 UInt64
modulo(number, 3) 类型为 UInt8

代码主要在 ExpressionActions::execute@Interpreters/ExpressionActions.cpp

4.3 AggregatingTransform

AggregatingTransform 是 Group By 高性能的核心所在。
本示例中的 modulo(number, 3) 类型为 UInt8,在做优化上,ClickHouse 会选择使用数组代替 hashtable作为分组,区分逻辑见 Interpreters/Aggregator.cpp

在计算 sum 的时候,首先会生成一个数组 [1024],然后做了一个编译展开(代码 addBatchLookupTable8@AggregateFunctions/IAggregateFunction.h):

static constexpr size_t UNROLL_COUNT = 4;
std::unique_ptr<Data[]> places{new Data[256 * UNROLL_COUNT]};
bool has_data[256 * UNROLL_COUNT]{}; /// Separate flags array to avoid heavy initialization.

size_t i = 0;

/// Aggregate data into different lookup tables.
size_t batch_size_unrolled = batch_size / UNROLL_COUNT * UNROLL_COUNT;
for (; i < batch_size_unrolled; i += UNROLL_COUNT)
{
    for (size_t j = 0; j < UNROLL_COUNT; ++j)
    {
        size_t idx = j * 256 + key[i + j];
        if (unlikely(!has_data[idx]))
        {
            new (&places[idx]) Data;
            has_data[idx] = true;
        }
        func.add(reinterpret_cast<char *>(&places[idx]), columns, i + j, nullptr);
    }
}

sum(number) … GROUP BY number % 3 计算方式:

array[0] = 0 + 3 + 6 + 9 = 18
array[1] = 1 + 4 + 7 = 12
array[2] = 2 + 5 + 8 = 15

这里只是针对 UInt8 做的一个优化分支,那么对于其他类型怎么优化处理呢?
ClickHouse 针对不同的类型分别提供了不同的 hashtable,声势比较浩大(代码见 Aggregator.h):

using AggregatedDataWithUInt8Key = FixedImplicitZeroHashMapWithCalculatedSize<UInt8, AggregateDataPtr>;
using AggregatedDataWithUInt16Key = FixedImplicitZeroHashMap<UInt16, AggregateDataPtr>;
using AggregatedDataWithUInt32Key = HashMap<UInt32, AggregateDataPtr, HashCRC32<UInt32>>;
using AggregatedDataWithUInt64Key = HashMap<UInt64, AggregateDataPtr, HashCRC32<UInt64>>;
using AggregatedDataWithShortStringKey = StringHashMap<AggregateDataPtr>;
using AggregatedDataWithStringKey = HashMapWithSavedHash<StringRef, AggregateDataPtr>;
using AggregatedDataWithKeys128 = HashMap<UInt128, AggregateDataPtr, UInt128HashCRC32>;
using AggregatedDataWithKeys256 = HashMap<DummyUInt256, AggregateDataPtr, UInt256HashCRC32>;
using AggregatedDataWithUInt32KeyTwoLevel = TwoLevelHashMap<UInt32, AggregateDataPtr, HashCRC32<UInt32>>;
using AggregatedDataWithUInt64KeyTwoLevel = TwoLevelHashMap<UInt64, AggregateDataPtr, HashCRC32<UInt64>>;
using AggregatedDataWithShortStringKeyTwoLevel = TwoLevelStringHashMap<AggregateDataPtr>;
using AggregatedDataWithStringKeyTwoLevel = TwoLevelHashMapWithSavedHash<StringRef, AggregateDataPtr>;
using AggregatedDataWithKeys128TwoLevel = TwoLevelHashMap<UInt128, AggregateDataPtr, UInt128HashCRC32>;
using AggregatedDataWithKeys256TwoLevel = TwoLevelHashMap<DummyUInt256, AggregateDataPtr, UInt256HashCRC32>;
using AggregatedDataWithUInt64KeyHash64 = HashMap<UInt64, AggregateDataPtr, DefaultHash<UInt64>>;
using AggregatedDataWithStringKeyHash64 = HashMapWithSavedHash<StringRef, AggregateDataPtr, StringRefHash64>;
using AggregatedDataWithKeys128Hash64 = HashMap<UInt128, AggregateDataPtr, UInt128Hash>;
using AggregatedDataWithKeys256Hash64 = HashMap<DummyUInt256, AggregateDataPtr, UInt256Hash>;

如果我们改成 GROUP BY number*100000 后,它会选择 AggregatedDataWithUInt64Key 的 hashtable 作为分组。

而且 ClickHouse 提供了一种 Two Level 方式,用语应对有大量分组 key 的情况,Level1 先分大组,Level2 小组可以并行计算。

针对 String 类型,根据不同的长度,hashtable 也做了很多优化,代码见 HashTable/StringHashMap.h

总结

ClickHouse 会根据 Group By 的最终类型,选择一个最优的 hashtable 或数组,作为分组基础数据结构,使内存和计算尽量最优。

这个”最优解“是怎么找到的?从 test 代码可以看出,是不停的尝试、测试验证出来的,浓厚的 bottom-up 哲学范。

hashtable 测试代码:Interpreters/tests

lookuptable 测试代码: tests/average.cpp


欢迎关注我的微信公众号【数据库内核】:分享主流开源数据库和存储引擎相关技术。

标题网址
GitHubhttps://dbkernel.github.io
知乎https://www.zhihu.com/people/…
思否(SegmentFault)https://segmentfault.com/u/db…
掘金https://juejin.im/user/5e9d3e…
开源中国(oschina)https://my.oschina.net/dbkernel
博客园(cnblogs)https://www.cnblogs.com/dbkernel
clickhouse到底有哪些吊炸天的优化? 从技术实现上来说,肯定是可行的。上面有三个part,现在我的诉求是查询出其中>=3且<=5的所有数据,虽然在各个part内部,数据本身是有序的,但是这三个part很不凑巧地都包含了3~5的范围,因此必须每个part都扫描一遍,而实际上,part3是没有任何符合条件的数据的,所以这个扫描其实是无用的操作。热数据,经常会被查询的数据(比如近两三天的数据),存储到固态磁盘,这样不论是写入,还是查询都非常快,超过一定时间的数据,可以通过存储策略转移到机械磁盘上,因为查询频率并不怎么高,那么受到的影响就会有限。 阅读详情

相关推荐

ClickHouse性能优化

列式存储是一种数据库存储技术,它将数据按列而不是按行存储。这意味着每一列的数据都存储在一起,而不是每一行的数据存储在一起。这种存储方式对于联机分析处理(OLAP)非常有用,因为它可以避免读取不必要的列,从而避免昂贵的磁盘读取操作。此外,将同一列的不同值存储在一起通常会导致更好的压缩比率(与行式系统相比),因为在实际数据中,相邻行的同一列通常具有相同或不太多的不同值。在ClickHouse中,主键和索引的定义对查询性能有很大影响。你可以在创建表时使用语句来定义主键和索引。

2401_84715997的博客 1886

MySql group by 及 clickhouse 聚合函数使用问题

背景:项目需要使用clickhouse及mysql数据库,且两个库中的表需要关联查询。 一.MySQL中使用 group by 说明:理论上select 所查询的列 都需要加在group by 后面,或者使用sum()、min()等聚合函数 1.MySQL中使用group by 查询时 可以通过MySQL sql_model设置是否可以查询未使用聚合函数的列。 sql_model设置值ONLY_FULL_GROUP_BY的语义就是确定select target list中的所有列的值都是明确语义,.

li_csdn_的博客 8784

大数据ClickHouse进阶(十三):ClickHouse的GROUP BY 子句

Group By子句又称聚合查询,与MySQL或者Hive中的使用方式一样,但是需要注意一点在Select查询中如果有聚合查询,例如max,min等,与聚合查询出现的字段一定要出现在Group by中,否则语句报错。ClickHouse中的Group by 还可以配合WITH ROLLUP、WITH CUBE、WITH TOTALS三种修饰符获取额外的汇总信息。CUBE 是立方体意思,WITH CUBE会基于聚合键之间所有的组合生成小计信息。

Lansonli(蓝深李)的博客 2609

2023-01-04 clickhouse-Group By 为什么这么快

ClickHouse 会根据 Group By 的最终类型,选择一个最优的 hashtable 或数组,作为分组基础数据结构,使内存和计算尽量最优。这个”最优解“是怎么找到的?从 test 代码可以看出,是不停的尝试、测试验证出来的,浓厚的 bottom-up 哲学范。

悟世之路 1031

【信息科学与工程学】【产品体系】第二十六篇 存储系统及存储服务器中所有学科知识01

存储系统是一门交叉学科,涉及计算机体系结构、分布式系统、编码理论、操作系统、网络等多个领域。编号类型学科/课程/产品存储系统领域的作用(含 AI infra / 存储网络 / 存储服务器 / 存储领域)核心知识点及数学建模/方程式/算法在存储系统中的作用及详细设计方法、时序、流程、模式代表教材/资料/论文 + 链接工业界应用001基础理论计算机体系结构、操作系统决定存储层级(寄存器-L1/L2/L3-内存-SSD-HDD-磁带),是 AI 训练集群、存储服务器、SAN/NAS 的物理底座存储层级模型:访问延

weixin_49199313的博客 49

麒麟系统grub被锁怎么办?双密码遗忘救援指南(实测V10 SP3版本)

本文详细介绍了麒麟系统V10 SP3版本在GRUB和root密码双遗忘情况下的救援指南。通过ISO镜像进入救援模式,结合单用户模式操作,实现密码重置与系统权限恢复,特别适用于等保合规环境。

390

ClickHouse数据聚合和分组

分组是数据聚合的一种方式,它将数据集按照某个属性的值进行分类,将具有相同属性值的数据项放置在同一个组中。分组可以帮助我们更好地理解数据的分布情况,以及对数据进行更细粒度的分析和处理。在数据库中,数据聚合通常是基于某个属性对数据进行分组,并对每个组进行聚合操作,例如计算平均值、求和或计数等。中的一种数据组织方式,可以将数据按照某个属性的值进行分区存储。是一个高性能、列式存储的分布式数据库,它内置了丰富的聚合和分组功能,可以帮助我们高效地进行数据分析和处理。中,可以使用各种聚合函数对分组后的数据进行计算。

互联网知识分享 3314

华为Watch的ECG功能技术分析

华为Watch的ECG(心电图)功能通过硬件传感器、算法优化和医疗认证的结合,实现了对心脏电活动的监测和房颤(AFib)等心律失常的预警。采用医疗级ECG模拟前端芯片(AFE),如TI(德州仪器)的ADS1292R等,支持低噪声、高采样率(通常≥200Hz)。结合PPG(光学心率)数据交叉验证,提高检测可靠性(如ECG和PPG心率不一致时触发警告)。通过RR间期变异性判断心律是否规则,识别房颤(AFib)的典型特征(如不规则不规则心律)。结合加速度计数据,识别手臂运动导致的信号失真,动态调整算法参数。

xu_wenming的博客 6447

【Clickhouse那些事】Clickhouse学习系列——一条SQL完成gourp by分组与不分组数值计算

笔者在近一两年接触了Clickhouse数据库,在项目中也进行了一些实践,但一直都没有一些技术文章的沉淀,近期打算做个系列,通过一些具体的场景将Clickhouse的用法进行沉淀和分享,供大家参考。

killer1989的博客 3336

抖音直播行为规范考试

抖音直播行为规范考试

Q1642968641的博客 1万+

常见信号逻辑电平标准介绍:TTL、CMOS、ECL、LVDS、CML

逻辑电平是指数字信号电压的高、低电平,相关参数定义如下:(1)输入高电平门限Vih:保证逻辑门的输入为高电平时所允许的最小输入高电平,当输入电平高于Vih时,则认为输入电平为高电平;(2)输入低电平门限Vil:保证逻辑门的输入为低电平时所允许的最大输入低电平,当输入电平低于Vil时,则认为输入电平为低电平;(3)输出高电平门限Voh:保证逻辑门的输出为高电平时的输出电平的最小值,逻辑门的输出为高电平时的电平值都必须大于此Voh;

liuchj的博客 1万+

clickhouse group by 优化神器

clickhouse group by 优化神器 clickhouse中有时候会有对大量分散数据进行group by后再筛选的需求 比如3亿条数据按照字段A进行group by,还有2亿条,再根据其他字段的count结果进行筛选: select A,count(*) from table t1 group by A having count(*)>8 因为字段A的分散性,group by 会非常耗费内存且慢 优化方法!!! 将表t1的分片字段改为A,那么表中的数据就会自动按照A值的不同分布在不同

KKT 5786

3D Gaussian Splatting环境配置和运行

使用conda命令根据environment.yml文件创建虚拟环境时,安装的pytorch是使用conda install 命令来安装的,实际上并不能成功安装带有gpu的pytorch,而这两个模块的安装依赖于带gpu的pytorch。如果不注释掉这三行,直接安装会报错

m0_46360757的博客 3326

ClickHouse和他的朋友们(15)Group By 为什么这么快

在揭秘 ClickHouse Group By 之前,先聊聊数据库的性能对比测试问题。在虎哥看来,一个“讲武德”的性能对比测试应该提供什么信息呢?首先要尊重客观事实,在什么场景下,x 比...

老叶茶馆 2861

逆向实战:从x-mini签名到RPC调用,Frida动态分析小红书加密协议

本文通过实战案例,详细拆解了小红书App的加密协议逆向分析过程。从识别x-mini签名等关键参数入手,结合静态分析与Frida动态Hook技术,追踪到核心的RPC调用入口,并最终封装成可独立调用的RPC函数,为安全研究和协议理解提供了完整的技术路径。

moss5的博客 1035

红米12C刷机避坑指南:NV数据损坏的修复与系统版本选择

本文提供了红米12C刷机过程中避免NV数据损坏的详细指南,涵盖系统版本选择、SP刷机工具使用和fastboot命令修复。重点分析了NV数据损坏的原因与表现,并给出通过工程固件和深度修复技巧解决基带未知、IMEI丢失等问题的实操方案,帮助用户安全完成刷机。

cuda7parallel的博客 1075

ClickHouse聚合函数(下):性能探索和调试

聚合函数性能分析;提升性能的实用技巧。

普普通通程序猿的博客 2662

Vue3多级路由缓存失效的深度解析与实战解决方案

本文深度解析了Vue3项目中多级路由下keep-alive缓存失效的根本原因,并提供了三种实战解决方案:通过路由扁平化根治问题、利用状态管理精细模拟缓存效果,以及借助keep-alive-vue3插件快速修复。文章旨在帮助开发者彻底理解Vue Router与keep-alive的协作机制,从而根据项目情况选择最佳策略,有效解决页面状态丢失的难题。

weixin_29015801的博客 285

Clickhouse GROUP BY 子句 Having 子句

参考: https://clickhouse.tech/docs/en/sql-reference/statements/select/group-by/ https://clickhouse.tech/docs/en/sql-reference/statements/select/having/

vkingnew 的技术博客 9890

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

ClickHouse高级查询语法和复杂查询

ClickHouse是一个分布式列式数据库管理系统,具备高性能和高可扩展性。它提供了丰富的查询语法来满足不同的查询需求。下面将介绍一些ClickHouse的高级查询语法。子句GROUP BY子句用于将数据按照某一列或多列进行分组。它通常与聚合函数一起使用,计算每个组的汇总结果。下面是一个示例:SELECTcategory,COUNT(*)FROMtableGROUPBYcategory参数介绍:category: 分组列COUNT(*): 聚合函数,计算每个组的行数。

互联网知识分享 3851

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

MySQL中distinct与group by之间的性能进行比较

最近在网上看到了一些测试,感觉不是很准确,今天亲自测试了一番。得出了结论,测试过程在个人计算机上,可能不够全面,仅供参考。 测试过程: 准备一张测试表  ? 1 2 3 4 5 CREATE TABLE `test_test` (   `id` int(11) NOT NULL

nicknailo博客 3672

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

ClickHouse介绍(四)ClickHouse使用操作

ClickHouse使用操作 这章主要介绍在ClickHouse使用的各个操作的注意点。常规的统一语法不做详细介绍。 1. Join操作 在ClickHouse中,对连接操作定义了不同的精度,包含ALL、ANY和ASOF三种类型,默认为ALL。可以通过join_default_strictness配置修改默认精度(位于system.setting表中)。下面分别说明这3种精度。 首先建表并插入测...

ZacksTang的博客 510

ClickHouse 实时数据去重查询argMax +group by

clickhouse查询数据去重筛选出最后一次创建时间的数据。

weixin_40228600的博客 3299

Clickhouse 如何在外部存储器中进行分组Group By 设置配置项 max_bytes_before_external_group_by、max_memory_usage

例如,如果 max_memory_usage 设置为10000000000,你想使用外部聚合,可以设置 max_bytes_before_external_group_by 到 10000000000,并且设置 max_memory_usage 到 20000000000。该 max_bytes_before_external_group_by 可以设置触发的阈值,当RAM消耗超过这个阈值后, GROUP BY 会把多余的临时数据输出到文件系统并在磁盘进行处理计算。将数据转储到文件系统只能在阶段1中发生。

天河书阁 - VicRestart 1439

clickhouse性能测试

clickhouse性能测试 准备 集群环境建表 CREATE TABLE test.ontime ON CLUSTER yc_clickhouse \ ( Year UInt16, \ Quarter UInt8, \ Month UInt8, \ DayofMonth UInt8, \ DayOfWeek UInt8, \ FlightDate Date, \ UniqueCarrier FixedString(7), \ AirlineID In

weixin_42042307的博客 983

clickhouse 性能测试

注意:本测试使用的是官网的数据集,由于官网聚合操作太多过滤,所以写了一些没有过滤的聚合操作,本测试属于极限测试,真实业务中的聚合操作肯定会有很多过滤操作的 一.表的创建和数据导入 创建表:必须在每个节点创建表,ddl 没有同步  create database test ; use test ; CREATE TABLE ontime ( Year UInt16, Quarter ...

keyuquan的博客 2万+
上一篇: 源码分析 | ClickHouse和他的朋友们(14)存储计算分离方案与实现
下一篇: 特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇
dbkernel
博客等级 码龄4年 60粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值