天天看點

天啦,從Mongo到ClickHouse我到底經曆了什麼?

前言:

在實作前端監控系統的最初,使用了 Mongo 作為日志資料存儲庫。文檔型存儲,在日志字段擴充和收縮上都能非常友善。天生的 JSON 格式和 NodeJs 配合也非常貼合。就這樣度過了幾個月的蜜月期。

而後有一天發現,表裡的資料越來越大了(單表上億),查詢變慢了,特别是聚合查詢。于是使用了各種優化手段:複合索引、時間條件限制、定期清理過老資料等等,但最終效果都不理想。

在事情的發展過程中也從同僚口中了解到有一個叫 ClickHouse 的資料庫,也許對目前的場景比較有幫助。于是,自己經曆了:

  • “Mongo是最棒的”
  • “一定是我 Mongo 沒用好,繼續深入優化肯定能行”
  • “我玩不動了,看看 ClickHouse 吧”
  • “ClickHouse 真香”

的心路曆程。到現在已經穩定使用 ClickHouse 許久之後,回顧曆史,有了這篇文章。

Mongo的苦與樂:

在最開始使用 Mongo 時,覺得使用起來非常順手。在使用過程中,也不斷的進行過優化,下面大概說下幾個核心設計點和遇到的問題。

  • 分表

前端監控日志收集是按照應用和資料類型區分來建表的,這算作一定的優化和單元拆分,可以讓資料不要全部集中在一起,也友善後期應用删除。在某些表資料量特别大的情況下,想要主從模式的單表按照時間分區,在mongo中,其實并不支援時間分區,隻支援叢集分片。也考慮過按照月份再分表,這樣的結果大概就是 app1_202101、app1_202102 這樣來分,但是這樣分的結果就是查詢會被時間範圍限制,不能友善的查連續的跨月的資料,會影響到我們很多的應用場景。

  • 索引

日志資料主要更偏向于按照時間來查詢,于是使用時間作為單索引。而後為了優化多字段聚合查詢,還使用了基于實際查詢條件的複合索引,但實際效果并不理想,而且索引本身會占據存儲空間。

  • 限制查詢條件

最初進入背景系統的時候,沒有限制時間,是以預設會查詢所有時間範圍的資料,在表資料量非常大的時候,就會需要很長時間,于是對所有查詢都做了預設的時間條件限制。但這種方式治标不治本,也不能完全滿足某些場景下的查詢需求。

  • 資料清理

既然表的資料量大,造成了查詢緩慢,那就删除半年前的資料,使表的資料量維持在半年内。這時可能需要一個定時任務來删除半年前的資料,但其實資料量大的表也隻是監控的某幾個前端應用,隻對這幾個表做删除就行了(其他的應用查詢性能不慢的能保留盡量保留),這裡需要額外的判斷邏輯。

在删除資料的過程中發現,當一個表上億以後,我删除1個月左右的資料(大概千萬級),資料庫CPU直接拉滿了,執行時間會非常長,這個時候我們查詢和插入都會被影響到,這裡提一嘴我們使用的主從模式。沒事,那就一天天的删除吧。清理了資料量大的表之後,又發現空間沒有釋放,Mongo 隻有在删除集合時才會釋放空間,隻是移除資料,空間占用依然在,如果要釋放空間,需要把資料庫先停下來,但這樣會影響正常使用(不停機直接釋放空間也是有方案的,隻是有一定的實作成本,大概流程是拷貝一個新表,再切換,最後再做一次資料同步)。如果某一段時間某表的日志量上升,這個表空間占用會被拉大。突然發現這個優化方案也是治标不治本,難受~

  • explain查詢分析

在優化的過程中,也使用了 Mongo 的分析語句,做查詢分析,但是發現事實就是,這些查詢語句沒啥問題,它就這個速度。

小結

随着,慢查詢的增多,項目本身也成為了一個風險項目。最初前端監控系統和其他業務系統共享使用的雲庫,由于前端監控在某段時期存在資料庫慢查詢的問題,而這個慢查詢剛好也是分鐘級别的定時任務。導緻資料庫産生了很多慢查詢,CPU 消耗也一直維持在較高位置。前端監控本身也會保持一定的監控資料并發量在做存儲。前端監控拖累了整個資料庫,并且由于慢查詢日志很多,導緻其它業務在排查資料庫慢日志的時候,很難找到他們自己的日志。後來,運維同僚就給單獨配置了一個獨享資料庫給前端監控,不是說同僚給開小竈給福利,而是花點小錢,把我這顆毒瘤清理出去。

