写点什么

ClickHouse 为什么这么快

  • 2023-03-22
    浙江
  • 本文字数:5329 字

    阅读完需:约 17 分钟

ClickHouse为什么这么快

ClickHouse 作为一款开源列式数据库管理系统(DBMS)主要用于数据分析(OLAP)领域。

近年来国内开源社区非常火热,各个大厂纷纷跟进大规模使用。而提到 ClickHouse 最先想到的就是它极致的性能,计算速度开源公开 benchmark 显示比传统方法快 100~1000 倍,提供 50MB~200MB/s 的高吞吐实时导入能力。所谓“天下武功为快不破”,那 ClickHouse 到底是如何做到快速查询的呢?今天我们就来一起了解一下。

一、自下而上的设计

早在 2019 年年末,ClickHouse 项目的创始人兼开源社区创始人 — Alexey Milovidov,就在北京举行的中国大数据技术大会(BDTC 2019)上分享过自下而上的设计理念,那他们是如何实现这种设计的呢?

1. 着眼硬件,先想后做

从硬件功能层面着手设计,包括 CPU、内存、硬盘、网络等等,基于将硬件功效最大化的目的,ClickHouse 会在内存中进行 GROUP BY,并且使用 HashTable 装载数据。与此同时,他们非常在意 CPU L3 级别的缓存,因为一次 L3 cache miss 会带来 70~100 纳秒的延迟,这意味着,在单核 CPU 上,它会浪费 4000 万/每秒的运算;而在一个 32 线程的 CPU 上,则可能会浪费 5 亿/每秒的运算。

2. 算法在前,抽象在后

实现性能的最大化,算法的选择是重中之重。有时候,选择比努力更重要,以字符串搜索算法为例,针对不同的场景,ClickHouse 最终选择了这些算法:

  • 对于常量,使用 Volnitsky 算法;

  • 对于非常量,使用 CPU 的向量化执行 SIMD,暴力优化;

  • 正则匹配使用 re2 和 hyperscan 算法。

3. 特定场景,特殊优化

针对同一个场景的不同状况,使用不同的实现方式,尽可能地将性能最大化。例如去重计数 uniqCombined 函数,根据数据量的不同,会选择不同的实现方式:

  • 当数据量较小的时候,会选择 Array 保存;

  • 当数据量中等时候,则会选择 HashSet;

  • 而当数据量很大的时候,则使用 HyperLogLog 算法。

4. 勇于尝鲜,不行就换

除了字符串之外,在很多场景下 ClickHouse 会使用最合适、最快的算法。如果市面上出现了号称性能强大的新算法,ClickHouse 也会将其纳入并进行验证,如果效果可行,就保留使用。

5. 持续测试,持续改进

实践是检验真理的唯一标准,ClickHouse 还拥有一个能够持续验证、持续改进的机制。

由于 Yandex 的天然优势,ClickHouse 经常会使用真实的数据做测试,这一点很好地保证了测试场景的真实性。与此同时,ClickHouse 是发版速度非常快的开源软件了,每个月都至少能发布一个版本,这得力于一个可靠的持续集成环境。也正是因为拥有这样的发版频率,ClickHouse 才得以在快速迭代、快速改进中逐渐强大起来。

二、列式存储

相比于行式存储,列式存储在分析场景下有着许多优良的特性。

1)分析场景中往往需要读大量行但是少数几个列。在行存模式下,数据按行连续存储,所有列的数据都存储在一个 block 中,不参与计算的列在 IO 时也要全部读出,读取操作被严重放大。而列存模式下,只需要读取参与计算的列即可,极大地减低了 IO,加速了查询。

2)同一列中的数据属于同一类型,压缩效果显著。列存往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本的同时,从磁盘中读取相应数据耗时也更短。

3)自由的压缩算法选择。不同列的数据具有不同的数据类型,适用的压缩算法也就不尽相同。可以针对不同列类型,选择最合适的压缩算法。

