客端日志的收集、存储和分析(二)前言一、ClickHou

前言

上一节我们介绍了如何接收客户端日志及数据存储方面ClickHouse的写入和更新相关的知识。在写入方面,我们提到了批量写入,批量写入是有利于ClickHouse处理新增数据的。但是,如果我们想保证拥有较快的查询速度,该以怎样的数据结构去组织数据写入呢?这一结我们来介绍一下如何为高效的查询组织数据写入ClickHouse。

一、ClickHouse数据分区

数据分区就是按照一个或多个字段,将数据放到不同的目录中,在很多的OLAP数据库中,都存在数据分区的设计,但是其它很多OLAP数据库都没有分区内小文件自动合并的功能。ClickHouse会对分区内的小文件定时进行合并,以便提高查询性能。分区最常用的就是按天分区。在ClickHouse的MergeTree系列引擎表中,是在创建表时通过partition by指定分区字段的,例如:

CREATE TABLE test (
    event String,
    user_id Int64,
    event_time String,
    dt String
  ) ENGINE = MergeTree()
  partition by dt
  order by event
复制代码

其中dt就是分区字段。有了分区字段之后,我们查询数据时,就可以使用where条件通过分区字段进行过滤,减小数据扫描的范围,提高查询效率。例如,我们只想查询2021年09月1日到9月30日的数据量,可以执行如下sql获取:

select count(1) from test where dt between '20210901' and '20210930'
复制代码

下图是分区的原理图

image.png
如20210201是就一个大的分区,就是我们定义的dt字段,在大分区中,还有很多小文件,每个文件都可能是一次insert的数据生成的文件,如20210201_1_1_0、20210201_2_2_0等。
分区虽然能减小查询时数据的扫描范围,但是切记分区不能分的太细,比如说按照分钟级别进行分区,因为MergeTree表引擎的一个分区对应一批文件,分的太细,可能会造成文件数过多,文件数过多,在查询时,就会扫描大量的小文件,会产生大量的文件寻址,这无疑会增加查询时间。再有,ClickHouse会定时合并每个分区中的小文件,分区数过多,可能会导致ClickHouse合并小文件的速度赶不上数据写入生成的小文件的速度。所以说,分区粒度不能太细。

二、ClickHouse MergeTree引擎一级索引

1. 一级索引使用示例

ClickHouse提供了一级索引,查询时使用一级索引,能大幅度提高查询性能,一级索引在创建表时通过order by关键字声明,也可通过primary key声明。如果不声明primary key关键字,那么默认情况下order by关键字声明的字段就是一级索引。示例如下:

CREATE TABLE heaven_eye_analyse.event_production (
    event String,
    user_id Int64,
    $lib String,
    dt String
  ) ENGINE = MergeTree()
  partition by dt
  order by event
复制代码

其中event就是一级索引。

下面我们来对比下使用一级索引和普通字段的查询时效。比较的总数据量是50亿。
先来看下使用普通字段的查询时效

image.png

通过普通字段$lib查询30天数据总量,耗时15s。再来看下使用一级索引的查询耗时

image.png
可以看到,耗时只有430ms,和使用普通字段查询相比,速度完全碾压。

2.一级索引原理

接着我们介绍下ClickHouse一级索引的原理。下面是一级索引的原理图

primary.idx、标记文件、数据文件的协同.png

MergeTree表中的每一列,都有两个文件,一个是.bin文件,另外一个是.mrk文件。一级索引除此之外还有一个primary.idx文件。

.bin文件

.bin文件用于存储数据。
由于同一列的数据大概率具有相似性,所以数据一般压缩后存储,可节省磁盘空间,但是ClickHosue的压缩不是针对整个.bin文件进行压缩,而是将.bin文件中的数据划分为多个部分,每个部分压缩在一起,称为一个压缩块,这样做的好处如下:比如说整个.bin文件有1亿条数据,如果是对整个文件进行压缩,那么假设只用读取其中一万条数据,那么需要对整个文件进行解压,这样就浪费了很多时间。相对的,如果只解压这一万条数据所在的存储块,那么效率会提升很多。
另外,每个压缩块中有一个元数据,元数据占9个字节,1个字节表示压缩算法类型,4个字节表示表示压缩前的数据大小,4个字节表示压缩后的数据大小。

.mrk文件

.mrk文件用于描述这一列对应的.bin文件中每个数据块(默认每8192条数据一个间隔)的起始位置及数据在数据块内的位置(解压后)

primary.idx文件

一级索引对应一个primary.idx文件。ClickHouse MergeTree表每隔一定量的数据(默认8192条)就生成一个索引标记,对于一亿条数据,使用12208行索引标记就能索引,由此来看,索引标记占用的空间会非常小,所以索引文件就可以常驻内存,加快存取速度。

primary.idx、标记文件、数据文件的协同

由上面的一级索引原理图可以看出,primary.idx中的每行索引标记和索引对应列的.mrk文件中的每个标记是一对一的关系,标记和压缩块根据一级索引对应的字段压缩前的大小,分为一对一、一对多和多对一。如果想根据一级索引字段查询某个范围内的数据,可以将查询条件和一级索引做交集,找到数据所在的区间对应的标记。举个例子,比如说想查询event between 5000 and 6000之间的数据,根据一级索引,发现索引4553和查询条件有交集,然后根据索引4553找到标记1,标记1指向压缩块1,此时先解压压缩块1,由于标记1的P2的位置是0,那么就从解压后的第一个元素开始,找到符合条件(event between 5000 和 6000)的数据。

总结

本节我们主要介绍ClickHouse提高查询效率的两个方式,即通过ClickHouse MergeTree表引擎的分区和一级索引存。两种方式结合使用,可以让我们在查询时大幅度提高性能。
下面划重点:
在使用分区时,切记分区不能分的太细。
在使用一级索引时,应该选择查询时能被经常命中的条件作为一级索引。
在知道如何高效的组织好数据存储后,我们下一节将介绍如何基于ClickHouse做业务上的数据分析,例如典型的漏斗模型分析、业务留存分析等。