译文 ClickHouse Materialized Views Illuminated, Part 1

ClickHouse Materialized Views Illuminated, Part 1

header

Altinity blog的读者们知道我们喜欢ClickHouse的物化视图. 物化视图可以实现聚合计算, 从Kafka读取数据, 实现最后点查询(last point queries)以及重组表主键索引和排序顺序. 除了这些功能之外, 物化视图可以在大量节点上很好地扩缩, 并可以处理大型数据集. 它们是ClickHouse的独特功能之一.

在计算领域, 强大的功能至少意味着一点点复杂性. 这篇由两部分组成的文章通过解释物化视图的工作原理来填补空白, 从而使初学者也可以有效地使用它们. 我们将通过几个详细的示例, 您可以根据自己的使用进行调整. 在此过程中, 我们将探索用于创建视图的语法的确切含义, 并让您深入了解ClickHouse在做什么. 示例是完全自包含的, 因此您可以将它们复制/粘贴到clickhouse-client中并自己运行它们.

How Materialized Views Work: Computing Sums

ClickHouse物化视图自动在表之间转换数据. 它们类似于触发器, 对插入的行运行查询并将结果存入第二个表. 让我们看一个基本的例子. 假设我们有一个记录用户下载的表, 如下所示.

1
2
3
4
5
6
7
CREATE TABLE download (
when DateTime,
userid UInt32,
bytes Float32
) ENGINE=MergeTree
PARTITION BY toYYYYMM(when)
ORDER BY (userid, when)

我们希望跟踪每个用户的每日下载. 让我们看看如何用一个查询来做到这一点. 首先, 我们需要为单个用户向表中添加一些数据.

1
2
3
4
5
6
7
INSERT INTO download
SELECT
now() + number * 60 as when,
25,
rand() % 100000000
FROM system.numbers
LIMIT 5000

接下来, 让我们运行一个查询来显示该用户的每日下载. 当添加新用户时, 这也将正常工作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT 
toStartOfDay(when) AS day,
userid,
count() AS downloads,
sum(bytes) AS bytes
FROM download
GROUP BY
userid,
day
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002573636722522631
2020-08-19 00:00:0025144073305428060
2020-08-20 00:00:0025144073183910537
2020-08-21 00:00:0025138470059352697
└─────────────────────┴────────┴───────────┴─────────────┘

我们可以通过每次运行查询以交互方式为应用程序计算这些每日总数, 但是对于大型表, 提前计算它们将更快, 更节省资源. 因此, 最好将结果放在单独的表格中, 该表格可以连续跟踪每天每个用户的下载总数. 我们可以使用以下物化视图来做到这一点.

1
2
3
4
5
6
7
8
9
10
11
CREATE MATERIALIZED VIEW download_daily_mv
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(day) ORDER BY (userid, day)
POPULATE
AS SELECT
toStartOfDay(when) AS day,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download
GROUP BY userid, day

这里有三件重要的事情需要注意. 首先, 物化视图定义允许类似于CREATE TABLE的语法, 这是有意义的, 因为这个命令将实际创建一个隐藏的目标表(.inner表)来保存视图数据. 我们使用的ClickHouse引擎旨在使计算和计数变得简单:SummingMergeTree. 它是用于计算聚合的物化视图的推荐引擎.

其次, 视图定义包含关键字POPULATE. 这告诉ClickHouse将download表中的现有数据插入物化视图. 我们稍后会更多地讨论automatic population.

需要注意, 在POPULATE填充历史数据的期间, 新进入的这部分数据会被忽略掉, 所以如果对准确性要求非常高, 应慎用

第三, 视图定义包含一个SELECT语句, 该语句定义在加载视图时如何转换数据. 这个查询在表中的新数据上运行, 以计算每天每个用户id的下载数量和总字节数. 它本质上与我们以交互方式运行的查询相同, 只是在本例中, 结果将放在隐藏的目标表(.inner表)中. 我们可以跳过排序, 因为视图定义已经确保了排序顺序.

现在, 我们从物化视图中查询数据

1
2
3
4
5
6
7
8
9
10
SELECT * FROM download_daily_mv
ORDER BY day, userid
LIMIT 5

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002573636722522631
2020-08-19 00:00:0025144073305428060
2020-08-20 00:00:0025144073183910537
2020-08-21 00:00:0025138470059352697
└─────────────────────┴────────┴───────────┴─────────────┘

这为我们提供了与先前查询完全相同的答案. 原因是上面介绍的POPULATE关键字. 它确保源表中的现有数据自动加载到视图中. 不过, 有一个重要警告:如果在填充视图时插入了新数据, ClickHouse将会丢失它们. 在本系列的第二部分中, 我们将展示如何手动插入数据并避免数据遗漏的问题.

现在, 尝试使用其他用户向表中添加更多数据.

1
2
3
4
5
6
7
INSERT INTO download
SELECT
now() + number * 60 as when,
22,
rand() % 100000000
FROM system.numbers
LIMIT 5000