4)高压缩比,意味着同等大小的内存能够存放更多数据,系统 cache 效果更好。

官方数据显示,通过使用列存,在某些分析场景下,能够获得 100 倍甚至更高的加速效应。

三、 存储结构

ClickHouse 采用类 LSMTree 的结构,数据写入后定期在后台 Compaction。通过类 LSM tree 的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台 compaction 时也是多个段 merge sort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力。

官方公开 benchmark 测试显示能够达到 50MB-200MB/s 的写入吞吐能力,按照每行 100Byte 估算,大约相当于 50W-200W 条/s 的写入速度。


四、索引

ClickHouse 支持主键索引(一级索引),它将每列数据按照 index granularity(默认 8192 行)进行划分,每个 index granularity 的开头第一行被称为一个 mark 行。主键索引存储该 mark 行对应的 primary key 的值。

对于 where 条件中含有 primarykey 的查询,通过对主键索引进行二分查找,能够直接定位到对应的 index granularity,避免了全表扫描从而加速查询。

ClickHouse 还支持二级索引(跳数索引),二级索引是在一级索引的基础上建立的,有一个重要的参数:granularity = 3,这个参数的意思是:在 3 段一级索引上创建二级索引。

二级索引支持的类型
  • minmax: 以 index_granularity 为单位,存储指定表达式计算后的 min、max 值;在等值和范围查询中能够帮助快速跳过不满足要求的块,减少 IO;

  • set(max_rows):以 index_granularity 为单位,存储指定表达式的 disinct value 集合,用于快速判断等值查询是否命中该块,减少 IO;

  • ngrambf_v1(n,size_of_bloom_fiter_in_bytes,number_of_hash_functions, random_seed):将 string 进行 ngram 分词后,构建 bloom filter,能够优化 等值、like、in 等查询条件;

  • tokenbf_v1(size_of_bloom_fiter_in_bytes,number_of_hash_functions,random_seed):与 ngrambf_v1 类似,区别是不使用 ngram 进行分词,而是通过标点符号进行词语分割;

  • bloom_filter([false_positive]):对指定列构建 bloomfilter,用于加速等值、like、in 等查询条件执行。

五、数据压缩

Clickhouse 的数据存储文件 column.bin 中存储的是一列数据,在进行压缩的时候:一个压缩数据块由头信息和压缩数据两部分组成,头信息固定使用 9 位字节表示,具体由 1 个 UInt8(1 字节)整型和 2 个 UInt32(4 字节)整型组成,分别代表使用的压缩算法类型、压缩后的数据大小和压缩前的数据大小。每个压缩数据块的体积,按照其压缩前的数据字节大小,都被严格控制在 64KB ~ 1MB,其上下限分别由 min_compress-block_size(默认 65535=64KB)与 max_compress_block_size(默认 1MB)参数指定。

1)单个批次数据 size <64KB:如果单个批次数据小于 64KB,则继续获取下一批数据,直至累计到 size >= 64KB 时,生成下一个压缩数据块,如果平均每条记录小于 8byte,多个数据批次压缩成一个数据块;

2)单个批次数据 64KB <=size <= 1MB:如果单个批次数据大小在 64KB 与 1MB 之间,则直接生成下一个压缩数据块;

3)> 单个批次数据 size > 1MB:如果单个批次数据直接超过 1MB,则首先按照 1MB 大小截断并生成下一个压缩数据块,剩余数据继续依照上述规则执行。此时,会出现一个批次数据生成多个压缩数据块的情况,如果平均每条记录的大小超过 128byte,则会把当前这一个批次的数据压缩成多个数据块。

注意:在一个 xxx.bin 字段存储文件中,并不是一个压缩块对应到一条一级索引,而是每 8192 条数据,构建一条一级索引。

六、标记

