入门尚可,已经是除了用户手册外最好的书了,当然书本身也不多
这篇书评可能有关键情节透露
想用来做实时报表分析是不那么合适的;
做clickhouse不容易啊,其实并发能力很低;
但是大数据量查询方面还可以,所以基本上就是堆资源去查出来的;
对于业务来说,实时读写并不现实,ch有很多异步的逻辑:
- 首先是zk的异步和不实时
- 修改是异步的;副本之间同步是异步的;
- mutation的修改是异步的;
—————————以下为原文摘抄———————————————————————————————————
ClickHouse原理解析与应用实践
推荐序一
原文:我们在2016年发布了ClickHouse的开源版本
原文:如果细看ClickHouse的架构,你会发现其中没有什么新颖的技术,其中使用的大部分技术都是经过了多年研究并已在其他数据库中实现了的成熟技术
推荐序二
原文:在用户行为分析转化漏斗场景里,ClickHouse比Spark快了近10倍
前言
原文:本书内容基于ClickHouse 19.17.4.11版本编写,演示时所用操作系统为CentOS 7.7。
1.3 OLAP常见架构分类
原文:OLAP名为联机分析,又可以称为多维分析,是由关系型数据库之父埃德加·科德(Edgar Frank Codd)于1993年提出的概念
原文:·下钻:从高层次向低层次明细数据穿透。例如从“省”下钻到“市”,从“湖北省”穿透到“武汉”和“宜昌”。
·上卷:和下钻相反,从低层次向高层次汇聚。例如从“市”汇聚成“省”,将“武汉”“宜昌”汇聚成“湖北”。
·切片:观察立方体的一层,将一个或多个维度设为单个固定值,然后观察剩余的维度,例如将商品维度固定为“足球”。
·切块:与切片类似,只是将单个固定值变成多个值。例如将商品维度固定成“足球”“篮球”和“乒乓球”。
1.4 OLAP实现技术的演进
原文:在ROLAP架构下,直接使用这些数据库作为存储与计算的载体;在MOLAP架构下,则借助物化视图的形式实现数据立方体
1.5 一匹横空出世的黑马
原文:ElasticSearch支持实时更新,在百万级别数据的场景下可以做到实时聚合查询,但是随着数据体量的继续增大,它的查询性能也将捉襟见肘
原文:在1亿数据集体量的情况下,ClickHouse的平均响应速度是Vertica的2.63倍、InfiniDB的17倍、MonetDB的27倍、Hive的126倍、MySQL的429倍以及Greenplum的10倍
2.1 ClickHouse的核心特性
原文:ClickHouse在数据存取方面,既支持分区(纵向扩展,利用多线程原理),也支持分片(横向扩展,利用分布式原理),可以说是将多线程和分布式的技术应用到了极致
原文:而ClickHouse则采用Multi-Master多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果
原文:而Elasticsearch这类搜索引擎在处理亿级数据聚合查询时则显得捉襟见肘。
原文:一张本地表等同于一份数据的分片
原文:而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件
原文:待到业务增长、数据量增大的时候,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询
2.2 ClickHouse的架构设计
原文:内存中的一列数据由一个Column对象表示
原文:需要操作单个具体的数值(也就是单列中的一行数据),则需要使用Field对象
原文:Block对象可以看作数据表的子集
原文:IStorage接口定义了DDL(如ALTER、RENAME、OPTIMIZE和DROP等)、read和write方法,它们分别负责数据的定义、查询与写入
原文:在函数具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用于一整列数据
原文:ClickHouse的集群由分片(Shard)组成,而每个分片又通过副本(Replica)组成
原文:如果一个索引由5个分片组成,副本的基数是1,那么这个索引一共会拥有10个分片(每1个分片对应1个副本)
原文:在物理存储层面则统一使用副本代表分片和副本
原文:真正表示1分片、1副本语义的配置,应该改为1个分片和2个副本
2.3 ClickHouse为何如此之快
原文:目的很单纯,就是希望能以最快的速度进行GROUP BY查询和过滤
原文:在字符串搜索方面,针对不同的场景,ClickHouse最终选择了这些算法:对于常量,使用Volnitsky算法;对于非常量,使用CPU的向量化执行SIMD,暴力优化;正则匹配使用re2和hyperscan算法
原文:去重计数uniqCombined函数,会根据数据量的不同选择不同的算法:当数据量较小的时候,会选择Array保存;当数据量中等的时候,会选择HashSet;而当数据量很大的时候,则使用HyperLogLog算法。
3.1 ClickHouse的安装过程
原文:演示服务器的操作系统为CentOS 7.7,而ClickHouse选用19.17.4.11版本
第4章 数据定义
原文:解决问题的最好方法就是恰好不需要
4.1 ClickHouse的数据类型
原文:字符串由String定义,长度不限。因此在使用String的时候无须声明大小
原文:UUID共有32位,它的格式为8-4-4-4-12
原文:DateTime64可以记录亚秒,它在DateTime之上增加了精度的设置
原文:Date类型不包含具体的时间信息,只精确到天
原文:ClickHouse还提供了数组、元组、枚举和嵌套四类复合类型
原文:Nullable类型与Java8的Optional对象有些相似
原文:如果一个列字段被Nullable类型修饰后,会额外生成一个[Column].null.bin文件专门保存它的Null值。这意味着在读取和写入数据时,需要一倍的额外文件操作
4.2 如何定义数据表
原文:数据分区(partition)和数据分片(shard)是完全不同的两个概念
原文:目前只有合并树(MergeTree)家族系列的表引擎才支持数据分区
原文:由PARTITION BY指定分区键
原文:分区键不应该使用粒度过细的数据字段
原文:物化视图拥有独立的存储
原文:普通视图只是一层简单的查询代理
原文:如果源表被写入新数据,那么物化视图也会同步更新
原文:如果使用了POPULATE修饰符,那么在创建视图的过程中,会连带将源表中已存在的数据一并导入,如同执行了SELECT INTO一般;反之,如果不使用POPULATE修饰符,那么物化视图在创建之后是没有数据的,它只会同步在此之后被写入源表的数据
4.3 数据表的基本操作
原文:目前只有MergeTree、Merge和Distributed这三类表引擎支持ALTER查询
原文:RENAME可以修改数据表的名称
原文:数据表的移动只能在单个节点的范围内
4.4 数据分区的基本操作
原文:目前只有MergeTree系列的表引擎支持数据分区。
原文:parts系统表专门用于查询数据表的分区信息
原文:ClickHouse支持将A表的分区数据复制到B表
原文:表分区可以通过DETACH语句卸载,分区被卸载后,它的物理数据并没有删除
4.5 分布式DDL执行
原文:如果在集群中任意一个节点上执行DDL语句,那么集群中的每个节点都会以相同的顺序执行相同的语句
原文:将一条普通的DDL语句转换成分布式执行十分简单,只需加上ON CLUSTER cluster_name声明即可
4.7 数据的删除与修改
原文:ClickHouse提供了DELETE和UPDATE的能力,这类操作被称为Mutation查询
原文:Mutation语句是一种“很重”的操作,更适用于批量数据的修改和删除
原文:无法回滚
原文:Mutation语句的执行是一个异步的后台过程,语句被提交之后就会立即返回
5.1 内置字典
原文:对于Yandex.Metrica字典数据的访问,这里用到了regionToName函数
5.3 本章小结
原文:数据字典能够有效地帮助我们消除不必要的JOIN操作(例如根据ID转名称),优化SQL查询,为查询性能带来质的提升
第6章 MergeTree原理解析
原文:因为只有合并树系列的表引擎才支持主键索引、数据分区、数据副本和数据采样这些特性,同时也只有此系列的表引擎支持ALTER相关操作
6.1 MergeTree的创建方式与存储结构
原文:MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改。为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段
原文:PARTITION BY [选填]:分区键,用于指定表数据以何种标准进行分区。分区键既可以是单个列字段,也可以通过元组的形式使用多个列字段,同时它也支持使用列表达式
原文:以ORDER BY(CounterID,EventDate)为例,在单个数据片段内,数据首先会以CounterID排序,相同CounterID的数据再按EventDate排序
原文:PRIMARY KEY [选填]:主键,顾名思义,声明后会依照主键字段生成一级索引,用于加速表查询
原文:通常直接使用ORDER BY代为指定主键,无须刻意通过PRIMARY KEY声明
原文:SETTINGS:index_granularity [选填]:index_granularity对于MergeTree而言是一项非常重要的参数,它表示索引的粒度,默认值为8192
原文:每间隔8192行数据才生成一条索引
原文:二级索引在ClickHouse中又称跳数索引,目前拥有minmax、set、ngrambf_v1和tokenbf_v1四种类型。这些索引的最终目标与一级稀疏索引相同,都是为了进一步减少所需扫描的数据范围,以加速整个查询过程
6.2 数据分区
原文:数据分区是针对本地数据而言的,是对数据的一种纵向切分
原文:Level:合并的层级,可以理解为某个分区被合并过的次数,或者这个分区的年龄
6.3 一级索引
原文:简单来说,在稠密索引中每一行索引标记都会对应到一行具体的数据记录。而在稀疏索引中,每一行索引标记对应的是一段数据,而不是一行
6.4 二级索引
原文:跳数索引在默认情况下是关闭的,需要设置allow_experimental_data_skipping_indices(该参数在新版本中已被取消)才能使用
原文:MergeTree共支持4种跳数索引,分别是minmax、set、ngrambf_v1和tokenbf_v1
6.5 数据存储
原文:每个列字段都拥有一个与之对应的.bin数据文件
原文:在.bin文件中只会保存当前分区片段内的这一部分数据
原文:每个压缩数据块的体积,按照其压缩前的数据字节大小,都被严格控制在64KB~1MB,其上下限分别由min_compress_block_size(默认65536)与max_compress_block_size(默认1048576)参数指定
原文:一个.bin文件是由1至多个压缩数据块组成的,每个压缩块大小在64KB~1MB之间
原文:通过压缩数据块,可以在不读取整个.bin文件的情况下将读取粒度降低到压缩数据块级别,从而进一步缩小数据读取的范围
6.6 数据标记
原文:primary.idx一级索引好比这本书的一级章节目录,.bin文件中的数据好比这本书中的文字,那么数据标记(.mrk)会为一级章节目录和具体的文字之间建立关联
原文:标记数据与一级索引数据不同,它并不能常驻内存,而是使用LRU(最近最少使用)缓存策略加快其取用速度。
原文:它的头信息固定由9个字节组成,压缩后大小
6.7 对于分区、索引、标记和压缩数据的协同总结
原文:数据写入的第一步是生成分区目录,伴随着每一批数据的写入,都会生成一个新的分区目录。在后续的某一时刻,属于相同分区的目录会依照规则合并到一起;接着,按照index_granularity索引粒度,会分别生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件)、每一个列字段的.mrk数据标记和.bin压缩数据文件。图6-20所示是一张MergeTree表在写入数据时,它的分区目录、索引、标记和压缩数据的生成过程
原文:虽然不能减少数据范围,但是MergeTree仍然能够借助数据标记,以多线程的形式同时读取多个压缩数据块,以提升性能。
原文:多个数据标记对应一个压缩数据块,当一个间隔(index_granularity)内的数据未压缩大小size小于64KB时,会出现这种对应关系
7.1 MergeTree
原文:MergeTree作为家族系列最基础的表引擎,提供了数据分区、一级索引和二级索引等功能
原文:MergeTree家族独有的另外两项能力——数据TTL与存储策略
原文:在MergeTree中,可以为某个列字段或整张表设置TTL
原文:目前ClickHouse没有提供取消列级别TTL的方法。
原文:表级别TTL目前也没有取消的方法
原文:通过ttl.txt文件记录过期时间,并将其作为后续的判断依据
原文:只有在MergeTree合并分区时,才会触发删除TTL过期数据的逻辑
原文:也可以使用optimize命令强制触发合并
原文:提供了控制全局TTL合并任务的启停方法
原文:将分区目录写入多块磁盘目录
原文:大家应该明白HOT/COLD策略的工作方式了。在这个策略中,由多个磁盘卷(volume卷)组成了一个volume组。每当生成一个新数据分区的时候,按照阈值大小(max_data_part_size),分区目录会依照volume组中磁盘卷定义的顺序,依次轮询并写入各个卷下的磁盘
7.2 ReplacingMergeTree
原文:ReplacingMergeTree就是在这种背景下为了数据去重而设计的,它能够在合并分区时删除重复的数据
原文:因为ReplacingMergeTree是以分区为单位删除重复数据的
原文:只有在相同的数据分区内重复的数据才可以被删除,而不同数据分区之间的重复数据依然不能被剔除
7.3 SummingMergeTree
原文:如果需要同时定义ORDER BY与PRIMARY KEY,通常只有一种可能,那便是明确希望ORDER BY与PRIMARY KEY不同
原文:如果同时声明了ORDER BY与PRIMARY KEY,MergeTree会强制要求PRIMARY KEY列字段必须是ORDER BY的前缀
原文:在修改ORDER BY时会有一些限制,只能在现有的基础上减少字段
原文:如果是新增排序字段,则只能添加通过ALTER ADD COLUMN新增的字段
原文:当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总。
原文:在汇总数据时,同一分区内,相同聚合Key的多行数据会合并成一行。其中,汇总字段会进行SUM计算;对于那些非汇总字段,则会使用第一行数据的取值
原文:支持嵌套结构,但列字段名称必须以Map后缀结尾。嵌套类型中,默认以第一个字段作为聚合Key。除第一个字段以外,任何名称以Key、Id或Type为后缀结尾的字段,都将和第一个字段一起组成复合Key
7.4 AggregatingMergeTree
原文:AggregatingMergeTree就有些许数据立方体的意思,它能够在合并分区的时候,按照预先定义的条件聚合数据。同时,根据预先定义的聚合函数计算数据并通过二进制的格式存入表内
原文:AggregatingMergeTree更为常见的应用方式是结合物化视图使用,将它作为物化视图的表引擎
原文:物化视图使用AggregatingMergeTree表引擎,用于特定场景的数据查询,相比MergeTree,它拥有更高的性能
原文:在进行数据计算时,因为分区内的数据已经基于ORBER BY排序,所以能够找到那些相邻且拥有相同聚合Key的数据。
原文:AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用
7.5 CollapsingMergeTree
原文:对数据源文件修改是一件非常奢侈且代价高昂的操作。相较于直接修改源文件,它们会将修改和删除操作转换成新增操作,即以增代删。
原文:CollapsingMergeTree就是一种通过以增代删的思路,支持行级数据修改和删除的表引擎。它通过定义一个sign标记位字段,记录数据行的状态
原文:如果sign标记为1,则表示这是一行有效的数据;如果sign标记为-1,则表示这行数据需要被删除
原文:折叠合并树(CollapsingMergeTree)名称的由来
原文:CollapsingMergeTree在折叠数据时,遵循以下规则。
·如果sign=1比sign=-1的数据多一行,则保留最后一行sign=1的数据。
原文:·如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=1,则保留第一行sign=-1和最后一行sign=1的数据。
·如果sign=1和sign=-1的数据行一样多,并且最后一行是sign=-1,则什么也不保留。
原文:要实现sign=1和sign=-1的数据相邻,则只能依靠严格按照顺序写入
7.6 VersionedCollapsingMergeTree
原文:VersionedCollapsingMergeTree对数据的写入顺序没有要求,在同一个分区内,任意顺序的数据都能够完成折叠操作
原文:以上面的ver_collpase_table表为例,在每个数据分区内,数据会按照ORDER BY id,ver DESC排序。所以无论写入时数据的顺序如何,在折叠处理时,都能回到正确的顺序。
7.7 各种MergeTree之间的关系总结
原文:ReplicatedMergeTree在MergeTree能力的基础之上增加了分布式协同的能力,其借助ZooKeeper的消息日志广播功能,实现了副本实例之间的数据同步功能
原文:当我们为7种MergeTree加上Replicated前缀后,又能组合出7种新的表引擎,这些ReplicatedMergeTree拥有副本协同的能力
第8章 其他常见类型表引擎
原文:Everything is table(万物皆为表)是ClickHouse一个非常有意思的设计思路
8.1 外部存储类型
原文:HDFS是一款分布式文件系统,堪称Hadoop生态的基石,HDFS表引擎则能够直接与它对接,读取HDFS内的文件
原文:虽然Kafka本身能够支持上述三层语义,但是目前ClickHouse还不支持恰好一次(Exactly once)的语义,因为这需要应用端与Kafka深度配合才能实现
原文:新建一张物化视图,用于将数据从kafka_queue同步到kafka_table
8.2 内存类型
原文:Set表引擎是拥有物理存储的,数据首先会被写至内存,然后被同步到磁盘文件中
原文:tmp临时目录:数据文件首先会被写到这个目录,当一批数据写入完毕之后,数据文件会被移出此目录。
原文:Join表引擎可以说是为JOIN查询而生的,它等同于将JOIN查询进行了一层简单封装
原文:Buffer表引擎完全使用内存装载数据,不支持文件的持久化存储,所以当服务重启之后,表内的数据会被清空
原文:数据首先被写入Buffer表,当满足预设条件时,Buffer表会自动将数据刷新到目标表
8.3 日志类型
原文:如果使用的数据量很小(100万以下),面对的数据查询场景也比较简单,并且是“一次”写入多次查询的模式,那么使用日志家族系列的表引擎将会是一种不错的选择
原文:不支持索引、分区等高级特性;不支持并发读写,当针对一张日志表写入数据时,针对这张表的查询会被阻塞,直至写入动作结束;但它们也同时拥有切实的物理存储,数据会被保存到本地文件中
原文:TinyLog是日志家族系列中性能最低的表引擎,它的存储结构由数据文件和元数据两部分组成。其中,数据文件是按列独立存储的,也就是说每一个列字段都拥有一个与之对应的.bin文件
8.4 接口类型
原文:Merge表引擎可以代理查询任意数量的数据表,这些查询会异步且并行执行,并最终合成一个结果集返回。
原文:被代理查询的数据表被要求处于同一个数据库内,且拥有相同的表结构,但是它们可以使用不同的表引擎以及不同的分区定义(对于MergeTree而言)
第9章 数据查询
原文:使用不恰当的SQL语句进行查询不仅会带来低性能,还可能导致不可预知的系统错误。
原文:ClickHouse对于SQL语句的解析是大小写敏感的
9.1 WITH子句
原文:可以定义子查询
原文:在WITH中使用子查询时有一点需要特别注意,该查询语句只能返回一行数据,如果结果集的数据大于一行则会抛出异常。
原文:在子查询中可以嵌套使用WITH子句
9.3 SAMPLE子句
原文:SAMPLE子句能够实现数据采样的功能,使查询仅返回采样数据而不是全部数据,从而有效减少查询负载
9.4 ARRAY JOIN子句
原文:ARRAY JOIN子句允许在数据表的内部,与数组或嵌套类型的字段进行JOIN操作,从而将一行数组展开为多行
原文:最终的数据基于value数组被展开成了多行,并且排除掉了空数组
9.5 JOIN子句
原文:连接精度分为ALL、ANY和ASOF三种,而连接类型也可分为外连接、内连接和交叉连接三种
原文:连接精度决定了JOIN查询在连接数据时所使用的策略,目前支持ALL、ANY和ASOF三种类型。如果不主动声明,则默认是ALL
原文:交叉连接(CROSS JOIN)不需要使用JOIN KEY,因为它会产生笛卡儿积。
原文:为了能够优化JOIN查询性能,首先应该遵循左大右小的原则,即将数据量小的表放在右侧
原文:如果应用程序会大量使用JOIN查询,则需要进一步考虑借助上层应用侧的缓存服务或使用JOIN表引擎来改善性能。
9.6 WHERE与PREWHERE子句
原文:PREWHERE目前只能用于MergeTree系列的表引擎,它可以看作对WHERE的一种优化,其作用与WHERE相同,均是用来过滤数据
原文:使用PREWHERE时,首先只会读取PREWHERE指定的列字段数据,用于数据过滤的条件判断
原文:这是因为在执行PREWHERE查询时,只需获取JavaEnable字段进行数据过滤,减少了需要处理的数据量大小
原文:因为ClickHouse实现了自动优化的功能,会在条件合适的情况下将WHERE替换为PREWHERE
9.7 GROUP BY子句
原文:在GROUP BY后声明的表达式,通常称为聚合键或者Key,数据会按照聚合键进行聚合
原文:当聚合查询内的数据存在NULL值时,ClickHouse会将NULL作为NULL=NULL的特定值处理
原文:ROLLUP能够按照聚合键从右向左上卷数据,基于聚合函数依次生成分组小计和总计
原文:CUBE会像立方体模型一样,基于聚合键之间所有的组合生成小计信息
9.8 HAVING子句
原文:HAVING子句需要与GROUP BY同时出现,不能单独使用
原文:HAVING的本质是在聚合之后增加了Filter过滤动作
原文:WHERE的执行优先级大于GROUP BY,所以如果需要按照聚合值进行过滤,就必须借助HAVING实现
9.10 LIMIT BY子句
原文:LIMIT BY子句和大家常见的LIMIT所有不同,它运行于ORDER BY之后和LIMIT之前,能够按照指定分组,最多返回前n行数据(如果数据少于n行,则按实际数量返回),常用于TOP N的查询场景