遷移前配置12核32g三節點(一主兩從),遷移後為2核4g三節點。雖然配置變小了,但是前端監控獨享資料庫後平均響應速度反而變快了一些,猜測是因為遷移到新庫,空間碎片較少和記憶體緩存隻專注在一個庫的原因。前端監控的資料是屬于日志資料,量更大,導緻資料庫性能下降,影響到業務資料服務。日志系統的資料庫和業務分離也是正确合理的。

基于以上種種吧,我感覺路已經走到頭了,該盡力的也盡力了,換個方向了解下 ClickHouse 吧,看看目前的很多問題,它是不是能解決。

ClickHouse全新認知

在最初聽到 ClickHouse 的時候,會下意識認為 Mongo 還有空間可以優化,如果換庫那成本得多高啊,于是就死磕 Mongo,用盡能想到的辦法。其實作在回過頭來看,有好有壞。好處是能夠更加深刻的體會到 Mongo 資料庫本身的特性,有實踐作為檢驗标準,對後面對比 ClickHouse 有一定幫助。壞處是,對解決問題本身來說,浪費了一些時間,折騰的有點過頭。現在開始介紹一下 ClickHouse 到底是怎樣的一個資料庫。

行存儲和列存儲

從資料存儲結構來劃分,這裡把資料庫分成了行資料庫和列資料庫兩種。

  • 行資料庫

傳統型資料庫或者說大家最常用的資料庫,大都是基于行的資料結構進行資料存儲的,比如 Mysql、Mongo 等。 通俗來說一個使用者表有 id、name、year、 兩個字段,我們插入的使用者資訊,就是按照一個完整的使用者資訊插入一行連續資料。

id:1,name: '葉小钗', year: 21 -> 存儲
id:2,name: '子慕', year: 20 -> 存儲
id:3,name: '大衛', year: 19 -> 存儲
Index ID 名字 年齡
#0 1 葉小钗 21
#1 2 子慕 20
#2 3 大衛 19
天啦,從Mongo到ClickHouse我到底經曆了什麼?
  • 列資料庫

ClickHouse,是基于列的資料結構進行存儲。通俗來說一個使用者表有 name、id 兩個字段,這兩個字段就是列,我們插入使用者資訊,就是按照字段列來連續存儲。

id: 1, 2, 3 -> 存儲
name: '葉小钗', '子慕', '大衛' -> 存儲
year: 21, 20, 19 -> 存儲
天啦,從Mongo到ClickHouse我到底經曆了什麼?
key #0 #1 #2
ID 1 2 3
名字 葉小钗 子慕 大衛
年齡 21 20 19

底層資料結構的差別,将會直接影響到具體的查詢,以下的動圖是 ClickHouse 官方文檔的差別展示動圖。如圖所示,查詢滿足條件的3個字段的資料。圖1中,行資料庫在進行查詢的時候,先一行行掃描資料,找到滿足條件的3個字段傳回,這個過程中,會讀取到其它很多不需要的字段,這就增加了 I/O 造成了記憶體的浪費,減慢查詢速度。

而圖2,因為列資料庫本身的存儲就是以字段列來進行連續存儲,是以隻需要掃描這3列,就能找到滿足條件的資料。

行資料庫(圖1):

列資料庫(圖2):

行資料庫複合索引

如果隻是從上面的描述來看,初次了解到這樣概念的同學可能會覺得。哇塞,這好厲害,完爆 Mysql、Mongo 等資料庫,以後我就用它吧!或者 Mysql 老玩家會說,切、這個問題用索引不就行了嗎,垃圾别忽悠我!當然故事并沒有這麼簡單,上面的内容也不全面,耐住性子,後面再繼續道來。

Mysql 和 Mongo 支援多字段建立複合索引,來提升查詢速率。但是索引本身是需要占用額外的存儲空間的,複合索引字段和資料量越多,就越浪費空間。并且複合索引有最左比對限制,查詢時無法靈活命中索引。

小結

從前面的内容我們講到了,在使用 Mongo 這樣的行資料庫時,本人在遇到性能問題并折騰許久以後,已經黔驢技窮了。而後了解到了列資料庫 ClickHouse,發現了行列資料庫的核心差別,在對比查詢的時候,發現原來列資料庫這麼厲害,那是不是行資料庫就應該棄用了呢?那如果是這樣的話,為啥 Mysql 還是那麼主流?接下來,再講講,OLTP(聯機事務處理)與OLAP(聯機分析處理)。

OLTP與OLAP

OLTP(On-Line Transaction Processing)和OLAP(On-Line Analytical Processing)是兩個不同應用場景的概念。

OLTP(聯機事務處理)更加注重事務處理,重點在執行資料庫的寫操作(增删改),要求寫操作的實時性和安全性。

OLAP(聯機分析處理)更加注重資料分析,重點在執行資料庫的讀操作,要求查詢操作的實時性。

