ClickHouse
2021-11-24 14:28:34 2 举报
AI智能生成
ClickHouse关键流程和原理总结
作者其他创作
大纲/内容
存储结构
MergeTree
目录结构
命名标准
PartitionId_MinBlockNum_MaxBlockNum_Level
PartitionId
分区Id
MinBlockNum
最小数据块编号
MaxBlockNum
最大数据块编号
Level
分区被合并的次数
目录内文件列表
primary.idx
描述
索引文件
作用
用于存放稀疏索引
[Column].mrk2
描述
标记文件
作用
保存了bin文件中数据的偏移信息,用于建立primary.idx和[Column].bin之间的映射
[Column].bin
描述
数据文件
作用
存储数据,默认使用lz4压缩存储
partition.dat
描述
分区文件
作用
保存分区表达式生成的值
minmax_[Column].idx
描述
minmax索引
作用
用于记录当前分区下分区字段的最小值和最大值
columns.txt
描述
列信息文件
作用
保存表的列字段信息
count.txt
描述
列数文件
作用
记录当前分区目录下数据的总行数
checksums.txt
描述
校验文件
作用
存放以上各个文件的size以及hash值,用于快速校验文件的完整性
文件组织
表结构示例
数据插入
设置index_grandulity为2
文件详解
primary.idx
按照主键字段生成,用于加快表查询
每隔index_granularity行数据就会取(Id,Name)的值作为索引值
(3,Lisa)
(6,Meimei)
31,vincent
id 是UInt64类型,用8字节小端模式存储,所以前8个字节是0X03
小端从右往左读
大端从左往右读
Name是String类型,变长字段,先使用1个字节存储String长度,Lisa长度是4,所以第九个字节是0X09
Id.mrk2
mrk2文件格式
offset in compressed file
大小
8 bytes
数据
代表该标记指向的压缩数据块在bin文件中的偏移量
offset in decompressed block
大小
8 bytes
数据
代表该标记指向的数据在解压数据块中的偏移量
Rows count
大小
8 bytes
数据
行数,通常情况下等于index_granularity
partition.dat
表达式生成的值为200002,UInt32类型,转换为16进制就是0x00030d42
minmax_Birthday.idx
分区字段的Birthday的类型为data,底层由UInt16实现,存储的是从1970年1月1日到现在所经过的天数
插入数据的最小值和最大值分别是2000-02-03和2000-02-08
转换为天数分别是10990和10995
转换为16进制分别为0X2aee和0x2af3
Id.bin
由若干个Block组成
checksum
大小
16 bytes
功能
对后面的数据进行校验
compression algorithm
1 byte
默认LZ4,编号0X82
compressed size
4 bytes
其值等于Compression algorithm + Compressed size + Decompressed size + Compressed data的长度
decompressed size
4 bytes
数据解压缩后的长度
compressed data
压缩数据
长度为Compressed size - 9
Granule
数量由min_compress_block_size控制
每次block写完一个granule的数据时,会校验当前block size是否大于等于min_compress_block_size
如果大于,则把当前block进行压缩然后写到磁盘
如果不大于,则继续等待下一个granule
(3,4,6,12)总大小为32,大于24,所以压缩到第一个Block中,最后一个31在第二个Block
索引过程
过程拆分
基于主键列查询
确定primary.idx中的数据位置
二分查找
primary.idx文件较小,可以常驻内存
确定id.mrk2中的位置
primary.idx数据与mrk2数据是一一对应的
从逻辑上来说,可以认为两者是下标对齐的
确定block位置
先根据offset in compressed file 确定在哪个block中(block起始偏移量)
再根据offset in decompressed block确定在选中block中的位置(相对偏移量,从0开始)
基于非主键列查询
全表扫描
分区合并
合并原则
PartitionId,分区Id不变
MinBlockNum,取该分区中最小的MinBlockNum
MaxBlockNum,取该分区中最大的MaxBlockNum
Level,取该分区中最大的level加1
示例
上面的两个分区目录200002_1_1_0和200002_2_2_0在过一段时间后最终会变成一个新的分区目录200002_1_2_1
建表要素
ENGINE
表引擎
Log系列
原理
数据被顺序append写到磁盘上
不支持delete,update
不支持索引
不支持原子写
插入阻塞查询
分类
TinyLog
不支持并发读取,性能差,格式简单,适合存储中间数据
Log
支持并发读取,每个列存储在单独文件中,性能较好
StripeLog
支持并发读取,所有列存放在一个文件中,性能较好
Integration系列
HDFS
MySQL
Kafka
RabbitMQ
JDBC
ODBC
MergeTree系列
MergeTree
简介
目前CK处理能力最好的引擎
支持索引,同时提供数据的实时更新能力
ReplacingMergeTree
按主键移除重复记录
不保证任意时刻都不出现重复
原理
在执行分区合并时,会触发删除重复数据,无法预测具体执行时间点,除非是手动执行
optimize命令,耗时较大
以分区为单位删除重复数据,只有在相同的数据分区内重复的数据才能够被删除
如果没有设置版本号,则保留最新插入的数据,否则保留ver字段最大的一行
SummingMergeTree
按主键对数值类型的列求合
其余的列会保留先插入的值,后插入的值会被丢弃
AggregatingMergeTree
对主键做聚合
uniq
anyif
quantiles
CollapsingMergeTree
写新state行是,异步折叠(删除)与之排序key重复的行
用于数据快速更新最新值并顺序入表的场景
VersionedCollapsingMergeTree
基本同CollapsingMergeTree
允许多线程乱序入表
通过Version列保证正确
GraphiteMergeTree
作为后端存Graphite数据
Special系列
Memory
数据存储在内存,重启数据丢失,读写不阻塞,不支持索引
Merge
在同一个server上吧多张相同结构的物理表合并为一张逻辑表
Distributed
在不同的server上吧多张相同结构的物理表合并为一张逻辑表
...
PARTITION BY
分区建
ORDER BY
排序键
指定数据以何种方式进行排序
默认情况下排序建和主键相同
SETTINGS
配置
index_granularity
索引粒度
每隔多少行数据生成一条索引
默认8192
min_compress_block_size
表示最小压缩的block大小
默认65536
index_granularity_bytes
自适应索引
限制大小生成一条索引
默认10M
为0表示不启用自适应功能
enbale_mixed_granularity_parts
是否开启自适应索引功能
默认开启
merge_with_ttl_timeout
数据TTL功能
storage_policy
多路径存储策略
物化视图
本质
就是一个临时表
创建语句
CREATE MATERIALIZED VIEW download_hour_mv
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(hour) ORDER BY (userid, hour)
AS SELECT
toStartOfHour(when) AS hour,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download WHERE when >= toDateTime('2020-09-01 04:00:00')
GROUP BY userid, hour
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(hour) ORDER BY (userid, hour)
AS SELECT
toStartOfHour(when) AS hour,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download WHERE when >= toDateTime('2020-09-01 04:00:00')
GROUP BY userid, hour
更新机制
在源数据表有数据写入时,如果检测到有物化视图跟它关联,会针对这批写入的数据进行物化操作
只能保证最终一致性
主从复制
ReplicatedMergeTree
zookeeper依赖
分布式DDL执行
执行过程
查询节点处理请求
节点收到用户的分布式DDL请求
节点校验DDL合法性
LogEntry
struct DDLLogEntry
{
String query;
std::vector<HostID> hosts;
String initiator; // optional
static constexpr int CURRENT_VERSION = 1;
...
}
{
String query;
std::vector<HostID> hosts;
String initiator; // optional
static constexpr int CURRENT_VERSION = 1;
...
}
,在zookeeper任务队列中创建znode并上传DDL LogEntry
同时在LogEntry的Znode下创建active和finish两个状态同步的znode
Cluster节点处理请求
cluster中的节点后台消费线程消费zookeeper中的LogEntry队列执行处理逻辑
处理过程中吧自己注册到active znode下,并把结果写回到finish znode下
查询节点读取Cluster执行结果
轮询LogEntry Znode下的active和finish状态znode
当目标节点全部执行完成或者触发超时逻辑时,用户就会获得结果反馈
ReplicatedMergeTree表主备节点之间状态同步
同步类型
写入同步
异步Merge同步
异步Mutation同步
同步特征
不是经典的主从模型(主节点执行更新,从节点同步follow),主备都可以写,同步是双向的
不是物理同步,没有基于物理文件的wal
逻辑同步日志粒度是MergeTree的DataPart级别(Data Part Log),没有单记录的同步日志
同步过程
同步写入
过程分解
把数据写入到本地临时Data Part中
从zookeeper上申请自增的block number序列号
commit临时Data Part
前置检查
检查本地表的meta版本是否已经落后于zookeeper上的状态
上传一个GET_PART类型的Log到Shard对应的Zookeeper目录下的log znode下
其他副本通过观察zookeeper来异步拷贝写入的Data Part
性能问题
一次Batch写入的过程和zookeeper交互的次数不下10此,要是Batch数据跨10个数据分区的话就是100次
使用Clickhouse时一定要做Batch写入并且按照数据分区提前聚合
一致性保证
望Zookeeper提交GET_PART Log时Zk session断开或者超时
本地的Data Part会继续commit,并跑错给用户重试写入数据,同时把Data Part丢到一个异步检查线程的任务队列中
异步检查线程会等待重连Zookeeper,检查本地的Data Part是否注册到Zookeeper上
如果没有则会移除本地的Data Part
优化参数
use_minimalistic_part_header_in_zookeeper
降低zookeeper压力配置
每个新写入的Data Part不再注册自己的columns信息和checksums到Zookeeper上
而是压缩成Hash值写到Data Part的Znode data中
insert_quorum
写入链路检查数据同步的副本数达到要求才能够成功返回
写入节点在Commit Data Part时还会创建一个Shard级别的quorum/status Znode,其他节点同步完数据之后需要更新到quorum/status,写入节点这边通过Watch机制收到通知再返回客户的写入请求
insert_deduplicate
对每次收到的批量写入数据计算一个Hash Value,然后注册到Zookeeper上。后续如果出现完全重复的一批数据,写入链路上会出现Zookeeper创建重复节点异常,用户就会收到重复写入反馈
异步Task
StorageReplicatedMergeTree::queueUpdatingTask
同步Zookeeper中Shard级别下的Data Part Log任务队列数据到自己的Znode任务队列中
在自己的Znode下维护更新当前正在处理的log_pointer(当前已经拷贝过的最大log Id)和min_unprocessed_insert_time(近似评估写入的延迟时间)信息
把任务放到节点的RAM队列中
StorageReplicatedMergeTree::mutationsUpdatingTask
从Zookeeper的Shard级别下的Mutation任务队列同步数据到节点的RAM状态中
是依赖Zookeeper的Watch机制来通知ClickHouse的BackgroundSchedulePool调度起工作Task,包括上一个queueUpdatingTask也是相同机制被调度
StorageReplicatedMergeTree::queueTask
负责从RAM任务队列中消费执行具体的操作,并且会有多个后台线程被调度起并行执行多个任务
ClickHouse在RAM状态中追踪了所有正在执行的任务即将产生和依赖的Data Part,可以保证有数据依赖关系的任务串行化执行
对于"GET_PART"类型的任务,Task执行逻辑会尝试从远端节点下载数据到本地,同时如果有quorum数量要求的话更新quorum统计信息
对于"MERGE_PARTS","MUTATE_PART"的任务,节点首先会尝试在本地进行实际的merge或者mutation动作,但是当本地的Input Data Part存在缺失或者损坏时,ClickHouse可以采用保守策略
尝试从远端下载merge完成的Data Part
每次merge、mutation的开销都是非常大的,配置只选择主副本完成merge、mutation任务,而让其他副本直接从远程下载可以大幅减轻集群的负载
当一些极端场景出现,远端的结果Data Part N也无法下载时(一般是这个任务对应的远端Data Part N再次发生了数据变更变成了Data Part M),节点会把当前这个任务放回到任务队列的尾端,让它延迟执行
StorageReplicatedMergeTree::mergeSelectingTask
这个Task的只有主副本节点会调度,它负责不断选择下一次要进行merge / mutation的Data Parts,把具体的merge / mutation的任务日志发布到Zookeeper的任务队列上
新写入的Data Part只有同步到全部副本节点后才可以参与merge
StorageReplicatedMergeTree::movePartsTask
这个异步Task主要是配合ClickHouse的存储分层设计
当高性能(SDD)的存储空间快用满时,它会不断自动地把数据往更低级(HDD)的存储上去迁移
StorageReplicatedMergeTree::mutationsFinalizingTask**
这个Task的作用是异步去更新当前副本的mutation任务队列执行进度
0 条评论
下一页