上面一部分已经给读者介绍了什么是一级索引,但一级索引并不能单独实现快速查找的目的,或者说,一级索引只实现了数据到 block 的映射。这样的话就还存在一个问题——即便我已经知道我的数据存储在了第一个 block,那我如何定位到这个 block 的位置呢?这个就需要通过标记文件来实现了,换句话说,标记文件存储了 block 到文件偏移量的映射。

每个颗粒会对应一个 mark,该 mark 主要存储着 2 项信息:

1)当前 block 在压缩后的物理文件中的 offset;

2)当前 granularity(颗粒)在解压后 block 中的 offset。

Block 是 ClickHouse 与磁盘进行 IO 交互、压缩/解压缩的最小单位,而 granularity 是 ClickHouse 在内存中进行数据扫描的最小单位。

七、多线程与分布式

数据 Partitioning

ClickHouse 支持 PARTITIONBY 子句,在建表时可以指定按照任意合法表达式进行数据分区操作。比如通过 toYYYYMM()将数据按月进行分区、通过 toMonday()将数据按周进行分区、对 Enum 类型的列可以直接把相同的值作为一个分区等。

数据 Partition 在 ClickHouse 中主要有两方面应用:

在 partition key 上进行分区裁剪,只查询必要的数据。灵活的 partition expression 设置,使得我们可以根据 SQL Pattern 进行分区设置,最大化贴合业务特点。

每个 partition 再进一步划分为多个 indexgranularity,然后通过多个 CPU 核心分别处理其中的一部分来实现并行数据处理。

数据 Sharding

ClickHouse 支持单机模式,也支持分布式集群模式。在分布式模式下,ClickHouse 会将数据分为多个分片,并且分布到不同节点上。不同的分片策略在应对不同的 SQL Pattern 时,各有优势。ClickHouse 提供了丰富的 sharding 策略,让业务可以根据实际需求选用。

1) random 随机分片:写入数据会被随机分发到分布式集群中的某个节点上。

2) constant 固定分片:写入数据会被分发到固定一个节点上。

3)column value 分片:按照某一列的值进行 hash 分片。

4)自定义表达式分片:指定任意合法表达式,根据表达式被计算后的值进行 hash 分片。

数据分片,让 ClickHouse 可以充分利用整个集群的大规模并行计算能力,快速返回查询结果。

更重要的是,多样化的分片功能,为业务优化打开了想象空间。比如在 hashsharding 的情况下,JOIN 计算能够避免数据 shuffle,直接在本地进行 local join;支持自定义 sharding,可以为不同业务和 SQL Pattern 定制最适合的分片策略;利用自定义 sharding 功能,通过设置合理的 sharding expression 可以解决分片间数据倾斜问题等。

另外,sharding 机制使得 ClickHouse 可以横向线性拓展,构建大规模分布式集群,从而具备处理海量数据的能力。

数据 Replacing

ClickHouse 通过主备复制提供了高可用能力,在集群模式下对 shard 配置副本,但 1 个节点只能拥有 1 个分片,也就是说如果要实现 1 分片、1 副本,则至少需要部署 2 个服务节点。相比于其他系统它的实现有着自己的特色:

1)默认配置下,任何副本都处于 active 模式,可以对外提供查询服务;

2)可以任意配置副本个数,副本数量可以从 0 个到任意多个;

3)不同 shard 可以配置不同副本个数,用于解决单个 shard 的查询热点问题。

八、向量化执行引擎

为了高效地使用 CPU,数据不仅仅按列存储,同时还按向量(列的一部分)进行处理。利用 CPU 的 SIMD 指令(SIMD 的全称是 Single Instruction Multiple Data)——即用单条指令操作多条数据,它的原理是在 CPU 寄存器层面实现数据的并行操作。ClickHouse 目前利用 SSE4.2 指令集实现向量化执行,媒介越接近 CPU,速度越快。

九、动态代码生成 Runtime Codegen