傳統的資料庫如 Mysql 主要是側重支援 OLTP,在資料量小的情況下,大部分時候是沒有查詢性能瓶頸的。是以,很多時候大家都主要接觸這類資料庫。随着技術的發展和網際網路使用者的增多。在面對大資料量查詢性能無法突破,于是發現需要換個更加利于 OLAP 的分析型資料庫了。 對于資料的生産,大部分時候,我們離不開傳統 OLTP 資料庫,是以常常是使用 OLTP 資料庫做業務資料存儲(友善增删改),後續通過解析業務資料,再把資料清洗一部分到 OLAP 資料庫中做專門的查詢分析。如此兩類資料庫并存,做到了讀寫分離,各自做自己更擅長的事情。

現在流行的數倉,就是采用這樣的架構。下圖的資料源,使用的 OLTP 資料庫,經過 ETL(抽取extract、轉換transform、加載load) 存儲到使用 OLAP 類資料庫的資料倉庫之中。

以下為盜圖

天啦,從Mongo到ClickHouse我到底經曆了什麼?

在我們開始用 ClickHouse 之後,為了提升某些業務資料的查詢效率,也通過定時同步的方式,把 Mysql 資料同步到了 ClickHouse,再進行查詢分析。

當然如果服務端收集到的第一手資料,通過服務邏輯整理後可以直接存儲,并且後期不會變更,也可以不使用 OLTP 型資料庫 + ETL,而是直接把資料存儲到 OLAP 分析型資料庫存儲。我們的前端監控現在就是這樣使用的。

小結

從兩種應用場景概念出發,我們知道了行資料庫擅長增删改,列資料庫擅長查詢。如果清晰了他們的差別,就能夠再根據不同系統需求,設計更合适的資料庫方案。

ClickHouse 優劣

官方的文檔比較詳細的描述了它的特點,文檔中每一個概念都能引申出非常多的知識。 ClickHouse 有着優越的查詢性能和壓縮性能,這是比較突出的。在同類型資料庫跑分中,也是位列前茅。它的優勢和特點在官方有詳細的介紹,這裡不多說了。Distinctive Features of ClickHouse。

它的劣勢我認為目前比較明顯的是社群建設還差了點,它并沒有 Mysql 那麼成熟,有很多配套并沒有三方庫支援。

性能對比

在使用了 ClickHouse 之後,基于目前系統同一結構的資料表,與 Mongo 做了條件查詢能力和存儲空間性能的對比,确實提升了不少,這裡的查詢能力提升和存儲能力提升,也印證了它列存儲格式條件查詢的優勢與其壓縮上的優勢,當然最後還是提一嘴兩種不同類型的資料庫做比較本身是不公平的。對比如下圖:

天啦,從Mongo到ClickHouse我到底經曆了什麼?

使用 ClickHouse 之後除了解決了查詢性能問題,還同時節省了存儲空間。特别爽的一點是,以前在 Mongo 中移除了資料,但是空間并沒有釋放的問題,也沒有了。我們通過給資料表配置時間分區,ClickHouse 将會按照時間拆分資料庫檔案,我可以執行删除分區,使資料删除和空間釋放,這延長了硬體存儲空間固定情況下日志資料的保留時間。

按照月份分區:

