Go 大資料生態開源專案 CDS 中 ClickHouse 使用的建表方案

kevwan 發表於 2020-11-29
Go

基於 go-zero 構建的 CDS 資料同步與 ClickHouse 的建表方案緊密相關,下面介紹了兩種建表方案。

實時表

起初 ClickHouse 並未考慮資料更新的問題,在官網中有介紹 ClickHouse 誕生的歷史。

https://clickhouse.tech/docs/en/introduction/history/

從中可以看出兩點:

  • 用於日誌分析
  • 使用非彙總資料線上計算

也就是說資料匯入後不考慮變更,而且想要直接分析源資料。

但是現實中我們有很多有價值的資料在事務型資料庫中儲存,或者我們需要用到的資料在事務型資料庫中。而事務型資料庫中儲存的是狀態型資料 (可以發生變化),對於 ClickHouse 而言,資料更新是一個非常困難的操作

因為上面提到的需求,更新這個功能在隨後還是以 mutation 的形式加入了。這種 mutation 形式在官網中:

https://clickhouse.tech/docs/en/sql-reference/statements/alter/update/

有這樣的描述 “this is a heavy operation not designed for frequent use.” 而且不支援更新用於計算主鍵或分割槽鍵的列。

可以看出,直接對資料執行更新操作對 ClickHouse 來說是一件非常糟糕的事。

這種情況在其他用於大資料處理的資料庫中也存在,比如以 HDFS 為支撐的資料倉儲,它同樣更多的要求資料是不可變的。

即便提供了更新操作,效能都不佳。解決這個需求一般的方法是用程式定期的對過往的資料進行合併,形成一份最新的資料。這種方法的缺點是不能做到實時更新資料。

cds 同步設計目標之一是解決事務型資料庫資料實時同步至 ClickHouse 的問題。

ClickHouseMergeTree 表引擎,這種引擎的特點就是它會自動在後臺合併資料。

MergeTree 表引擎家族中有一個 ReplacingMergeTree 的表引擎,它會在合併資料的時候根據主鍵刪除具有相同主鍵的重複項。不過官網也指出了它的缺點:

“Data deduplication occurs only during a merge. Merging occurs in the background at an unknown time, so you can't plan for it. Some of the data may remain unprocessed. Although you can run an unscheduled merge using the OPTIMIZE query, don't count on using it, because the OPTIMIZE query will read and write a large amount of data. Thus, ReplacingMergeTree is suitable for clearing out duplicate data in the background in order to save space, but it doesn't guarantee the absence of duplicates.”

沒有提到的是,在查詢時加上 final 關鍵字就可以得到最終資料,而不用動用 OPTIMIZE 這種超重型操作。

final 也有缺點,就是會導致 ClickHouse 以單執行緒的方式執行,不過這個方式在新的版本中已經改變了https://github.com/ClickHouse/ClickHouse/pull/10463,開發中的新引擎 MaterializeMySQL也使用了同樣的方法 https://github.com/ClickHouse/ClickHouse/issues/4006 。加上如何合理使用 prewhere 和索引,查詢速度還算可以。

利用 ReplacingMergeTree的表引擎,我們只需要將資料插入 ClickHouse ,資料就可以被自動合併了。

update

那麼刪除的操作呢?可以新增一個刪除的標誌列。如果源資料被刪除,那麼插入一條新的刪除標誌為真的資料,ReplacingMergeTree 合併後會變成這一列,查詢時在 where 中新增過濾條件就好了

delete)

cds中的rtu模組已經實現了上述update/delete變更insert` 的操作。

rtu

ReplacingMergeTree 具體建表方式如下:

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
) ENGINE = ReplacingMergeTree([ver])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]

這裡有幾個需要非常注意的點:

  1. 你需要指定一個版本列用於資料合併時確定最新資料,一般指定成 update_time 可以實現上面的功能。

  2. 資料的合併發生在同一個叢集分片的同一個分割槽裡。也就是說對資料插入有所要求。

ClickHouse 推薦資料直接插入 clickhosue 叢集節點的本地儲存表中,而不是通過分散式表插入。這意味著你需要將資料按主鍵自行雜湊好後插入對應叢集節點的本地儲存表。packge cds/tools/ckgroup實現了這個功能。

  1. 這種表引擎對 ORDER BY 的設定有所要求,它必須是主鍵,但主鍵可能並非 olap 查詢的常用維度,會導致查詢效能不佳。如果需要很高的查詢效能,可以考慮定期將資料匯入至普通 MergeTree 表中。

  2. ReplacingMergeTree表引擎合併後會刪除舊版本的資料。

這種表引擎給 cds 中全量同步和增量同步一起進行時可能出現的重複資料自動去重。

歷史版本與還原

如果想要查詢歷史中某段時間幾天的資料每天的情況,就需要儲存每天所有的資料。如果每天儲存一個所有資料的快照的話,將會非常佔用儲存空間,很不經濟。

如果只儲存增量和變更資料將會節省很多空間,問題是如何從一堆增量和變更資料中還原每一天的資料?

對於 clickhouse 而言,這種情況下不能使用 ReplacingMergeTree 表引擎,在上面提到的第 4 點ReplacingMergeTree表引擎合併後會刪除舊版本的資料。

在 clickhouse 中使用普通 MergeTree,利用 argMax合理的分割槽 方案可以實現版本還原。如:

-- 查詢某一日全部使用者中編輯角色的數量
SELECT date
     , uniq(user_id)
FROM (
         SELECT date
              , id                                    user_id
              , argMax(users.role, users.update_time) role_
         FROM (
                  SELECT id
                       , update_time
                       , role
                       , toDate('2020-11-11') date
                  FROM default.user
                  WHERE 
                    create_time < toDateTime(date + INTERVAL 1 DAY)
                    AND update_time < toDateTime(date + INTERVAL 1 DAY)
                  ) users
         GROUP BY date, id
         ) day_snap_shot -- 生成當日快照
WHERE role_ = 'editor' 
GROUP BY date;

上面介紹了兩種建表方案,一種實時的,一種帶有所有版本變更的。兩種方案各有優劣,根據使用場景選擇。這兩種方案都不完美。

我們仍然在探索新的方法,希望你也能參與進來,一起建設更好的資料。

專案地址

cds 專案地址:https://github.com/tal-tech/cds

go-zero 專案地址:https://github.com/tal-tech/go-zero

如果覺得文章不錯,歡迎 star 並加入微信交流群 🤝

cds

更多原創文章乾貨分享,請關注公眾號
  • Go 大資料生態開源專案 CDS 中 ClickHouse 使用的建表方案
  • 加微信實戰群請加微信(註明:實戰群):gocnio