在经典的数据库实现中,通常对表达式计算采用火山模型,也就是将查询转换成一个个的 operator。为了连接不同的算子,operator 之间采用统一的接口,在每个算子内部都实现了父类的这些虚函数。另外,在每个算子内部都要考虑多种变量,因为会存在由于 if-else 分支判断过多导致 CPU 分支预测失效的情况。

ClickHouse 实现了 Expression 级别的 runtime codegen,动态地根据当前 SQL 直接生成代码,然后编译执行。如下图例子所示,Expression 直接生成代码,不仅消除了大量的虚函数调用,并且由于在运行时表达式的参数类型、个数等都是已知的,也消除了不必要的 if-else 分支判断。

十、近似计算

近似计算以损失一定结果精度为代价,极大地提升了查询性能。在海量数据处理中,近似计算价值更加明显。

ClickHouse 实现了多种近似计算功能:

近似估算 distinct values、中位数、分位数等多种聚合函数;

建表 DDL 支持 SAMPLE BY 子句,支持对于数据进行抽样处理。

linux 安装 ClickHouse

相信通过上面的介绍大家已经了解到了 Clickhouse 的强大之处,那么下面就一起来实际安装一下 ClickHouse 吧。

一、系统要求

ClickHouse 可以在任何具有 x86_64,AArch64 或 PowerPC64LE CPU 架构的 Linux,FreeBSD 或 Mac OSX 上运行。

官方预构建的二进制文件通常针对 x86_64 进行编译,并利用 SSE 4.2 指令集,因此,除非另有说明,支持它的 CPU 使用将成为额外的系统需求。下面是检查当前 CPU 是否支持 SSE 4.2 的命令:

要在不支持 SSE 4.2 或 AArch64,PowerPC64LE 架构的处理器上运行 ClickHouse,您应该通过适当的配置调整从源代码构建 ClickHouse。

二、下载安装包

ClickHouse 支持很多种安装,

  • DEB 安装包

  • RPM 安装包

  • TGZ 安装包

  • Docker 安装包

  • 其他环境安装包,对于非 linux 操作系统和 Arch64 CPU 架构,ClickHouse 将会以 master 分支的最新提交的进行编译提供

  • 源代码安装

这里选择 TGZ 安装包安装

下载地址:Index of /clickhouse/tgz/

所需的版本可以通过 curl 或 wget 从存储库https://repo.clickhouse.tech/tgz/中下载,也可以下载好上传服务器。

选择自己需要的版本进行下载,这里选择在 stable 目录下的 21.9.4.35 版本,分别是:

在服务器上依次将这四个安装包解压并执行一下解压文件夹下 install 下的 doinst.sh 脚本。

在执行./clickhouse-server-21.9.4.35/install/doinst.sh 后,clickhouse 会默认创建一个 default 的用户,让你设置密码,不设置密码可以按回车。

三、启动

可以有以下几种形式启动 ClickHouse Server

默认日志路径是/var/log/clickhouse-server/,配置文件路径是/etc/clickhouse-server/config.xml。

通过客户端连接 clickhouse

--multiline 的意思是对长的 SQL 进行转义,否则执行跨行 SQL 时,会提示错误,如下所示,他会将每行都当作独立的 SQL 执行,可简写为-m。

如果登录后查询发现返回有乱码,就要将你的终端软件字符集调整为 UTF-8

再次查询

注:文章部分参考来源于网络,如有侵权,请联系删除!


本期内容就到这里了,如果喜欢就点个关注吧,微信公众号搜索“数 新 网 络 科 技 号”可查看更多精彩内容~

发布于: 刚刚阅读数: 3
用户头像

云数据智能操作系统领导者 2022-12-05 加入

浙江数新网络有限公司是一家拥抱开源,专注于云数据平台的大数据服务商,致力于结合全球云数仓先进理念,打造适合中国落地路径的云数仓体系。

评论

发布
暂无评论
ClickHouse为什么这么快_Clickhouse_数新网络官方账号_InfoQ写作社区