1. 程式人生 > 其它 >clickhouse(十一)物化檢視使用二

clickhouse(十一)物化檢視使用二

在上一篇關於物化檢視的文章中, 我們介紹了一種構造ClickHouse物化檢視的方法, 該檢視使用SummingMergeTree引擎計算總和和計數. SummingMergeTree可以為這兩種型別的聚合使用普通的SQL語法. 我們還讓物化檢視定義自動為資料建立基礎表(.inner表). 這兩種技術都很快速, 但對生產系統有限制(都不太適用於生產環境).

在本篇文章中, 我們將展示如何在現有的表上建立一個具有一系列聚合型別的物化檢視. 當您需要計算的不僅僅是簡單的總和時, 此方法非常適合. 對於表中有大量正在插入的資料(針對Part 1)中的POPULATE, 使用POPULATE會填充歷史資料, 但這期間向原表中新插入資料會被忽略掉而不會寫入物化檢視中)或必須處理表結構變更的情況, 這也非常方便.

使用State函式和To Tables建立更靈活的物化檢視

線上面的例子中, 我們將測量裝置的讀數. 讓我們從表定義開始.

1
2
3
4
5
6
7
CREATE TABLE counter (
when DateTime DEFAULT now(),
device UInt32,
value Float32
) ENGINE=MergeTree
PARTITION BY toYYYYMM(when)
ORDER BY (device, when)

接下來, 我們新增足夠的資料, 以使查詢速度變得足夠慢: 10個裝置的10億行合成數據. 注意: 如果您要嘗試這些操作, 只需輸入100萬行即可. 無論資料量如何, 示例都可以工作.

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
INSERT INTO counter SELECT 
toDateTime('2015-01-01 00:00:00') + toInt64(number / 10) AS when,
(number % 10) + 1 AS device,
((device * 3) + (number / 10000)) + ((rand() % 53) * 0.1) AS value
FROM system.numbers
LIMIT 1000000000

↓ Progress: 1.00 billion rows, 8.00 GB (5.13 million rows/s., 41.07 MB/s.) Ok.

0 rows in set. Elapsed: 194.814 sec. Processed 1.00 billion rows, 8.00 GB (5.13 million rows/s., 41.07 MB/s.)


SELECT count(*)
FROM counter

┌────count()─┐
1000000000 │
└────────────┘

SELECT *
FROM counter
LIMIT 1

┌────────────────when─┬─device─┬─value─┐
2015-01-01 00:00:00 │ 1 │ 3.6 │
└─────────────────────┴────────┴───────┘


[root@bj2-all-clickhouse-test-02 11:21:30 /data/clickhouse/node2/data/duyalan]
#du -sh counter/
13G counter/

[root@bj2-all-clickhouse-test-02 11:21:35 /data/clickhouse/node2/data/duyalan]
#du -sh counter/
12G counter/

[root@bj2-all-clickhouse-test-02 11:26:04 /data/clickhouse/node2/data/duyalan]
#du -sh counter/
6.5G counter/
資料慢慢被壓實

現在, 讓我們看一下我們希望定期執行的示例查詢. 它彙總了整個取樣期間所有裝置的所有資料. 在這種情況下, 這意味著表中3.25年的資料, 都是在2019年之前.

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
SELECT 
device,
count(*) AS count,
max(value) AS max,
min(value) AS min,
avg(value) AS avg
FROM counter
GROUP BY device
ORDER BY device ASC