如果您从实例化视图中进行选择, 您将看到它现在具有用户ID 22和25的总数. 请注意, 一旦INSERT完成, 将立即填充新数据. 这是ClickHouse实例化视图的重要功能, 这使其对于实时分析非常有用.

下面是查询和新结果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT *
FROM download_daily_mv
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002241621063519801
2020-08-19 00:00:0022144071523929305
2020-08-20 00:00:0022144070435459582
2020-08-21 00:00:0022144070725673036
2020-08-22 00:00:002226413826466067
└─────────────────────┴────────┴───────────┴─────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:0025146775441118166
2020-08-19 00:00:00252880144811386193
2020-08-20 00:00:00252880145138479865
2020-08-21 00:00:00252773138488485955
└─────────────────────┴────────┴───────────┴──────────────┘

作为练习, 您可以对源表运行原始查询, 以确认它与物化视图中的总数相匹配.

作为最后一个示例, 让我们使用物化视图按月汇总. 在本例中, 我们将物化视图视为一个普通表, 按月分组, 如下所示. 我们添加了WITH TOTALS子句, 它打印一个方便的聚合的总和.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT 
toStartOfMonth(day) AS month,
userid,
sum(downloads),
sum(bytes)
FROM download_daily_mv
GROUP BY
userid,
month
WITH TOTALS
ORDER BY
userid ASC,
month ASC

┌──────month─┬─userid─┬─sum(downloads)─┬───sum(bytes)─┐
2020-08-01225000247575047791
2020-08-012510000503879470179
└────────────┴────────┴────────────────┴──────────────┘

Extremes:
┌──────month─┬─userid─┬─sum(downloads)─┬───sum(bytes)─┐
0000-00-00015000751454517970
└────────────┴────────┴────────────────┴──────────────┘

从前面的示例中, 我们可以清楚地看到实例化视图如何正确地汇总源数据中的数据(how the materialized view correctly summarizes data from the source data). 如上例所示, 我们甚至可以“summarize the summaries”. 那么幕后到底发生了什么? 下图说明了数据的逻辑流.

logical

如图所示, 原表上INSERT的值被转换并应用于隐藏的目标表(.inner表). 要填充视图(To populate the view), 您要做的就是在源表中插入数据.

您可以从隐藏的目标表(.inner表)和物化视图中进行查询. 实际上ClickHouse就是将查询路由到创建物化视图时自动创建的internel表中的.

图中还有另外一件重要的事情需要注意. 物化视图创建一个具有特殊名称私有表来保存数据. 如果您通过输入DROP TABLE download_daily_mv删除物化视图, 则私有表也会被删除. 如果需要更改视图, 则需要将其删除并使用新数据重新创建

1
2
3
4
5
6
7
8
>SHOW TABLES

>┌─name─────────────────────┐
>│ .inner.download_daily_mv │
>│ download │
>│ download_daily_mv │
>└──────────────────────────┘

.inner.download_daily_mv就是 internel表或叫私有表

Wrap-up - 总结

我们刚刚审阅的示例使用SummingMergeTree创建一个视图以累加每日用户下载量. 我们从物化视图对SELECT使用了标准SQL语法. 这是SummingMergeTree引擎的特殊功能, 仅适用于总和和计数. 对于其他类型的聚合, 我们需要使用其他方法.

另外, 我们的示例使用POPULATE关键字将现有表数据发布到视图创建的私有目标表(.inner表)中. 如果在填充视图时到达新的INSERT行, ClickHouse将错过它们. 当您是唯一使用数据集的人时, 此限制很容易解决, 但对于不断加载数据的生产系统来说是个问题. 此外, 删除视图后, 专用表也会消失. 这使得很难更改视图以适应源表中的架构更改.

在下一篇文章中, 我们将展示如何创建物化视图来计算其他类型的聚合, 比如平均值或最大值/最小值. 我们还将展示如何显式定义目标表(.inner表), 并使用我们自己的SQL语句手动将数据加载到其中. 我们还将简要介绍模式迁移(schema migration). 同时, 我们希望您喜欢这一简要介绍, 并发现示例有用.

实测及问题

删除基表, 物化视图仍然可以查询

这是符合预期的, 因为物化视图是存储了数据的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
bj2-all-clickhouse-test-02 :) drop table download;

DROP TABLE download

Ok.

0 rows in set. Elapsed: 0.016 sec.

bj2-all-clickhouse-test-02 :) show tables;

SHOW TABLES

┌─name─────────────────────┐
│ .inner.download_daily_mv │
│ download_daily_mv │
│ sbtest │
│ sbtest_local │
└──────────────────────────┘

4 rows in set. Elapsed: 0.001 sec.

bj2-all-clickhouse-test-02 :) select * from download_daily_mv;

SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:0025146775441118166
2020-08-19 00:00:00252880144811386193
2020-08-20 00:00:00252880145138479865
2020-08-21 00:00:00252773138488485955
└─────────────────────┴────────┴───────────┴──────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002241621063519801
2020-08-19 00:00:0022144071523929305
2020-08-20 00:00:0022144070435459582
2020-08-21 00:00:0022144070725673036
2020-08-22 00:00:002226413826466067
└─────────────────────┴────────┴───────────┴─────────────┘

