ClickHouse系列教程三:MergeTree引擎分析

ClickHouse系列教程: ClickHouse系列教程


Clickhouse之MergeTree引擎分析
CRUD
Clickhouse支持查询(select)和增加(insert),但是不直接支持更新(update)和删除(delete)。
插入:MergeTree不是LSM树,因为它不包含“memtable”和“log”:插入的数据直接写入文件系统。这使得它仅适用于批量插入数据,而不是非常频繁地插入单行; 每秒一次插入很好,但是每秒一千次不行。如果有大量内容想要插入,可以使用Buffer引擎,Buffer引擎做的是把缓冲数据写入RAM,定期将其刷新到另一个表。
Clickhouse是没有update和delete命令的,根据官方的说法:ClickHouse是以性能为导向的系统,想要性能最好,但修改过的数据很难进行比较好的存储和处理。
Clickhouse通过ALTER的变种实现了UPDATE和DELETE。
Mutations(突变)是一种ALTER查询变体,允许更改或删除表中的行。突变适用于更改表中许多行的操作(单行操作也是可以的)。
该功能处于测试阶段,从1.1.54388版本开始提供。 Mutations的更新功能是版本18.12.14开始提供的,目前MergeTree引擎支持Mutations。
现有表已准备好按原样进行突变(无需转换),但在将第一个突变应用于表后,其元数据格式将与先前的服务器版本不兼容,并且回退到先前版本变得不可能。
命令如下:
ALTER TABLE [db.]table DELETE WHERE filter_expr;
ALTER TABLE [db.]table UPDATE column1 = expr1 [, …] WHERE filter_expr;
注意:更新功能不支持更新有关主键或分区键的列。
对于
MergeTree表,通过重写整个数据部分来执行突变。此操作没有原子性 - 一旦完成准备就会替换突变部分,并且在突变开始执行后,SELECT的查询将看到来自已经突变的部分的数据以及尚未突变的部分的数据。
突变按其创建顺序排序,并按顺序应用于每个部分。INSERT也部分地进行了突变 - 在提交突变之前插入表中的数据将被突变,之后插入的数据将不会被突变。请注意,突变不会以任何方式阻止INSERT。
突变本身使用系统配置文件设置异步执行。要跟踪突变的进度,您可以使用system.mutations表。即使ClickHouse服务器重新启动,成功提交的突变也将继续执行。一旦提交突变,就无法回滚突变,但如果突变由于某种原因而被卡住,则可以通过KILL MUTATION取消突变。
已完成突变的条目不会立即删除,保留条目的数量由finished_mutations_to_keep存储引擎参数确定。 旧的突变条目会被删除。
突变的具体实现过程是先使用where条件找到需要修改的parts(分区),然后重建每个part,用新的part替换旧的part。对于有大的part的表进行重建会很耗费时间(默认一个part最大大小为150G )。突变在每个小的part是原子性的。
如果不想使用突变,毕竟突变不是原子性,而且开销较大,另外一个比较好的选择是使用ReplacingMergeTree替代MergeTree。ReplacingMergeTree会删除主键相同的重复项,删除操作会在合并的过程中完成。而合并是在后台不定时地做,因此你无法预先知道数据是否合并完成。ReplacingMergeTree有个可选字段ver,类型可以为UInt*,Date或者DateTime。合并的时候,ReplacingMergeTree 从同一个分区中的所有具有相同主键的行中选择一行留下 如果 ver 列未指定,选择最后一条; 如果 ver 列已指定,选择 ver 值最大的版本留下。注意:ReplacingMergeTree去重不同分区中的相同主键的行。示例如下:
create table t2 (birth Date, id UInt16, name String, point UInt16,ver UInt8) ENGINE=ReplacingMergeTree(birth, (id, name), 8192,ver);

insert into t2(birth, id, name, point,ver) values (‘2017-04-01’, 1, ‘qwe’, 10,0);
insert into t2(birth, id, name, point,ver) values (‘2017-06-01’, 4, ‘asd’, 15,0);
insert into t2(birth, id, name, point,ver) values (‘2017-04-03’, 5, ‘zxc’, 11,0);
insert into t2(birth, id, name, point,ver) values (‘2017-04-01’, 1, ‘qwe’, 100,1);

select * from t2;

现在2个版本的内容都在,等待优化完成后只保留一个:

如果不想等待,可以在select时在表名后增加关键字FINAL ,但这样会导致查询变慢:
select birth, id, name, point, ver from t2 FINAL ;