┌─device─┬─────count─┬────────max─┬─────min─┬────────────────avg─┐
1 │ 100000000 │ 100008.15 │ 3.077 │ 50005.599374785554 │
2 │ 100000000 │ 100011.164 │ 6.0761 │ 50008.59962170133 │
3 │ 100000000 │ 100014.1 │ 9.0022 │ 50011.599634214646 │
4 │ 100000000 │ 100017.17 │ 12.0063 │ 50014.59989124005 │
5 │ 100000000 │ 100020.164 │ 15.0384 │ 50017.59997032414 │
6 │ 100000000 │ 100023.19 │ 18.1045 │ 50020.60019940771 │
7 │ 100000000 │ 100026.055 │ 21.0566 │ 50023.60046194672 │
8 │ 100000000 │ 100029.14 │ 24.0477 │ 50026.60002471252 │
9 │ 100000000 │ 100032.17 │ 27.0218 │ 50029.60008679837 │
10 │ 100000000 │ 100035.02 │ 30.0629 │ 50032.60051765903 │
└────────┴───────────┴────────────┴─────────┴────────────────────┘

10 rows in set. Elapsed: 12.036 sec. Processed 1.00 billion rows, 8.00 GB (83.08 million rows/s., 664.67 MB/s.)

bj2-all-clickhouse-test-02 :) select min(when),max(when) from counter;

SELECT
min(when),
max(when)
FROM counter

┌───────────min(when)─┬───────────max(when)─┐
2015-01-01 00:00:00 │ 2018-03-03 09:46:39 │
└─────────────────────┴─────────────────────┘

1 rows in set. Elapsed: 9.941 sec. Processed 1.00 billion rows, 4.00 GB (100.59 million rows/s., 402.36 MB/s.)

前面的查詢很慢, 因為它必須讀取表中的所有資料才能獲得答案. 我們想要設計一個物化檢視, 該檢視讀取的資料要少得多. 事實證明, 如果我們定義了一個每天彙總資料的檢視, 則ClickHouse將正確地在整個時間間隔內彙總每天的資料.

與前面的簡單示例(Part 1)不同, 我們將自己定義目標(.inner表)表. 這樣做的好處是, 該表現在可見, 這使得載入資料以及進行模式遷移(表結構變更)更加容易. 下面是目標表的定義.

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE counter_daily (
day DateTime,
device UInt32,
count UInt64,
max_value_state AggregateFunction(max, Float32),
min_value_state AggregateFunction(min, Float32),
avg_value_state AggregateFunction(avg, Float32)
)
ENGINE = SummingMergeTree()
PARTITION BY tuple()
ORDER BY (device, day)

該表定義引入了一種新的資料型別, 稱為AggregateFunction, 該資料型別儲存部分聚合的資料(which holds partially aggregated data). 這個資料型別用於sum和count以外的聚合需求. 接下來, 我們建立相應的物化檢視. 它從counter(源表)中選擇資料, 並使用CREATE語句中的特殊TO語法將資料傳送到counter_daily(目標表). 該表有聚合函式, SELECT語句有與之相匹配的函式, 如’ maxState ‘. 我們將在詳細討論聚合函式時討論它們之間的關係.

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE MATERIALIZED VIEW counter_daily_mv
TO counter_daily
AS SELECT
toStartOfDay(when) as day,
device,
count(*) as count,
maxState(value) AS max_value_state,
minState(value) AS min_value_state,
avgState(value) AS avg_value_state
FROM counter
WHERE when >= toDate('2019-01-01 00:00:00')
GROUP BY device, day
ORDER BY device, day

TO關鍵字使我們可以指向目標表(儲存物化檢視資料的表, 在本例中即是counter_daily表), 但有一個缺點. ClickHouse不允許在TO中使用POPULATE關鍵字. 因此, 物化檢視建立後沒有任何資料. 我們將手動載入資料. 但是, 我們還將使用一個不錯的技巧, 使我們可以避免在同時進行活動資料載入的情況下出現問題.

注意, 檢視定義有一個WHERE子句. 這意味著2019年之前的任何資料都應該被忽略. 我們現在有了一種不丟失資料的方法來處理資料載入. 該檢視將處理2019年到達的新資料. 同時, 我們可以通過插入載入2018年及之前的舊資料.

讓我們通過將新資料載入到counter表中來演示它是如何工作的. 新資料將於2019年開始, 並將自動載入到檢視中.

