这是我参与「第五届青训营」伴学笔记创作活动的第 14 天
简介
ClickHouse 是一个面向列的数据库管理系统(DBMS),用于查询的在线分析处理(OLAP)。由号称“俄罗斯Google”的Yandex开发而来,在2016年开源。本文主要对ClickHouse相关知识进行简要学习。
列式存储
概念:传统的行式数据库系统中,处于同一行的数据总是被物理的存储在一起,而列式数据库来自不同列的数据被单独存储,来自同一列的数据被存储在一起。
优点:
-
数据压缩:可以使读的数据量更少,如相同类型的压缩、排序后压缩等,针对不同类型使用不同的压缩算法。
-
数据选择:可以选择特定的列做计算而不是读所有列,对聚合计算友好。
-
延迟物化:物化指将列数据转换为可以被计算或者输出的行数据或者内存数据结果的过程,物化后的数据通常可以用来做数据过滤、聚合计算、Join。延迟物化可以尽可能推迟物化操作的发生,对缓存、CPU/内存带宽友好,可以利用到执行计划和算子的优化,可以保留直接在压缩列做计算的机会。
-
向量化:使用SIMD指令完成代码设计和执行的逻辑,对数据格式和执行模型存在要求,列式存储适合设计这样的数据格式和执行模型。
- SIMD:对于现代多核CPU,有能力用一条指令执行多条数据。
- 数据格式要求:数据需要连续内存,需要明确数据类型。
- 执行模型要求:数据需要按批读取,函数的调用需要明确数据类型。
缺点:
- 选择完成时,被选择的列要重新组装。
- INSERT/UPDATE比较麻烦
- 点查询不合适
适用场景:
- 统计分析类查询(OLAP,比如数据仓库业务,此类型的表上会做大量的汇聚计算,且涉及的列操作较少,关联、分组操作较多)
- 即时查询(查询条件不确定时,行存表扫描难以使用索引)
ClickHouse的存储设计
表定义和结构
-
分布式表:不存储数据,用于将查询路由到集群的各个节点
- cluster:逻辑集群,由多个节点组成
- shard_key:指导数据写入分布式表时的分布方式
-
本地表:实际存储数据的表
集群架构
支持横向扩容和主备容灾:横向通过shard进行扩容,同时支持备份操作,如下图右侧每个节点均存在一个备份节点。
引擎架构
与常见的数据库架构相同,主要包括客户端、查询管理和数据管理三部分。
索引设计
-
Hash Index
- 将输入的key通过一个HashFunction映射到一组bucket上
- 每个bucket都包含一个指向一条记录的地址
- 哈希索引在查找的时候只适用于等值比较
-
B-Tree
-
数据写入是有序的,支持增删改查
-
每个节点都按照升序排列key值
-
每个key有两个指向左右孩子节点的引用:
左孩子节点保存的key都小于当前的key
右孩子节点保存的key都大于当前的key
-
-
B+Tree
- 所有的数据都存储在叶子节点,非叶子节点只保存key值
- 叶子节点维护到相邻叶子节点的引用
- 可以通过key值做二分查找,也可以通过叶子节点做顺序访问
-
LSM tree(Log-structured merge-tree)
-
是一种为大吞吐写入场景而设计的数据结构,着重优化顺序写入,主要数据结构为SSTable和Memtable
-
SSTable:
Key按顺序存储到文件中,称为segment
SSTable中包含有多个segment
每个segment写入磁盘后都是不可更改的,新加的数据只能生成新的segment
-
Mentable:
在内存中的数据保存在memtable中,大多数实现都是一颗Binary search tree
当memtable存储的数据到达一定的阈值时就会按顺序写入到磁盘
-
数据查询
需要从最新的segment开始遍历每一个key
也可以为每个segment建立一个索引
-
数据合并(Compaction)
指将多个segment合并成一个segment的过程
一般由一个后台线程完成
不同的segment写入新的segment的时候也需要排序,形成新的segment之后,就的segment就会被删除
-
索引实现
索引内部结构
-
数据按照主键顺序一次排序
-
数据被划分为granules(最小的数据读取单元,不同的granules可以并行读取)
-
每个granule对应primary.idx里面的一行,需要全部加载到内存中,保存的每一行数据称为一个index mark,每一列都有自己的mark文件
-
mark文件每一行保存两个地址可用于索引:
block_offset:定位一个granule的压缩数据在屋里文件中的位置,压缩数据会以一个block为单位解压到内存中
granule_offset:用于定位一个granule在解压之后的block中的位置
缺陷:数据按照key的顺序做排序,只有第一个key的过滤效果好,后面key的过滤效果依赖于第一个key的基数大小。
查询优化:
- 主键包含的数据顺序写入
- 主键构造一个主键索引
- 每个列构建一个稀疏索引
- 通过mark的选择让主键索引可以定位到每一列的索引
- 可以通过多种手段优化非主键列的索引
数据查询步骤
- 通过主键找到需要读的mark
- 切分mark,然后并发调度reader
- reader通过mark block_offset得到需要读的数据文件的偏移量
- reader通过mark granule_offset得到解压之后数据的偏移量
- 构建列式filter做数据过滤
CLickHouse应用场景
-
大宽表存储和查询:
- ClickHouse可以建非常多列,可以增加、删除、清空每一列的数据,查询的时候引擎可以快速选择需要的列,可以将列涉及到的过滤条件下推到存储层而加快查询速度。
- 具有动态的表结构
-
离线数据分析:便于数据导入
- 数据可以通过spark生成click house文件
- 支持按列导入
-
实时数据分析:使用memory table可以减少parts数量
-
复杂类型查询:bitmap、bitmap64以及lowcardinality
总结
- ClickHouse是标准的列存结构
- 存储设计是LSM-Tree架构
- 使用稀疏索引加速查询
- 每个列都有丰富的压缩算法和索引结构
- 具备基于列存设计的高效的数据处理逻辑