[05数据库与存储 - ClickHouse] 18 - ClickHouse - 列存储 | 青训营笔记

375 阅读8分钟

这是我参与「第五届青训营」伴学笔记创作活动的第18天。今天的内容是关于列存储的,以 ClickHouse 为例。

1 数据库的基本概念

数据库定义:数据库是结构化信息或数据的有序集合,一般以电子形式存储在计算机系统中。通常由数据库管理系统来控制。

  • 关系型数据库:把数据以表形式进行存储,再在各个表之间建立关系,通过表之间的关系来操作不同表之间的数据。
  • 非关系型数据库:NoSQL 或非关系型数据库,支持存储和操作非关系型数据或半结构化的数据。NoSQL 没有固定的表结构和表间关系,数据之间可以是独立的。
  • 单机数据库:在一台计算机上完成数据库的存储和查询。
  • 分布式数据库:由位于不同站点的两个或多个文件组成。数据库可以存储在多台计算机上。
  • OLTP 数据库:online transactional processing,高速分析数据库,专为多个用户执行大量事务而设计
  • OLAP 数据库:online analytical processing,旨在同时分析多个数据维度,帮助团队更好地理解其数据中的复杂关系

ClickHouse 是一个关系型的、分布式的 OLAP 数据库。

1.1 OLAP 数据库

OLAP 数据库可以实现大量数据的读写,PB级别的存储(如何优化读写查询);可以进行多维分析,使用复杂的聚合函数;提供窗口函数,自定义 UDF (user define function);离线实时分析。

1.2 SQL

格式:select expression from table where condition group by expression order by expression

优点:标准化、高度非过程化、同一种语法提供2种使用方式(直接操作或嵌入高级语言)、语言简洁

1.3 数据库的架构

Parser 负责进行词法分析、语法分析、生成 AST。

Analyzer:负责变量绑定、类型推导、语义检查、安全、权限检查、完整性检查等。

Optimizer:进行评估,用来查询生成性能最优的执行计划。

Executor:将执行计划翻译成可执行的物理计划并驱动它执行。

1.4 存储引擎

存储引擎的作用主要包括管理内存数据结构、管理磁盘数据、读写算子。

2 列式存储

2.1 列式存储的优点

2.1.1 压缩算法

列式存储中在数据压缩的优点:减少读的数据量(IO 密集型计算时具有性能优势)、相同数据类型压缩率更高、排序后压缩率更高、可以针对不同数据类型选择不同压缩算法。

LZ4 压缩算法

输入:abcde_bcdefgh_abcdefghxxxxxxx

输出:abcde_(5,4)fgh_(14,5)fghxxxxxxx

(5, 4) 表示向前5个 byte 匹配到的内容长度为4。重复项越长或越多,压缩率越高。

Run-length encoding

输入:WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW

输出:12W1B12W3B24W1B14W

压缩重复的数据。

Delta encoding

输入:105, 135, 112, 135, 143, 147

输出:105(base), 30, -23, 23, 8, 4

把数据存储为连续数据之间的差异,而非数据本身。

2.1.2 数据处理

可以选择特定列计算,对聚合计算友好。

2.1.3 延迟物化

物化:把一种数据格式变成另一种数据格式。

延迟物化:把数据类型/格式的转化时间尽可能推迟。

优点:缓存友好、CPU / 内存带宽友好、可以利用到执行计划和算子的优化,例如filter、保留直接在压缩列做计算的机会

2.1.4 向量化

SIMD (single instruction multiple data):对于现代多核CPU,其都有能力用一条指令执行多条数据(如 for 循环)

数据格式要求:连续内存、明确数据类型

执行模型要求:数据按批读取、函数调用需要明确数据类型

2.2 行存和列存

image.png

3 ClickHouse 存储设计

image.png

3.1 ClickHouse 的架构

分布式表:不存储数据,将查询路由到集群的各个节点(cluster:逻辑集群,包含多个节点;shard_key:指导数据写入分布式表时的分布方式)

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