1
2
3
4
5
6
INSERT INTO counter
SELECT
toDateTime('2019-01-01 00:00:00') + toInt64(number/10) AS when,
(number % 10) + 1 AS device,
(device * 3) + (number / 10000) + (rand() % 53) * 0.1 AS value
FROM system.numbers LIMIT 100000000

現在, 使用以下INSERT手動載入舊資料. 它會載入2018年及之前的所有資料.

1
2
3
4
5
6
7
8
9
10
11
12
INSERT INTO counter_daily
SELECT
toStartOfDay(when) as day,
device,
count(*) AS count,
maxState(value) AS max_value_state,
minState(value) AS min_value_state,
avgState(value) AS avg_value_state
FROM counter
WHERE when < toDateTime('2019-01-01 00:00:00')
GROUP BY device, day
ORDER BY device, day

我們終於可以從檢視中查詢資料了. 與目標表和物化化檢視一樣, ClickHouse使用專用語法從檢視中進行選擇.

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
物化檢視目標表
SELECT
device,
sum(count) AS count,
maxMerge(max_value_state) AS max,
minMerge(min_value_state) AS min,
avgMerge(avg_value_state) AS avg
FROM counter_daily
GROUP BY device
ORDER BY device ASC

┌─device─┬─────count─┬────────max─┬─────min─┬────────────────avg─┐
1 │ 110000000 │ 100008.15 │ 3.051 │ 45914.69035234097 │
2 │ 110000000 │ 100011.164 │ 6.0291 │ 45917.69056040798 │
3 │ 110000000 │ 100014.1 │ 9.0022 │ 45920.690478928045 │
4 │ 110000000 │ 100017.17 │ 12.0063 │ 45923.69086044358 │
5 │ 110000000 │ 100020.164 │ 15.0114 │ 45926.69083122718 │
6 │ 110000000 │ 100023.19 │ 18.0475 │ 45929.691088042426 │
7 │ 110000000 │ 100026.055 │ 21.0566 │ 45932.69135215635 │
8 │ 110000000 │ 100029.14 │ 24.0107 │ 45935.690912335944 │
9 │ 110000000 │ 100032.17 │ 27.0218 │ 45938.69098338585 │
10 │ 110000000 │ 100035.02 │ 30.0429 │ 45941.69140548378 │
└────────┴───────────┴────────────┴─────────┴────────────────────┘

10 rows in set. Elapsed: 0.019 sec. Processed 13.69 thousand rows, 1.12 MB (729.14 thousand rows/s., 59.84 MB/s.)

物化檢視
SELECT
device,
sum(count) AS count,
maxMerge(max_value_state) AS max,
minMerge(min_value_state) AS min,
avgMerge(avg_value_state) AS avg
FROM counter_daily_mv
GROUP BY device
ORDER BY device ASC

┌─device─┬─────count─┬────────max─┬─────min─┬────────────────avg─┐
1 │ 110000000 │ 100008.15 │ 3.051 │ 45914.69035234097 │
2 │ 110000000 │ 100011.164 │ 6.0291 │ 45917.69056040798 │
3 │ 110000000 │ 100014.1 │ 9.0022 │ 45920.690478928045 │
4 │ 110000000 │ 100017.17 │ 12.0063 │ 45923.69086044358 │
5 │ 110000000 │ 100020.164 │ 15.0114 │ 45926.69083122718 │
6 │ 110000000 │ 100023.19 │ 18.0475 │ 45929.691088042426 │
7 │ 110000000 │ 100026.055 │ 21.0566 │ 45932.69135215635 │
8 │ 110000000 │ 100029.14 │ 24.0107 │ 45935.690912335944 │
9 │ 110000000 │ 100032.17 │ 27.0218 │ 45938.69098338585 │
10 │ 110000000 │ 100035.02 │ 30.0429 │ 45941.69140548378 │
└────────┴───────────┴────────────┴─────────┴────────────────────┘

