分类
Articles

ClickHouse内幕(10)列的内存结构

ClickHouse是一个基于列存的计算引擎,也就是说其数据计算是基于列的,在计算机世界中数据结构与算法是形影不离的两兄弟,在ClickHouse中数据在内存中也是基于列存储的。这里总结ClickHouse中不同类型的列在内存中的存储结构。

1.定长列

典型的定长列比如:UInt8、Int64、FixedString等,它们的特点是每个数据的长度是固定的。如果需要获取第n行的数据,可以直接通过获取到。

2.变长列

典型的变长列比如:String等,它们的特点是每个数据的长度是不固定的,如果需要获取第n行的数据,需要知道它的起始地址和长度,所以需要维护一个额外的offset数据。

3.Nullable列

Nullable列表示允许空值的列,比如:Nullable(UInt8)、Nullable(String)等,在原始列的基础上额外存储一个UInt8类型的列,充当该行是否是Null的mask。

4.LowCardinality列

如果列的基数集很小,并且列是变长的或者列的value很大,可以将其中每个不同的值进行编码,然后实际列中只存储编码后的值,因为编码后的值是定长的并且每个值占用的存储空间更小,这样就以达到提升计算效率和节省存储空间的目的。

5.Array列

Array列是一个复杂数据类型,每行可以包含多个数据,整体的存储思路是首先将每行的多个数据展评,然后所有行的数据存储在一个连续的内存空间中,然后在额外设置一个offset来划分每行的数据。

请注意图中的Flat data column是一个逻辑图,其中的column可以是定长的也可以是变长的。

6.Tuple列

Tuple列是表示一个元组,比如:Tuple(UInt8, String, UInt8),在ClickHouse中直接按照多个列的方式组织数据。

7.Map列

Map中的每个值可以包含多个entry,每个entry其实是一个两个元素的Tuple,所以组合Array和Tuple的存储思路,ClickHouse中设置了两个Flat column分别表示Map的key和value,同时多个entry在两个Flat column中是被打平的,然后设置一个额外的offset分割每行的值。

8.const列

const列表示常量的列,可以直接存储一个常量值和行数

9.Sparse列

如果一个列除了默认值外的值的比例很低,那么ClickHouse会自动对其进行Sparse编码。参考:参数ratio_of_defaults_for_sparse_serialization。

Sparse编码的思路是设置一个列值存储非默认值,然后设置一个额外的offset记录非默认值对应的行号。

10.Variant列

Variant列表示多个普通列的union,比如variant(String, UInt32, Array(UInt32)),表示改列的值可以是String, UInt32或者Array(UInt32)。

存储的整体思路是首先对每个数据类型的列单独存储(下文成其为单独存储列),然后对其进行编号(Discriminator ID),其次设置一个额外的索引列表示每行的数据对应的单独存储列的ID,最后在设置一个额外的表示每行的数据在单独存储列中的offset。

11.Dynamic列

Dynamic列与Variant列基本一致,数据存储方式也是一致的,区别是Dynamic列不需要提前定义好数据数据类型。

12.Json列

Json列是最复杂的数据类型,它的实现基于Dynamic列,这里举一个列子:

CREATE TABLE default.test (json JSON(a.b UInt32)) ENGINE = Memory;

// test contains 3 rows
┌─json────────────────────────────────────────┐
1. │ {"a":{"b":"42"},"c":["1","2","3"]}          │
2. │ {"f":"Hi"}                       │
3. │ {"a":{"b":"43","e":"10"},"c":["4","5","6"]} │
   └─────────────────────────────────────────────┘

json列是一个Json类型的列,在ClickHouse中Json列是以Json Paths为单位存储的,Json Path是所有Json值中的path的集合,并且不包含非叶子节点,比如上表中的Json Path为:

SELECT distinctJSONPaths(json) FROM test;

   ┌─distinctJSONPaths(json)─┐
1. │ ['a.b','a.e','c','f']   │
   └─────────────────────────┘

在ClickHouse中Json Path分成两类:1.Typed Json Path;2.Non-Typed Json Path。上表中Json Path a.b指定了数据类型UInt32,那么它是Typed,其它的是非Typed的。

Json的整体存储思路是每个Json Path单独存储一个列(下文称为单独存储列),如果Json Path是Typed的那么其单独存储列的数据类型是固定的,其它的Json Path的单独存储列的数据类型为Dynamic的。

PS:为了便于理解上图中没有展开单独存储列的结构

13.小结

本文汇总了ClickHouse中各种类型的列在内存中的存储结构,其中包括简单数据类型、复杂数据类型。

从其设计中可以总结出四条原则:

  1. 紧凑存储,以在物理上连续的数组为基础
  2. 组合:复杂数据结构以简单数据结构组合而成,比如:Tuple、Map、Variant、Json等
  3. 打平多entry的列:比如Array、Map
  4. 兼顾高性能和存储空间:比如Sparse、LowCardinality等

本作品采用 知识共享署名 4.0 国际许可协议 进行许可, 转载时请注明原文链接。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注