前言:
在實現前端監控系統的最初,使用了 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 |
- 列資料庫
ClickHouse、Hive、Spark等,是基於列的資料結構進行儲存。通俗來說一個使用者表有 name、id 兩個欄位,這兩個欄位就是列,我們插入使用者資訊,就是按照欄位列來連續儲存。
id: 1, 2, 3 -> 儲存
name: '葉小釵', '子慕', '大衛' -> 儲存
year: 21, 20, 19 -> 儲存
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 類資料庫的資料倉儲之中。
以下為盜圖
在我們開始用 ClickHouse 之後,為了提升某些業務資料的查詢效率,也通過定時同步的方式,把 Mysql 資料同步到了 ClickHouse,再進行查詢分析。
當然如果服務端收集到的第一手資料,通過服務邏輯整理後可以直接儲存,並且後期不會變更,也可以不使用 OLTP 型資料庫 + ETL,而是直接把資料儲存到 OLAP 分析型資料庫儲存。我們的前端監控現在就是這樣使用的。
小結
從兩種應用場景概念出發,我們知道了行資料庫擅長增刪改,列資料庫擅長查詢。如果清晰了他們的區別,就能夠再根據不同系統需求,設計更合適的資料庫方案。
ClickHouse 優劣
官方的文件比較詳細的描述了它的特點,文件中每一個概念都能引申出非常多的知識。 ClickHouse 有著優越的查詢效能和壓縮效能,這是比較突出的。在同型別資料庫跑分中,也是位列前茅。它的優勢和特點在官方有詳細的介紹,這裡不多說了。Distinctive Features of ClickHouse。
它的劣勢我認為目前比較明顯的是社群建設還差了點,它並沒有 Mysql 那麼成熟,有很多配套並沒有三方庫支援。
效能對比
在使用了 ClickHouse 之後,基於當前系統同一結構的資料表,與 Mongo 做了條件查詢能力和儲存空間效能的對比,確實提升了不少,這裡的查詢能力提升和儲存能力提升,也印證了它列儲存格式條件查詢的優勢與其壓縮上的優勢,當然最後還是提一嘴兩種不同型別的資料庫做比較本身是不公平的。對比如下圖:
使用 ClickHouse 之後除了解決了查詢效能問題,還同時節省了儲存空間。特別爽的一點是,以前在 Mongo 中移除了資料,但是空間並沒有釋放的問題,也沒有了。我們通過給資料表配置時間分割槽,ClickHouse 將會按照時間拆分資料庫檔案,我可以執行刪除分割槽,使資料刪除和空間釋放,這延長了硬體儲存空間固定情況下日誌資料的保留時間。
按照月份分割槽:
CREATE TABLE xxx ( time DateTime, status Int32, ) ENGINE = MergeTree PARTITION BY toYYYYMM(time) ORDER BY time
資料表資料夾結構:
刪除月份資料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 我經歷了各種折騰,各種知識學習,各種技術嘗試,最後折騰下來還是收穫滿滿~
好了,今天的內容就到這裡了。對於這些資料庫使用的經驗包,還是來自於持續迭代前端監控系統。我之前已經寫過幾篇關於前端監控的部落格,大家可以關注我,回顧之前的文章,喜歡的話希望你一鍵三連~