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的數據累加為最終的數字.

afaf

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命令.

imgimg

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM