ClickHouse 使用物化字段投影 PROJECTION 提升性能

Posted 东海陈光剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ClickHouse 使用物化字段投影 PROJECTION 提升性能相关的知识,希望对你有一定的参考价值。

1.1. 使用物化字段投影 PROJECTION 提升性能

ClickHouse作为ROLAP典型代表之一,纯列式存储单表查询性能几乎没有对手。 但是,MergeTree 主键只支持一种排序规则,对查询性能的提升有局限。而有了Projection,我们就可以针对查询主题,创建其他排序规则,实现预聚合优化(空间换时间)。

Projection 名字起源于Vertica,相当于传统意义上的物化视图。它借鉴 MOLAP 预聚合的思想,在数据写入的时候,根据 projection 定义的表达式,计算写入数据的聚合数据同原始数据一并写入。数据查询的过程中,如果查询SQL通过分析可以通过聚合数据得出,直接查询聚合数据减少计算的开销,解决了由于数据量导致的内存问题。Projeciton 底层存储上属于part目录下数据的扩充,可以理解为查询索引的一种形式。

1.1.1. 新增高基维度投影

新增高基维度投影

我们创建的clickhouse_tutorial.user_tag表的联合索引排序键是order by (WatchID, UserID, EventTime),我们可以再选择UserID字段作为高基维度投影字段,执行如下 SQL:

ALTER TABLE clickhouse_tutorial.user_tag ADD PROJECTION pUserID(SELECT * ORDER BY UserID);

统计字段的基数值

SELECT

    uniqExact(UserID),

    uniqExact(RegionID),

    uniqExact(OS)

FROM clickhouse_tutorial.user_tag

Query id: 35fcdb0b-f530-4f8c-8aa5-6faf71276e31

0 rows in set. Elapsed: 0.002 sec.

Received exception from server (version 21.12.1):

Code: 584. DB::Exception: Received from localhost:9000. DB::Exception: No projection is used when allow_experimental_projection_optimization = 1 and force_optimize_projection = 1. (PROJECTION_NOT_USED)

提示PROJECTION_NOT_USED,这个时候需要把强制使用 projection 优化设置关掉:set force_optimize_projection = 0。

重新执行上面的 SQL,输出:

SELECT

    uniqExact(UserID),

    uniqExact(RegionID),

    uniqExact(OS)

FROM clickhouse_tutorial.user_tag

Query id: c3beca8c-b261-4e5f-bb6d-b78bbc233ef2

┌─uniqExact(UserID)─┬─uniqExact(RegionID)─┬─uniqExact(OS)─┐

│            119689 │                4727 │            68 │

└───────────────────┴─────────────────────┴───────────────┘

1 rows in set. Elapsed: 3.022 sec. Processed 1.70 billion rows, 22.05 GB (561.17 million rows/s., 7.30 GB/s.)

这样我们就有了低基投影pOS、中基投影pRegionID、高基投影pUserID。

1.1.2. 构建测试数据

为了方便测试性能,我们构造 10 亿行级别的测试数据。多次执行如下INSERT 语句:

INSERT INTO clickhouse_tutorial.user_tag

(UserID,

 WatchID,

 EventTime,

 Sex,

 Age,

 OS,

 RegionID,

 RequestNum,

 EventDate)

select UserID,

       WatchID,

       EventTime,

       Sex,

       Age,

       OS,

       RegionID,

       RequestNum,

       EventDate

from clickhouse_tutorial.user_tag;

数据行:

SELECT count()

FROM clickhouse_tutorial.user_tag

Query id: 2bb042e1-9559-4ee4-979d-cc7466a23715

┌────count()─┐

│ 1696002516 │

└────────────┘

1 rows in set. Elapsed: 0.001 sec.

1.1.3. 关闭 Projection 优化开关测试

关闭投影优化:

SET allow_experimental_projection_optimization=0;

执行查询

select * from clickhouse_tutorial.user_tag where UserID = 1389883949241360436;

59888 rows in set. Elapsed: 0.669 sec. Processed 1.70 billion rows, 13.67 GB (2.54 billion rows/s., 20.44 GB/s.)

查看执行计划:

EXPLAIN actions = 1

SELECT *

FROM clickhouse_tutorial.user_tag

WHERE UserID = 1389883949241360436

Query id: b27ea9ba-4f0b-4db3-b315-f1e827529445

┌─explain───────────────────────────────────────────────────────────────────┐

│ Expression ((Projection + Before ORDER BY))                               │

│ Actions: INPUT :: 0 -> UserID UInt64 : 0                                  │

│          INPUT :: 1 -> WatchID UInt64 : 1                                 │

│          INPUT :: 2 -> EventTime DateTime : 2                             │

│          INPUT :: 3 -> Sex UInt8 : 3                                      │

│          INPUT :: 4 -> Age UInt8 : 4                                      │

│          INPUT :: 5 -> OS UInt8 : 5                                       │

│          INPUT :: 6 -> RegionID UInt32 : 6                                │

│          INPUT :: 7 -> RequestNum UInt32 : 7                              │

│          INPUT :: 8 -> EventDate Date : 8                                 │

│ Positions: 0 1 2 3 4 5 6 7 8                                              │

│   SettingQuotaAndLimits (Set limits and quota after reading from storage) │

│     ReadFromMergeTree                                                     │