CREATE TABLE xxx (
    time DateTime,
    status Int32,
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(time)
ORDER BY time      

資料表檔案夾結構: 

天啦,從Mongo到ClickHouse我到底經曆了什麼?

删除月份資料SQL:

ALTER TABLE xxx DROP PARTITION '202104'      

ClickHouse 使用中的一些記錄

新API較多、舊版本不支援

由于疊代比較快,是以文檔中的一些文法可能在不久前的資料庫版本中并不支援。一定要多注意目前使用的資料庫版本,盡量保證生産環境、測試環境、還有本地環境是同一個版本的 ClickHouse。

支援 Mysql 協定 但有限制

ClickHouse 支援 Mysql 協定,可以使用 Mysql 連接配接方式和 sql 文法。打開 ClickHouse 9004 端口便可通過 Mysql 連接配接庫配置位址端口賬戶密碼,進行連接配接和查詢。這對于使用者是友好的。但是會有些限制,比如不支援預編譯、某些資料類型會被當作字元串傳遞。

MYSQL ORM 問題

ORM(對象關系映射Object Relational Mapping)它可以抹平一些資料庫操作成本,幫助我們提升開發效率。既然 ClickHouse 提供了 Mysql 協定,是以理論上其實也可以使用 Mysql ORM 庫。

我使用的 Node,是以用 Sequelize 做為 ORM 庫, 驅動程式使用 Mysql2。由于建表和資料類型的差異原因,是以并不能用 Mysql 的 ORM 庫管理 ClickHouse 的表結構。隻能使用 sql 文法。

在使用 select * 語句的時候,由于 ORM 會把 * 轉換為所有字段,就會變成 select id,key1,key2,key3 ,因為 ClickHouse 并沒有預設使用自增 id 字段,這時就會報錯,那大不了指定所有字段列,勉強還能用。

在使用 save 方法儲存資料的時候(也就是 insert 語句),收到一個 ClickHouse 的報錯:

MySQLHandler: MySQLHandler: Cannot read packet: : Code: 48, e.displayText() = DB::Exception: Command [ERRFMT] is not implemented., Stack trace (when copying this message, always include the lines below)      

報錯資訊描述的并不是很準确,經過一段時間源碼追蹤,根據下面的上下文,目測是 Mysql2 包傳遞到 ClickHouse 之後,有部分指令,并不支援。MySQLHandler.cpp.html#183 。

而後從 Mysql2 源碼看出來,其預設使用了 PrepareStatement,會通過此語句進行預處理,而 ClickHouse 對于 Mysql 協定不支援預編譯。是以最後的結論是 ClickHouse 可以使用 Mysql 協定,但是并不支援使用 Mysql ORM庫,有這樣的限制對于實際應用開發并不友好。

CLICKHOUSE ORM

經過評估選擇了 ClickHouse 的 HTTP接口 調用資料庫。從官方的三方用戶端庫( client-libraries)中選擇了 TimonKK/clickhouse 這個庫。它主要是實作了一個連接配接驅動,當然HTTP是無狀态的,它就是做了一些封裝,幫忙組裝HTTP請求,而我們隻需要寫SQL語句就行。

隻有一個驅動還不行,沒有ORM的加成,開發體驗并不好,效率也偏低。于是自己參考了 Mongoose(ODM)和 SequelizeJS 的使用習慣,開發了一個比較基礎的 ClickHouse ORM:node-clickhouse-orm。因為 ClickHouse 熟悉度不夠,自己在這方面知識能力也不足,并且投入成本太高了,是以就隻實作了一部分平時我用到的功能。

目前還沒有推廣,使用量也很小,最近也就是2022年2月中旬左右,對此庫進行了改造,目前已經釋出了2.0.0内測版本npmjs.com/clickhouse-…。

後續它還會疊代和維護,但是一個人的精力和能力有限,是以我打算建個開源交流群,歡迎體驗和入群參與讨論。

ORM/ODM

  • ORM:Object Relational Mapping
  • ODM:Object Document Mapping。

之前有一點迷惑這個 ORM 和 ODM 到底有啥差別,為什麼名稱不一樣。Mongoose 被稱為 ODM,并不是 ORM,但是它們看起來沒啥差別,就因為 Mongo 是文檔型資料庫,這裡就硬要改個 ODM 的名字?

最後想了下,雖然它們的名稱有點差別,但是它們的本質還是将資料的存儲格式抽象為程式中的邏輯對象,讓使用方通過操作對象就能和資料庫互動。都是資料庫中間件,因資料庫類型不同叫法不同。而且 Mongo 本身的資料庫操作文法就已經是對象的操作方式了,它和 Mysql 這種也是有所差別。

推薦 LightHouse GUI工具

LightHouse是 ClickHouse 的 GUI 工具,用這個名稱的庫比較多,不要搞混了。它是用 HTML 開發的,是以隻需要直接下載下傳代碼庫到本地,然後用浏覽器運作就行,都不需要啟動伺服器來運作,直接輕按兩下打開即可。

這個工具功能比較簡單,但是也比較安全。使用的 HTTP 接口和資料庫互動,它的 Ajax 請求 Query 裡有一個

readonly=1

,是以工具本身主要是用來做查詢的,如果想要做其它 SQL 操作,可以改源代碼去掉

readonly=1

結語

系統引入 ClickHouse 之後,帶來了極大的性能提升。但系統并非不再使用 Mongo,系統本身的中繼資料和一些需要常修改的資料依然還是使用的 Mongo。

天啦,從 Mongo 到 ClickHouse 我經曆了各種折騰,各種知識學習,各種技術嘗試,最後折騰下來還是收獲滿滿~

好了,今天的内容就到這裡了。對于這些資料庫使用的經驗包,還是來自于持續疊代前端監控系統。我之前已經寫過幾篇關于前端監控的部落格,大家可以關注我,回顧之前的文章,喜歡的話希望你一鍵三連~

  • 打造前端監控系統
  • 前端監控SDK開發分享
  • 從無到有<前端異常監控系統>落地

有沒有人打賞?沒有的話,那我晚點再來問問。

關注大詩人公衆号,第一時間擷取最新文章。

---轉發請标明,并添加原文連結---

繼續閱讀