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进阶(二十):MaterializeMySQL物化引擎深入了解