- 这是我参与「第五届青训营 」伴学笔记创作活动的第 19 天
1. ClickHouse存储设计
1.1 索引设计
Log-structured merge-tree(LSM tree)是一种为大吞吐写入场景而设计的数据结构
-
着重优化顺序写入
-
主要数据结构
- SSTables
- Memtable
SSTables:
- Key按顺序存储到文件中,称为segment
- 包含多个segment
- 每个segment写入磁盘后都是不可更改的,新加的数据只能生成新的segment
Memtable:
- 在内存中的数据保存在memtable中,大多数实现都是一颗Binary search tree
- 当mentable存储的数据到达一定的阈值的时候,就会按顺序写入到磁盘
数据查询:
- 需要从最新的segment开始遍历每个key
- 也可以为每个segment建一个索引
Compaction(合并):
- Compaction指将多个segment合并成一个segment的过程
- 一般是有一个后台线程完成
- 不同的segment写入新的segment的时候也需要排序,形成新的segment之后,旧的segment文件就会被删除
主键索引:
CREATE TABLE hits_UserID_URL
(
'UserID' Uint32,
'URL' STRING,
'EventTime' DATETIME
)
ENGINE = MergeTree
PRIMARY KEY (UserID, URL)
ORDER BY (UserID, URL, EventTime)
SETTINGS index_granularity = 8192, index_granularity_bytes = 0;
数据按照主键顺序依次做排序
- 首先按照UserID做排序
- 再按照URL排序
- 最后是EventTime
数据被划分为granules
-
granules是最小的数据读取单元
-
不同的granules可以并行读取
-
每个granules都对应primary.idx里面的一行
-
默认每8192行记录主键的一行值,primary.idx需要被全部加载到内存里面
-
里面保存的每一行数据称为一个index mark,每个列都有这样一个mark文件
-
mark文件保存的是每个granules的物理地址
-
每一列都有一个自己的mark文件
-
mark文件里面的每一行保存两个地址
- block_offset:用于定位一个granule的压缩数据在物理文件中的位置,压缩数据会以一个block为单位解压到内存中
- granule_offset:用于定位一个granule在解压后的block中的位置
-
缺陷:
- 数据按照key的顺序做排序,因此只有第一个key的过滤效果好,后面的key过滤效果依赖第一个key的基数大小
1.2 查询优化
secondary_index:在URL列上构建二级索引
构建多个主键索引
-
再建一个表,使用需要优化的字段做主键第一位
create table hits_URL_UserID ( 'UserID' UInt32, 'URL' String, 'EventTime' DateTime ) ENGINE = MergeTree PRIMARY KEY (UserID, URL) ORDER BY (UserID, URL, EventTime) SETTINGS index_granularity = 8192, index_granularity_bytes = 0;- 数据需要同步两份
- 查询需要用户判断查哪张表
-
建一个物化视图
-
物化视图:可以通过select查询将一个表的数据写入一张隐式表
- 数据自动同步到隐式表
- 查询需要用户判断查哪张表
-
-
使用Projection
-
Projection:类似于物化视图,但是不是将数据写入新的表,而是存储在原始表中,以一个列文件的形式存在
- 数据自动同步到隐式表
- 查询自动路由到最优的表
-
小结:
- 主键包含的数据顺序写入
- 主键构造一个主键索引
- 每个列构建一个稀疏索引
- 通过mark的选择让主键索引可以定位到每一列的索引
- 可以通过多种手段优化非主键列的索引
1.3 数据合并
一个part内的数据是有序的
不同part之间的数据是无序的
数据合并是将多个part合并成一起的过程
数据的可见性:
- 数据合并过程中,未被合并的数据对查询可见
- 数据合并完成后,新part可见,被合并的part被标记删除
1.4 数据查询
- 通过主键找到需要读的mark
- 切分mark,然后并发的调度reader
- Reader通过mark block_offset得到需要读的数据文件的偏移量
- Reader通过mark granule_offset得到解压后数据的偏移量
- 构建列式filter做数据过滤
2. ClickHouse应用场景
2.1 大宽表存储和查询
-
大宽表查询
- 可以建非常多的列
- 可以增加,删除,清空每一列的数据
- 查询的时候引擎可以快速选择需要的列
- 可以将列涉及到的过滤条件下推到存储层,从而加速查询
-
动态表结构
- map中的每个key都是一列
- map中的每一列都可以单独查询
- 使用方式同普通列,可以做任何运算
2.2 离线数据分析
-
数据导入
- 数据可以通过spark生成clickhouse格式的文件
- 导入到hdfs上由hive2ch导入工具完成数据导入
- 数据直接导入到各个物理节点
-
数据按列导入
- 保证查询可以及时访问已有数据
- 可以按需加载需要的列
2.3 实时数据分析
使用memory table减少parts数量
- 数据先缓存在内存中
- 到达一定阈值再写到磁盘
复杂类型查询:
-
bitmap索引(构建、查询)
-
bitmap64类型
-
lowcardinality
- 对于低基数列使用字典编码
- 减少数据存储和读写的IO使用
- 可以做运行时的压缩数据过滤
总结
- ClickHouse是标准的列存结构
- 存储设计是LSM-Tree架构
- 使用稀疏索引加速查询
- 每个列都有丰富的压缩算法和索引结构
- 基于列存设计的高效设计处理逻辑