ClickHouse-你没有见过的列存储 | 青训营笔记

90 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 18 天

本节课程分为四个部分:数据库基本概念、列式存储、ClickHouse存储设计、ClickHouse典型应用场景

1、数据库

数据库是结构化信息或数据的有序集合,一般以电子形式存储在计算机系统中。通常由数据库管理系统(DBMS)来控制。在现实中,数据、DBMS及关联应用一起被称为数据库系统,通常简称为数据库

1.1 数据库的类型

数据库有很多种,至于各种数据库孰优孰劣,主要取决于企业希望如何使用数据

1.1.1 关系数据库

关系型数据库是以表形式储存数据,在各个表之间建立关系,通过表之间关系来操作不同表之间的数据

1.1.2 非关系数据库

NoSQL或非关系数据库,支持存储和操作非结构化及半结构化数据。对比关系型数据库,NoSQL没有固定表结构,数据之间不存在表与表之间的关系,数据之间可以独立。NoSQL放弃了传统关系型数据库的强事务保证和关系模型,通过所谓最终一致性和非关系数据模型(例如键值对、图、文档)来提高Web应用所注重的高可用性和可扩展性

1.1.3 单机数据库

在一台计算机上完成数据的存储和查询的数据库系统

1.1.4 分布式数据库

分布式数据库由位于不同站点的两个或多个文件组成。数据库可以存储在多台计算机上,位于同一个物理位置,或分散在不同的网络上

1.1.5 OLTP数据库

OLTP(Online Transactional Processing)数据库是一种高速分析数据库,专为多个用户执行大量事务设计

1.1.6 OLAP数据库

OLAP (Online Analytical Processing)数据库旨在同时分析多个数据维度,帮助团队更好地理解其数据中的复杂关系

大量数据的读写,PB级别的存储

多维分析,复杂的聚合函数

离线/实时分析,对查询速度有要求

1.2 SQL

一种编程语言,目前几乎所有的关系数据库都使用SQL(Structured Query Language)编程语言来查询、操作和定义数据,进行数据访问控制

1.2.1 SQL结构

 查询包含一系列含有最终结果的字段,紧跟SELECT关键词

FROM子句指定了选择的数据表。可以包含JOIN二层子句来为数据表的连接设置规则

WHERE子句后接一个比较谓词以限制返回的行。WHERE子句仅保留返回结果里使得比较谓词的值为True的行

GROUP BY子句用于将若干含有相同值的行合并。GROUP BY通常与SQL聚合函数连用,或者用于清除数据重复的行。GROUP BY应用在WHERE子句之后

HAVING子句后接一个谓词来过滤从GROUP BY中获得的结果,由于其作用于GROUP BY之上,所以聚合函数也可以放到其谓词中

ORDER BY子句指明将哪个字段用作排序关键字,以及排序顺序,如果无此子句,那么返回结果的顺序不能保证有序

1.3 SQL的用途

定义数据模型、读写数据库数据

1.4 SQL优点

标准化、高度非过程化、同一语法结构提供两种使用方式、语言简洁易学易用(完成数据定义、数据操纵、数据控制的核心功能只用了9个动词:CREATE、ALTER、DROP、SELECT、INSERT、UPDATE、DELETE、GRANT、REVOKE)

1.5 数据库架构&SQL的执行

Client

Parser:词法分析,语法分析,生成AST树(Abstract Syntax Tree)

Analyzer:变量绑定、类型推导、语义检查、安全、权限检查、完整性检查等,为生成计划做准备

Optimizer:为查询生成性能最优的执行计划

进行代价评估:Executor将执行计划翻译成可执行的物理计划

Storage Engine:管理内存数据结构(Index、内存数据、缓存(Query Cache、Data Cache、Index Cache));管理磁盘数据(磁盘数据文件格式、磁盘数据增删查改);读写算子(数据写入逻辑、数据读取逻辑)

2、列式存储

2.1 行式存储

适用于点查询、增删改查操作较多的场景

2.2 列式存储

适用于统计分析类查询(数据仓库业务)、即时查询

数据压缩(LZ4、Run-length encoding、Delta encoding):使读的数据量更少,在O密集型计算中获得大的性能优势。相同类型、排序之后压缩效率更高

数据处理:查询优化,可以选择特定的列做计算而不是读所有列、对聚合计算友好。延迟优化,将列数据转换为可以被计算或者输出的行数据或内存数据结果的过程,物化后的数据通常可以用来做数据过滤,聚合计算,Join。缓存友好、CPU/内存带宽友好、可以利用到执行计划和算子的优化例filter、保留直接在压缩列做计算的机会

