老闆讓我 7 天研究一個 Plausible 統計

kumfo發表於2022-03-18

開始文章前先扯個淡

最近在思考統計功能的時候,一開始是尋思著用程式配合mysql來進行儲存,但是如果用mysql來存一些比較細節話的資料然後再做統計,資料量會非常龐大,然後又思考用mysql儲存資料,然後用定時指令碼或佇列來按照具體要求更新到redis中,這樣查詢就很快了。

後來想起我們本身用的plausible來記錄資料,然後尋思著用plausibleevent來記錄自定義的goals,發現的確可行,模擬了500萬資料之後,表現良好,感覺自己棒棒噠。誰知道資料量起來之後,2000萬的資料就能讓服務炸了。期間,也遇到了一些其他問題,也給官方提了一些建議,逼不得已不得不學了elixir,當然這是後話了。這裡提一嘴這個plausible主要是給後來者提個醒,如果也想用plausible來做自定義的一些統計功能,最好是放棄掉,改為直接用ClickHouse來處理。

最後沒有辦法,我觀察了plausible的資料儲存結構,發現根本原因就是event的儲存型別導致的,效能誤解,從而萌生了去研究一下ClickHouse,看看自己實現能不能得到很好的效能,於是便有了這篇文章,如果對大家有用,歡迎探討,如果理解有錯的話,歡迎各位給我斧正。

行式資料庫 vs 列式資料庫結構對比

以下先以官方文件的表格來拋磚引玉,記錄一下這段時間對於clickhouse的學習成果。

行式資料庫

RowWatchIDJavaEnableTitleGoodEventEventTime
#0893543506621Investor Relations12016-05-18 05:19:20
#1903295099580Contact us12016-05-18 08:10:20
#2899537060541Mission12016-05-18 07:38:00
#N

列式資料庫

Row:#0#1#2#N
WatchID:893543506629032950995889953706054
JavaEnable:101
Title:Investor RelationsContact usMission
GoodEvent:111
EventTime:2016-05-18 05:19:202016-05-18 08:10:202016-05-18 07:38:00

從表格上看,一下子比較難以理解,行式資料庫和我們電子表格看到的表現一樣,但是儲存是一行一行的儲存,列式資料庫是以一列一列的儲存。以下是以檔案儲存結構來做一個示例:

也就是說,對於行資料庫,一行資料是形成一個完整的檔案(實際底層具體儲存不是這樣子,這裡是方便說明整個思想),這個檔案包含了這一整行的每個資料欄位,取到這一行就能拿到完整的資料。

而對於列式儲存來說,一列資料為一個檔案,如以上所展示的,WatchId 的資料全部存在WatchId檔案裡面。

然後我做了個腦圖再次對這個結構做個對比。
資料表對比.png

以下用json來描述兩種資料結構的區別:

  1. 行式資料庫儲存結構

    [
     {
         "id": 1,
         "title": "t1",
         "content": "c1"
     },
     {
         "id": 2,
         "title": "t2",
         "content": "c2"
     },
     {
         "id": 2,
         "title": "t2",
         "content": "c2"
     },
     {
         "id": 3,
         "title": "t3",
         "content": "c3"
     }
    ]
  2. 列式資料庫結構

    {
     "id": [
         1,
         2,
         3,
         4
     ],
     "title": [
         "t1",
         "t2",
         "t3",
         "t4"
     ],
     "content": [
         "c1",
         "c2",
         "c3",
         "c4"
     ]
    }

列式資料庫主要應用場景

列式資料庫主要運用場景為OLAP(聯機分析),也就是說主要應用於資料分析這一塊,通常不用來處理具體的業務資料儲存。行式資料庫主要用於處理業務資料的儲存,業務資料的特點是對單條資料經常會產生更新,會對資料一致性有非常嚴格的要求。對於OLAP來說,資料很少會有變動,所以通常來說,列式資料庫主要目的是為了更快的寫入以及更快的資料查詢和統計。

以下我列出一些OLAP場景的關鍵特徵:

  • 絕大多數是讀請求
  • 資料以相當大的批次(> 1000行)更新,而不是單行更新;或者根本沒有更新。
  • 已新增到資料庫的資料不能修改。
  • 對於讀取,從資料庫中提取相當多的行,但只提取列的一小部分。
  • 寬表,即每個表包含著大量的列
  • 查詢相對較少(通常每臺伺服器每秒查詢數百次或更少)
  • 對於簡單查詢,允許延遲大約50毫秒
  • 列中的資料相對較小:數字和短字串(例如,每個URL 60個位元組)
  • 處理單個查詢時需要高吞吐量(每臺伺服器每秒可達數十億行)
  • 事務不是必須的
  • 對資料一致性要求低
  • 每個查詢有一個大表。除了他以外,其他的都很小。
  • 查詢結果明顯小於源資料。換句話說,資料經過過濾或聚合,因此結果適合於單個伺服器的RAM中