9 rows in set. Elapsed: 0.008 sec.

数据在分区合并时聚合

重新创建download表和物化视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SELECT 
toStartOfDay(when) AS day,
userid,
count() AS downloads,
sum(bytes) AS bytes
FROM download
GROUP BY
userid,
day
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002538719855551536
2020-08-19 00:00:0025144073316885071
2020-08-20 00:00:0025144072322018002
2020-08-21 00:00:0025144071675677053
2020-08-22 00:00:002529314125612942
└─────────────────────┴────────┴───────────┴─────────────┘



SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002538719855551536
2020-08-19 00:00:0025144073316885071
2020-08-20 00:00:0025144072322018002
2020-08-21 00:00:0025144071675677053
2020-08-22 00:00:002529314125612942
└─────────────────────┴────────┴───────────┴─────────────┘

再次向基表中插入数据, 然后查看物化视图中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
INSERT INTO download SELECT 
now() + (number * 60) AS when,
25,
rand() % 100000000
FROM system.numbers
LIMIT 5000



SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002538719855551536
2020-08-19 00:00:0025144073316885071
2020-08-20 00:00:0025144072322018002
2020-08-21 00:00:0025144071675677053
2020-08-22 00:00:002529314125612942
└─────────────────────┴────────┴───────────┴─────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:002538518771807859
2020-08-19 00:00:0025144073675739078
2020-08-20 00:00:0025144070555293899
2020-08-21 00:00:0025144070773685728
2020-08-22 00:00:002529514750186600
└─────────────────────┴────────┴───────────┴─────────────┘

可以看到物化视图中的数据并没有”全部聚合完整”

查看物化视图private表分区情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
bj2-all-clickhouse-test-02 :) select * from system.parts where table='.inner.download_daily_mv'\G

SELECT *
FROM system.parts
WHERE table = '.inner.download_daily_mv'

Row 1:
──────
partition: 202008
name: 202008_1_1_0
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:33:51
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 1
level: 0
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_1_0/
hash_of_all_files: f4b55a88dac393d25ffe1c703cca4f6d
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: e18b006f608580db132ed90adb46902f

Row 2:
──────
partition: 202008
name: 202008_2_2_0
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:35:13
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 2
max_block_number: 2
level: 0
data_version: 2
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_2_2_0/
hash_of_all_files: 049d090ea65c24be8544ab86846ea9fa
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: 9ffea93e6b9bc375c7f04374b4a4a6a9

2 rows in set. Elapsed: 0.002 sec.

可以看到, 有两个active分区202008_1_1_0, 202008_2_2_0

我们手动OPTIMIZE尝试合并分区

1
2
3
4
5
6
7
bj2-all-clickhouse-test-02 :) optimize table download_daily_mv;

OPTIMIZE TABLE download_daily_mv

Ok.

0 rows in set. Elapsed: 0.004 sec.

再次查询物化视图数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bj2-all-clickhouse-test-02 :) select * from download_daily_mv;

SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:002577238627359395
2020-08-19 00:00:00252880146992624149
2020-08-20 00:00:00252880142877311901
2020-08-21 00:00:00252880142449362781
2020-08-22 00:00:002558828875799542
└─────────────────────┴────────┴───────────┴──────────────┘

5 rows in set. Elapsed: 0.001 sec.

查看private表分区情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
bj2-all-clickhouse-test-02 :) select * from system.parts where table='.inner.download_daily_mv'\G

SELECT *
FROM system.parts
WHERE table = '.inner.download_daily_mv'

Row 1:
──────
partition: 202008
name: 202008_1_1_0
part_type: Wide
active: 0
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:33:51
remove_time: 2020-08-18 17:38:24
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 1
level: 0
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_1_0/
hash_of_all_files: f4b55a88dac393d25ffe1c703cca4f6d
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: e18b006f608580db132ed90adb46902f

Row 2:
──────
partition: 202008
name: 202008_1_2_1
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:38:24
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 2
level: 1
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_2_1/
hash_of_all_files: fdc534a9b9aa0904cde863ee1deff532
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: df972eeaad3304d9a16bfa8cb46861ce

Row 3:
──────
partition: 202008
name: 202008_2_2_0
part_type: Wide
active: 0
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:35:13
remove_time: 2020-08-18 17:38:24
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 2
max_block_number: 2
level: 0
data_version: 2
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_2_2_0/
hash_of_all_files: 049d090ea65c24be8544ab86846ea9fa
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: 9ffea93e6b9bc375c7f04374b4a4a6a9

3 rows in set. Elapsed: 0.002 sec.

可以看到只有一个active分区了202008_1_2_1

所以对于本例中的物化视图, 在查询是仍然应该使用聚合函数聚合数据

1
2
3
4
5
6
7
8
9
SELECT 
day,
userid,
sum(downloads),
sum(bytes)
FROM download_no_populate_daily_mv
GROUP BY
day,
userid

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2022 Fan() All Rights Reserved.

访客数 : | 访问量 :