10 rows in set. Elapsed: 0.003 sec. Processed 13.69 thousand rows, 1.12 MB (3.99 million rows/s., 327.87 MB/s.)

源表
SELECT
device,
count(*) AS count,
max(value) AS max,
min(value) AS min,
avg(value) AS avg
FROM counter
GROUP BY device
ORDER BY device ASC

┌─device─┬─────count─┬────────max─┬─────min─┬────────────────avg─┐
1 │ 110000000 │ 100008.15 │ 3.051 │ 45914.69035234098 │
2 │ 110000000 │ 100011.164 │ 6.0291 │ 45917.69056040798 │
3 │ 110000000 │ 100014.1 │ 9.0022 │ 45920.69047892806 │
4 │ 110000000 │ 100017.17 │ 12.0063 │ 45923.69086044358 │
5 │ 110000000 │ 100020.164 │ 15.0114 │ 45926.690831227155 │
6 │ 110000000 │ 100023.19 │ 18.0475 │ 45929.69108804243 │
7 │ 110000000 │ 100026.055 │ 21.0566 │ 45932.691352156355 │
8 │ 110000000 │ 100029.14 │ 24.0107 │ 45935.690912335944 │
9 │ 110000000 │ 100032.17 │ 27.0218 │ 45938.69098338586 │
10 │ 110000000 │ 100035.02 │ 30.0429 │ 45941.69140548378 │
└────────┴───────────┴────────────┴─────────┴────────────────────┘

10 rows in set. Elapsed: 14.041 sec. Processed 1.10 billion rows, 8.80 GB (78.34 million rows/s., 626.72 MB/s.)

該查詢正確總結了包括新插入資料在內的所有資料. 您可以通過在counter表上重新執行原始選擇來檢查計算結果是否一致. 不同之處在於, 物化檢視返回資料的速度要快900倍(在我的測試中為4680倍). 由此可見, 學習一些新語法是值得的!!

此時, 我們可以回過頭來解釋一下在這一切的幕後發生了什麼.

Aggregate Functions

Aggregate functions類似於收集器(collectors), 允許ClickHouse從分佈在多個parts上的資料構建聚合. 下面的圖表顯示瞭如何計算平均值. 我們從源表中的一個可選值開始. 物化檢視使用avgState函式將資料轉換為partial aggregate, avgState函式是一個內部結構. 最後, 在查詢資料時, 應用avgMerge將partial aggregates的資料累加為最終的數字.

af

partial aggregate使物化檢視能夠處理分佈在多個節點上的多個parts上的資料. 即使您更改了group by列, merge函式也可以正確地組裝聚合. 僅僅結合簡單的平均值是行不通的, 因為它們在將每個部分平均值加到總數時缺乏必要的權重. 這種行為有一個重要的後果(這段不會翻譯, 原文: It would not work just to combine simple average values, because they would be lacking the weights necessary to scale each partial average as it added to the total. This behavior has an important consequence.).

還記得上面我們提到過, ClickHouse可以使用帶有彙總的每日資料的物化檢視來回答我們的示例查詢嗎?這是聚合函式工作的結果. 這意味著我們的daily檢視還可以回答關於周、月、年或整個間隔的問題.

ClickHouse有點不尋常, 它直接以SQL語法公開了partial aggregates, 但是它們解決問題的方式非常強大. 當您設計例項化檢視時, 請嘗試使用每日彙總之類的技巧來解決單個檢視中的多個問題. 單個檢視可以回答很多問題.

Table Engines for Materialized Views

ClickHouse有多個對物化檢視有用的引擎. AggregatingMergeTree引擎只使用聚合函式. 如果您想做計數或求和, 您需要使用目標表中的AggregateFunction資料型別來定義它們. 您還需要在檢視和select語句中使用state和merge函式. 例如, 要處理計數(count), 您需要在上面的示例中使用countState(count)和countMerge(count).