向量化:SIMD(Single Instruction Multiple Data),对于现代多核CPU,其都有能力用一条指令执行多条数据。数据需要是连续内存,需要明确数据类型。执行需要按批处理,函数调用需要明确数据类型

3、ClickHouse存储设计

ClickHouse主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告

3.1 表定义和结构

分布式表:不存储数据,用于将查询路由到集群的各个节点

cluster:逻辑集群,由多个节点组成

shard_key:指导数据写入分布式表时的分布方式

本地表:实际存储数据的表

3.2 集群架构

3.3 引擎架构

3.4 存储架构

3.4.1 数据结构

文件组织:索引

文件内容

part和partition:part是物理文件夹的名字、partition是逻辑结构

part和column:每个column都是一个文件、所有的column文件都在自己的part文件夹下

column和index:一个part有一个主键索引、每个column都有列索引

3.5 索引设计

3.5.1 B+树

所有的数据都存储在叶子节点,非叶子节点只保存key值;叶子节点维护到相邻叶子节点的引用;通过key值做二分查找,通过叶子节点做顺序访问

3.5.2 Log-structured merge-tree(LSM tree)

为大吞吐写入场景而设计的数据结构,着重优化顺序写入,SSTables和Memtable是主要数据结构

3.5.2.1 SSTables

key按顺序存储到文件中,被成为segment,每个segment写入磁盘后不可更改,新加的数据只能生成新的segment

3.5.2.2 Memtable

在内存中的数据保存在Memtable中,大多数实现都是一颗二叉搜索树。当Memtable存储的数据到达一定阈值时,就会按顺序写入到磁盘

3.5.3 数据查询

从最新的segment开始遍历每个key或为每个segment建立一个索引

3.5.4 合并(Compaction)

将多个segment合并成一个segment的过程

3.5.5 主键索引

数据按照主键顺序一次排序:UserID->URL->EventTime

granule是引擎做数据处理的最小数据单位,引擎读数据的时候不是按照一行一行读取的,而是最少读取一个granule。方便构建稀疏索引,并行计算

每个granule都对应主键primary里面的一行

默认每8192行记录主键的一行值,primary.idx全部加载到内存里面

每个主键的一行数据被称为一个mark,每个列都有一个mark文件,mark文件存储所有granule在物理文件里面的地址

mark文件里每一行存储两个地址:block_offset,用于定位一个granule的压缩数据在物理文件中的位置,压缩数据会以一个block为单位解压到内存中;granule_offset,用于定位一个granule在解压之后的block中的位置

3.5.6 索引的缺陷和优化

缺陷:数据按照key的顺序排序,只有第一个key的过滤效果好,后面的key过滤效果依赖第一个key的基数大小

二级索引:在URL列上构建二级索引

构建多个主键索引:建表,数据同步备份,查询用户需要判断查哪张表;建物化视图,数据自动同步到隐藏式,查询用户需要判断查哪张表;使用Projection(查询自动路由到最优的表),存储在原始表,以一个列文件的形式存在

3.6 数据合并

一个part内的数据是有序的,不同part之间的数据是无序的,数据合并是将多个part合并成一起的过程,part的合并发生在一个分区内

数据合并过程中,未被合并的数据对查询可见,数据合并完成后,新part可见,被合并的part被标记删除

3.7 数据查询

通过主键找到需要读的mark,切分marks,然后并发的调度reader,reader通过mark block_offset得到需要读的数据文件的偏移量,通过mark granule_offset得到解压之后数据的偏移量,构建列式filter做数据过滤

4、ClickHouse典型应用场景

4.1 大宽表存储和查询

可以建非常多的列,查询的时候引擎可以快速选择需要的列,将列涉及到的过滤条件下推到存储层加速查询

4.2 动态表结构

map中的每个key都是一列,map中的每一列都可以单独的查询,使用方式同普通列,可以做任何计算

4.3 离线数据分析

数据导入:数据可以通过spark生成clickhouse格式的文件,导入到hdfs上,再由hive2ch导入工具完成数据导入。数据直接导入到各个物理节点

数据按列导入:保证查询可以及时访问已有数据,可以按需加载需要的列

4.4 实时数据分析

数据可以被立刻查询。使用memory table减少parts数量。数据先缓存在内存中,到达一定阈值再写到磁盘

4.5 复杂类型查询

bitmap索引

bitmap64类型

lowcardinality:对于低基数列使用字典编码、减少数据存储和读写的IO使用、可以做运行时的压缩数据过滤

课后个人总结

本节课程分为四个部分:数据库基本概念、列式存储、ClickHouse存储设计、ClickHouse典型应用场景,我有数据库基础,通过平时阅读的文章也对数据库进行了深入了解,ClickHouse我是第一次见,列式存储也是初次耳闻,知识点很多,需要慢慢消化