分区规则分析
一个分区是指按指定规则逻辑组合一起的表的记录集,可以按任意标准进行分区,如按月,按日或按事件类型。为了减少需要操作的数据,每个分区都是分开存储的。访问数据时,ClickHouse 尽量使用这些分区的最小子集。
分区的使用是为了提高性能,因为分区键列的最小值和最大值存储在每个表部分中,这些值可用于修剪查询所需的表部分。但它的主要用途是促进数据操作任务(例如丢弃旧数据)。如果主键使用得很好,那么分区不会提高性能。
MergeTree引擎默认是以表中的Date字段作为分区 (partitions)的标志。如果不手动指定,Clickhouse会把Date字段的年和月自动作为分区的标准。
下面是运行实例:
drop table t;
create table t (birth Date, id UInt16, name String, point UInt16) ENGINE=MergeTree(birth, (id, name), 10);
insert into t(birth, id, name, point) values (‘2017-04-01’, 1, ‘qwe’, 10);
insert into t(birth, id, name, point) values (‘2017-06-01’, 4, ‘asd’, 15);
insert into t(birth, id, name, point) values (‘2017-04-03’, 5, ‘zxc’, 11);
所有表的分区情况都存在system.parts这个表中,查询分区情况:
SELECT name,min_date,max_date,min_block_number,max_block_number,level,partition,active
FROM system.parts
WHERE table = ‘t’;

Name是这个分区的名字,比如说20170401_20170401_1_1_0是由
min_date,分区中最小的日期:20170401
max_date,分区中最大的日期:20170401
min_block_number,数据块的最小编号
max_block_number,数据块的最大编号
level,块级别,即在由块组成的合并树中,该块在树中的深度
组成的。
最后一个参数activate是指这个分区是否处于激活状态,1为激活状态,0为非激活状态。非激活片段是那些在合并到较大片段之后剩余的源数据片段。损坏的数据片段也表示为非活动状态。
这时文件夹的目录组织如下:

其中detached 目录存放着使用 DETACH 语句从表中分离的分区片段。损坏的片段也会移到该目录,而不是删除。服务器不使用detached目录中的分区片段。
ClickHouse 大约在插入后15分钟定期报告合并操作,合并插入的数据片段。此外,你也可以使用 OPTIMIZE 语句直接执行合并:
OPTIMIZE TABLE t PARTITION 201704;
合并后再次查看分区情况:

新增一个分区,同时旧的2个分区的激活状态变为0。非合并部分将在合并后约10分钟删除:

那些有相同分区表达式值的数据片段才会合并。这意味着 你不应该用太精细的分区方案(超过一千个分区)。否则,会因为文件系统中的文件数量和需要找开的文件描述符过多,导致 SELECT 查询效率不佳。 不同分区之间的文件是永远不会合并的。

3.1 一条数据写入后,数据写入文件后有哪些类型文件?每种类型文件的存储结构是如何的(加上图示)?

文件存储结构
前面提到的这个表:
birth Date, id UInt16, name String, point UInt16
分区文件夹中的内容如下:

MergeTree表中的数据存储在“parts”中。每个部分以主键顺序存储数据(数据按主键元组的字典顺序排序)。所有表的列都存储他们的column.bin文件中。文件由压缩块组成,每个块通常是64 KB到1 MB的未压缩数据,具体取决于平均值大小。这些块由一个接一个地连续放置的列值组成,每列的列值的顺序相同(顺序由主键定义),因此当您按多列进行迭代时,您将获得相应行的值。
上图展示的birth.bin,id.bin,name.bin,point.bin这些文件中存储的就是数据文件。
主键本身是“稀疏的”。它不是针对每一行,而是针对某些范围的数据。单独的primary.idx文件具有每个第N行的主键值,其中N称为index_granularity(通常,N = 8192)。此外,对于每一列,我们都有带有“标记”的column.mrk文件,这些文件是数据文件中每个第N行的偏移量。每个标记都是一对:文件中的偏移量到压缩块的开头,以及解压缩块中的偏移量到数据的开头。通常,压缩块通过标记对齐,并且解压缩块中的偏移量为零。 primary.idx的数据始终驻留在内存中,并缓存column.mrk文件的数据。
上图展示birth.mrk, id.mrk, name.mrk, point.mrk 就是带标记的文件。
当我们要从MergeTree中的某个部分读取内容时,我们会查看primary.idx数据并查找可能包含请求数据的范围,然后查看column.mrk数据并计算从哪里开始读取这些范围的偏移量。由于稀疏性,可能会读取过多的数据。 ClickHouse不适用于高负载的简单点查询,因为必须为每个键读取具有index_granularity行的整个范围,并且必须为每列解压缩整个压缩块。我们使索引稀疏,因为我们必须能够为每个服务器维护数万亿行,而索引没有明显的内存消耗。此外,由于主键是稀疏的,因此它不是唯一的:它无法在INSERT时检查表中是否存在键。您可以在表中使用相同的键创建许多行。