我們建議使用SummingMergeTree引擎在物化檢視中進行聚合. 它可以很好地處理聚合函式. 它可以很好地處理聚合函式. 但是, 它會將它們隱藏起來以進行總數和計數, 這對於簡單的案例來說非常方便. 在這種情況下, 它不會阻止您使用state和merge函式; 只是你沒必要這麼做. 同時, 它完成了AggregatingMergeTree的所有工作.

Schema Migration

在生產系統中, 資料庫模式往往會發生變化, 特別是那些正在積極開發的系統. 當使用帶有顯式目標表的物化檢視時, 可以相對容易地管理這些更改.

讓我們舉一個簡單的例子. 假設counter表的名稱更改為counter_replicated. 一旦應用了此更改, 物化檢視將無法工作. 更糟糕的是, 這個錯誤將阻止對counter表的插入. 您可以按照以下方式處理更改.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Delete view prior to schema change.
DROP TABLE counter_daily_mv
-- Rename source table.
RENAME TABLE counter TO counter_replicated
-- Recreate view with correct source table name.
CREATE MATERIALIZED VIEW counter_daily_mv
TO counter_daily
AS SELECT
toStartOfDay(when) as day,
device,
count(*) as count,
maxState(value) AS max_value_state,
minState(value) AS min_value_state,
avgState(value) AS avg_value_state
FROM counter_replicated
GROUP BY device, day
ORDER BY device, day

根據架構遷移中的實際步驟, 您可能必須處理更改物化檢視定義時插入到源表的資料(這些資料未插入到物化檢視中). 您可以使用過濾條件和手動載入來處理該問題, 如我們在主要示例中所示.

Materialized View Plumbing and Data Sizes

最後, 讓我們再看看資料表和物化檢視之間的關係. 目標表是一個普通表. 您可以從目標表或物化檢視中選擇資料. 沒有區別. 此外, 如果您刪除物化檢視, 目標表扔將保留. 正如我們剛才所展示的, 您可以通過簡單地刪除和重新建立檢視來對其進行模式更改. 如果需要更改目標表本身, 可以像對任何其他表一樣執行ALTER table命令.

img

該圖還顯示了源表和目標表的資料大小. 物化檢視通常遠小於其彙總資料的表. 我們的示例就是這樣的結果. 以下查詢顯示了此示例的大小差異.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT 
table,
formatReadableSize(sum(data_compressed_bytes)) AS tc,
formatReadableSize(sum(data_uncompressed_bytes)) AS tu,
sum(data_compressed_bytes) / sum(data_uncompressed_bytes) AS ratio
FROM system.columns
WHERE database = currentDatabase()
GROUP BY table
ORDER BY table ASC

┌─table────────────────────────────────┬─tc─────────┬─tu─────────┬──────────────ratio─┐
│ counter │ 7.14 GiB │ 12.29 GiB │ 0.5805970993939394 │
│ counter_daily │ 248.77 KiB │ 494.31 KiB │ 0.503261750004939 │
│ counter_daily_mv │ 0.00 B │ 0.00 B │ nan │
└──────────────────────────────────────┴────────────┴────────────┴────────────────────┘

如計算所示, 物化檢視目標表大約比物化檢視派生的源資料小3萬倍. 這種差異極大地加快了查詢速度. 如前面所示, 在使用來自物化檢視的資料時, 測試查詢的執行速度大約快了900x(我這裡測試為4680x).

Wrap-up

ClickHouse物化檢視非常靈活, 這得益於強大的聚合功能以及源表、物化檢視和目標表之間的簡單關係. 物化檢視允許顯式目標表, 這是一個有用的特性, 可以簡化模式遷移. 還可以通過向檢視選擇定義新增篩選條件並手動載入丟失的資料來減少可能丟失的檢視更新.

物化檢視還有許多其他方法可以幫助轉換資料. 這裡描述了其中的一些問題, 比如last point queries, 並且計劃將來在這個部落格上寫一些其他的問題.

欲瞭解更多資訊, 請檢視網路研討會ClickHouse and the Magic of Materialized Views.