│     ReadType: Default                                                     │

│     Parts: 47                                                             │

│     Granules: 207045                                                      │

└───────────────────────────────────────────────────────────────────────────┘

16 rows in set. Elapsed: 0.028 sec.

1.1.4. 开启 Projection 优化开关测试

开启投影优化:

SET allow_experimental_projection_optimization=1;

执行查询:

select * from clickhouse_tutorial.user_tag where UserID = 1389883949241360436;

59888 rows in set. Elapsed: 0.119 sec. Processed 442.37 thousand rows, 12.23 MB (3.72 million rows/s., 102.86 MB/s.)

查看执行计划:

EXPLAIN actions = 1

SELECT *

FROM clickhouse_tutorial.user_tag

WHERE UserID = 1389883949241360436

Query id: f2db2a78-d275-4d55-ae10-5fe1cdf43f8a

┌─explain───────────────────────────────────────────────────────────────────┐

│ Expression ((Projection + Before ORDER BY))                               │

│ Actions: INPUT :: 0 -> UserID UInt64 : 0                                  │

│          INPUT :: 1 -> WatchID UInt64 : 1                                 │

│          INPUT :: 2 -> EventTime DateTime : 2                             │

│          INPUT :: 3 -> Sex UInt8 : 3                                      │

│          INPUT :: 4 -> Age UInt8 : 4                                      │

│          INPUT :: 5 -> OS UInt8 : 5                                       │

│          INPUT :: 6 -> RegionID UInt32 : 6                                │

│          INPUT :: 7 -> RequestNum UInt32 : 7                              │

│          INPUT :: 8 -> EventDate Date : 8                                 │

│ Positions: 0 1 2 3 4 5 6 7 8                                              │

│   SettingQuotaAndLimits (Set limits and quota after reading from storage) │

│     ReadFromStorage (MergeTree(with Normal projection pUserID))           │

└───────────────────────────────────────────────────────────────────────────┘

13 rows in set. Elapsed: 0.039 sec.

1.1.5. 性能数据

pUserID的性能对比

为了直观方便地看到性能差距,特意整理pUserID的性能对比表如下。

SELECT *

FROM clickhouse_tutorial.user_tag

WHERE UserID = 1389883949241360436

pUserID(高基数)

对比项

关闭 Projection 优化

开启 Projection 优化

倍数

扫描数据行

1700000000

442370

3843

处理数据大小(MB)

13998.08

12.23

1145

响应时间(秒)

0.669

0.119

6

pRegionID的性能对比

pRegionID的性能对比表如下。

SELECT

    RegionID,

    count(1)

FROM clickhouse_tutorial.user_tag

GROUP BY RegionID

开启Projection优化:

4727 rows in set. Elapsed: 0.020 sec. Processed 98.99 thousand rows, 2.42 MB (4.97 million rows/s., 121.37 MB/s.)

不开启Projection优化:

4727 rows in set. Elapsed: 1.005 sec. Processed 1.70 billion rows, 6.78 GB (1.69 billion rows/s., 6.75 GB/s.)

pRegionID(中基数)

对比项

关闭 Projection 优化

开启 Projection 优化

倍数

扫描数据行

1700000000

98990

17173

处理数据大小(MB)

6942.72

2.42

2869

响应时间(秒)

1.005

0.02

50

pOS的性能对比

SELECT

    OS,

    count(1)

FROM clickhouse_tutorial.user_tag

GROUP BY OS

开启Projection优化:

68 rows in set. Elapsed: 0.008 sec. Processed 2.43 thousand rows, 210.31 KB (309.74 thousand rows/s., 26.77 MB/s.)

不开启Projection优化:

68 rows in set. Elapsed: 0.378 sec. Processed 1.70 billion rows, 1.70 GB (4.49 billion rows/s., 4.49 GB/s.)

pOS(低基数)

对比项

关闭 Projection 优化

开启 Projection 优化

倍数

扫描数据行

1700000000

2430

699588

处理数据大小(MB)

1740.8

0.20538

8476

响应时间(秒)

0.378

0.005

76 

1.1.6. 维度字段基数对 Projection 性能的影响

可以看到由于UserID、RegionID、OS这 3 个字段的基数值的不同,对 Projection 查询性能带来的影响。如下表所示。

基数值

68

4727

119689

扫描数据行倍数

699588

17173

3843

处理数据大小倍数

8476

2869

1145

响应时间倍数

76

50

6

可以看出,基数越大,Projection 性能提升越小;基数越小,Projection 性能提升越明显。从性能测试的数据上看,Projeciton对查询性能有着百倍级别的提升。

但是,事物总是两面性的,这个世界上没有完美的事物。 Projection在高基维的场景下,性能表现一般,而且Projection 还付出了额外的存储计算开销,这个时候,就需要进行权衡(trade-off)了。所以,建议Projection构建的时候不要使用高基维度字段。

以上是关于ClickHouse 使用物化字段投影 PROJECTION 提升性能的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 Clickhouse 的 AggregatingMergeTree 物化视图中的嵌套字段?

clickhouse-物化视图

大数据ClickHouse进阶(二十):MaterializeMySQL物化引擎深入了解

大数据clickhouse clickhouse 物化视图使用详解

带有 TO 子句的 Clickhouse 物化视图不支持按

ClickHouse Kafka引擎不消费问题排查