MergeTree(合併樹) 資料表引擎

主要特點

MergeTreeClickHouse最基礎的資料儲存引擎,絕大部分時候都是用這個資料引擎。

MergeTree 系列的引擎被設計用於插入極大量的資料到一張表當中。資料可以以資料片段的形式一個接著一個的快速寫入,資料片段在後臺按照一定的規則進行合併。相比在插入時不斷修改(重寫)已儲存的資料,這種策略會高效很多。

主要特點:

  • 儲存的資料按主鍵排序。

    這使得您能夠建立一個小型的稀疏索引來加快資料檢索。

  • 如果指定了分割槽鍵的話,可以使用分割槽。

    在相同資料集和相同結果集的情況下 ClickHouse 中某些帶分割槽的操作會比普通操作更快。查詢中指定了分割槽鍵時 ClickHouse 會自動擷取分割槽資料。這也有效增加了查詢效能。

    通常來說,都是以時間來進行分割槽,從表象來看的話,就是一個分割槽是一個資料夾,這樣可以避免一個檔案儲存大量的資料。

  • 支援資料副本。

    ReplicatedMergeTree 系列的表提供了資料副本功能。

  • 支援資料取樣。

    需要的話,您可以給表設定一個取樣方法。

結構說明

table_name.png

這段時間為了研究plausible的統計效能問題,主要也是圍繞業務展開的,對其他幾個資料引擎沒有做深入的瞭解,其他引擎也是繼承於MergeTree引擎,如果大家感興趣可以瞭解一下,以下我大致列出其他幾個資料引擎:

  • VersionedCollapsingMergeTree

    • 允許快速寫入不斷變化的物件狀態。
    • 刪除後臺中的舊物件狀態。 這顯著降低了儲存體積。
  • GraphiteMergeTree

    • 該引擎用來對 Graphite資料進行瘦身及彙總。對於想使用CH來儲存Graphite資料的開發者來說可能有用。
    • 如果不需要對Graphite資料做彙總,那麼可以使用任意的CH表引擎;但若需要,那就採用 GraphiteMergeTree 引擎。它能減少儲存空間,同時能提高Graphite資料的查詢效率。
  • AggregatingMergeTree

    • 該引擎繼承自 MergeTree,並改變了資料片段的合併邏輯。 ClickHouse 會將一個資料片段內所有具有相同主鍵(準確的說是 排序鍵)的行替換成一行,這一行會儲存一系列聚合函式的狀態。
  • CollapsingMergeTree

    • 該引擎繼承於 MergeTree,並在資料塊合併演算法中新增了摺疊行的邏輯。
  • ReplacingMergeTree

    • 該引擎和 MergeTree 的不同之處在於它會刪除排序鍵值相同的重複項
  • SummingMergeTree

    • 當合並 SummingMergeTree 表的資料片段時,ClickHouse 會把所有具有相同主鍵的行合併為一行,該行包含了被合併的行中具有數值資料型別的列的彙總值。如果主鍵的組合方式使得單個鍵值對應於大量的行,則可以顯著的減少儲存空間並加快資料查詢的速度。

ClickHouse 資料庫目前梳理的一些注意點

  • 不適合處理事務,沒有完整的事務支援。
  • 缺少高頻率,低延遲的修改或刪除已存在資料的能力。僅能用於批量刪除或修改資料,資料更新是非同步的
  • 大部分資料查詢適用標準的SQL語句,如果一直使用MySQL這種行式資料庫,沒太多切換成本
  • 定義排序欄位或者說是定義索引,儲存時候就會按照排序儲存,範圍查詢速度非常快
  • ClickHouse提供各種各樣在允許犧牲資料精度的情況下對查詢進行加速的方法(目前我沒有用過,暫時不知道應用場景)
  • 稀疏索引使得ClickHouse不適合通過其鍵檢索單行的點查詢。
  • 資料型別豐富,支援Map(key,value),Tuple(T1,T2,...),Array(T), Geo,Nested巢狀資料結構(類似於巢狀表)
  • Datetime 型別group bytoStartOf*開頭的轉換函式,如toStartOfDay,相比用DATE函式轉換,能提升幾十倍效能

相關文章