3.3 执行查询的过程是如何用上以上数据?展开过程分析:索引过滤、数据读取、排序、分组

3.4 内存中维护哪些数据结构,如何被使用上的?

3.5 引擎的合并策略是如何的?

如果你新建一个不合规的MergeTree表,会报错:

Received exception from server (version 19.9.3):
Code: 42. DB::Exception: Received from localhost:9000, 127.0.0.1. DB::Exception: Storage MergeTree requires 3 to 4 parameters: 
name of column with date,
[sampling element of primary key],
primary key expression,
index granularity

MergeTree is a family of storage engines.

MergeTrees are different in two ways:
- they may be replicated and non-replicated;
- they may do different actions on merge: nothing; sign collapse; sum; apply aggregete functions.

So we have 14 combinations:
    MergeTree, CollapsingMergeTree, SummingMergeTree, AggregatingMergeTree, ReplacingMergeTree, GraphiteMergeTree, VersionedCollapsingMergeTree
    ReplicatedMergeTree, ReplicatedCollapsingMergeTree, ReplicatedSummingMergeTree, ReplicatedAggregatingMergeTree, ReplicatedReplacingMergeTree, ReplicatedGraphiteMergeTree, ReplicatedVersionedCollapsingMergeTree

In most of cases, you need MergeTree or ReplicatedMergeTree.

For replicated merge trees, you need to supply a path in ZooKeeper and a replica name as the first two parameters.
Path in ZooKeeper is like '/clickhouse/tables/01/' where /clickhouse/tables/ is a common prefix and 01 is a shard name.
Replica name is like 'mtstat01-1' - it may be the hostname or any suitable string identifying replica.
You may use macro substitutions for these parameters. It's like ReplicatedMergeTree('/clickhouse/tables/{shard}/', '{replica}'...
Look at the <macros> section in server configuration file.

Next parameter (which is the first for unreplicated tables and the third for replicated tables) is the name of date column.
Date column must exist in the table and have type Date (not DateTime).
It is used for internal data partitioning and works like some kind of index.

If your source data doesn't have a column of type Date, but has a DateTime column, you may add values for Date column while loading,
    or you may INSERT your source data to a table of type Log and then transform it with INSERT INTO t SELECT toDate(time) AS date, * FROM ...
If your source data doesn't have any date or time, you may just pass any constant for a date column while loading.

Next parameter is optional sampling expression. Sampling expression is used to implement SAMPLE clause in query for approximate query execution.
If you don't need approximate query execution, simply omit this parameter.
Sample expression must be one of the elements of the primary key tuple. For example, if your primary key is (CounterID, EventDate, intHash64(UserID)), your sampling expression might be intHash64(UserID).

Next parameter is the primary key tuple. It's like (CounterID, EventDate, intHash64(UserID)) - a list of column names or functional expressions in round brackets. If your primary key has just one element, you may omit round brackets.

Careful choice of the primary key is extremely important for processing short-time queries.

Next parameter is index (primary key) granularity. Good value is 8192. You have no reasons to use any other value.

For the Collapsing mode, the last parameter is the name of a sign column - a special column that is used to 'collapse' rows with the same primary key while merging.

For the Summing mode, the optional last parameter is a list of columns to sum while merging. This list is passed in round brackets, like (PageViews, Cost).
If this parameter is omitted, the storage will sum all numeric columns except columns participating in the primary key.

For the Replacing mode, the optional last parameter is the name of a 'version' column. While merging, for all rows with the same primary key, only one row is selected: the last row, if the version column was not specified, or the last row with the maximum version value, if specified.

For VersionedCollapsing mode, the last 2 parameters are the name of a sign column and the name of a 'version' column. Version column must be in primary key. While merging, a pair of rows with the same primary key and different sign may collapse.

Examples:

MergeTree(EventDate, (CounterID, EventDate), 8192)

MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)

CollapsingMergeTree(StartDate, intHash32(UserID), (CounterID, StartDate, intHash32(UserID), VisitID), 8192, Sign)

SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, PhraseID, ContextType, RegionID, PageID, IsFlat, TypeID, ResourceNo), 8192)

SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, PhraseID, ContextType, RegionID, PageID, IsFlat, TypeID, ResourceNo), 8192, (Shows, Clicks, Cost, CostCur, ShowsSumPosition, ClicksSumPosition, SessionNum, SessionLen, SessionCost, GoalsNum, SessionDepth))

ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/hits', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)

For further info please read the documentation: https://clickhouse.yandex/
. 

0 rows in set. Elapsed: 0.001 sec. 

参考资料:

  • ClickHouse 使用 - YS.Zou

你可能感兴趣的:(linux,c++,数据库,clickhouse)