集群的横纵向分布:

image.png

3.2 ClickHouse 的存储架构

数据结构:

image.png

.bin 文件为数据文件,.mrk 为索引文件。

关于 part 和partition,part 是物理文件夹的名字,partition 是逻辑结构。在内存中可以看到 partition 中包含哪些 part。

关于 part 和 column,每个column都是一个文件,所有的column文件都在自己的part文件夹下。

关于 column 和 index,一个part有一个主键索引,每个column都有列索引。

3.3 ClickHouse 的索引设计

Hash Index

将 key 映射到 bucket 上,每个 bucket 都包含一个指向一条记录的地址。但哈希索引在查找时只适用于等值查询。

b-tree

数据是有序的,且支持增删改查。每个节点包含多个孩子,按照升序排列 key,每个 key 有2个指向左右孩子节点的引用。

b+ tree

所有数据都存在叶子节点,非叶子节点只保存 key。叶子节点维护到相邻节点的引用。可以通过 key 做二分查找,也可以通过叶子节点做顺序查找。

ClickHouse 不使用 b tree 或 b+ tree 的原因:对于大数据量,树的深度太高;受制于列存,需要建立索引,索引数据量太大,多个列如何平衡查询和存储;OLAP 写入量非常大。

实际应用的索引 LSM tree (Log-structured merge tree)

着重优化顺序写入,主要数据结构为 SSTables 和 Memtable。

SSTables

  1. key 按顺序存储到文件中,称为 segment
  2. 包含多个 segment
  3. 每个 segment 写入磁盘后都是不可更改的,新增数据只能生成新的 segment

Memtable

  1. 在内存中,数据存在 memtable 中,大多数实现都是一颗 binary search tree
  2. 当 memtable 的数据存储达到阈值时,就会写入磁盘

数据查询:从最新的 segment 开始遍历每个 key;也可以给每个 segment 建索引

数据合并 compaction:指将多个 segment 合并成一个 segment;一般由一个后台线程完成;不同 segment 写入新的 segment 的时候也要排序;形成新的 segment 后原先的 segment 会被删除。

在数据存储到磁盘时会划分为 granule,也是最小的读取单元,不同的 granule 可以并行。每个 granule 对应 primary.idx 中的一行。默认每8192行记录主键的一行值,primary.idx 需要被全部加载到内存里面。6每个主键的一行数据被称为一个 mark。每个列都有这样一个 mark 文件,mark 文件存储所有 granule 在物理文件里面的地址,每一列都有一个 mark 文件。

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

3.4 索引的缺点和优化

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

解决方法:建立二级索引 secondary index、构建多个主键索引、再建一个表(缺点:数据需要同步两份,查询需要用户判断查哪张表)、建一个物化视图(数据自动同步到隐式表,但是需要用户判断查哪张表)、使用projection(数据自动同步到隐式表,查询自动路由到最优的表)

3.5 数据合并

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

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

3.6 数据查询

查询过程:

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

4 ClickHouse 典型应用场景

4.1 大宽表存储和查询

可以建非常多的列;查询的时候;可以增加、删除、清空每一列;引擎可以快速选择需要的列;可以将列涉及的过滤条件下推到存储层,从而加快查询。

动态表结构:map 中每个 key 都是一列;map 中每一列都可以单独查询;使用方式同普通列,可以进行任何计算。

4.2 离线数据分析

数据导入:数据可以通过 spark 生成 clickhouse 格式的文件,导入到 hdfs 上由 hive2ch 导入工具完成数据导入,数据直接导入到各个物理节点。(缺点:需要 spark 把数据全部准备好)

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

4.3 实时数据分析

image.png

要求:数据可以被立刻查询

使用 memory table 减少 parts 数量:数据先缓存在内存中,到达一定阈值再写到磁盘

4.4 复杂类型查询

bitmap 索引

建立:

image.png

查询:

image.png

bitmap64 类型

汇总有相同值的 key。

lowcardinality

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