微信Windows端IM訊息資料庫的優化實踐:查詢慢、體積大、檔案損壞等
本文由微信客戶端技術團隊工程師“Jon”分享,原題“Windows微信:訊息資料庫架構演進”,有較多修訂。
1、引言
本文分享的是,微信客戶端團隊基於對微信使用者日常使用場景和資料分析,通過分離重要和非重要資料、採用可靠的分庫策略等,對微信Windows端IM本地資料庫的架構進行的優化和改造,並最終得到一個具備良好實踐效果的技術改造方案。
2、背景說明
微信的Windows客戶端自2014年上線以來,使用者數穩步增長。隨著時間的不斷推移,很多使用者本地積攢的訊息量越來越大。
最初的本地IM資料庫設計秉著遵循“簡單易用、方便管理”的原則,把使用者收到的所有訊息都統一存放在使用者當前客戶端本地的“同一個SQLite資料檔案中”。
(作者注:微信不會儲存聊天記錄,聊天內容只儲存在使用者手機、電腦等終端裝置上。)
3、當前問題
由於初期這套本地資料庫設計方案的短板,隨著目前微信使用越來越廣泛、訊息堆積越來越多,從而逐漸暴露出了許多技術問題。
3.1 問題1:資料查詢慢
隨著使用時間的推移,資料也逐漸增多,當資料量越來越龐大:
- 1)資料庫的查詢和插入效率會受到影響;
- 2)即使訊息資料庫存在索引,索引的查詢效率也隨之下降。
從檔案系統的角度,資料庫檔案是逐頁增長的。因為長時間的使用微信會使得訊息量的逐步累積,讓資料庫體積逐漸增長,也會導致碎片化更嚴重,這在機械硬碟下,也會進一步影響讀寫效率。
對使用者最直觀的影響就是——切換聊天變得很卡,這個問題對於重度使用者尤甚,甚至會出現點選聊天就卡頓的情況。
3.2 問題2:儲存檔案大
隨著時間的推移,訊息量的逐步累積,資料庫儲存檔案的體積也是越來越大,顯著佔用使用者儲存空間。
3.3 問題3:磁碟檔案損壞
磁碟檔案意外損壞也有可能導致資料丟失。
因為所有訊息都放到一個數據庫檔案,就類似把所有雞蛋放在一個籃子。
資料庫檔案也可能會因為儲存壞道、電腦意外斷電、sqlite自身bug等原因導致資料庫檔案發生損壞。如果發生損壞時,有可能導致使用者丟失訊息資料。即使有DB恢復機制,也無法保證能恢復出所有歷史記錄。
當這種情況發生時,對使用者影響十分大,因為聊天記錄可能沒了!
PS:微信移動端也有類似困擾,有興趣可以閱讀《微信客戶端SQLite資料庫損壞修復實踐》。
4、原因分析
4.1 概述
上述資料庫儲存檔案變大和查詢變慢的問題,都是由於訊息資料的不斷增多引起。
但訊息數的增長是無法避免的,那麼有沒有辦法控制增長速度,並且控制資料庫的大小?
我們從兩個方向進行分析:訊息情況、日常使用場景
4.2 分析1:訊息情況
微信裡的IM訊息可分為三大類:
- 1)單人聊天訊息;
- 2)群聊訊息;
- 3)以及訂閱號/服務號訊息(統稱為公眾號訊息)。
按訊息的重要性來說:
- 1)單聊/群聊訊息:這是使用者的私人訊息,被刪除或者丟失無法恢復,對使用者損失最大;
- 2)公眾號的訊息:因為只要關注了公眾號,都可以拉取閱讀,屬於公共訊息,所以對使用者來說重要性稍低。
按訊息的大小來說:
- 1)基於對測試帳號的訊息大小資料分析,我們發現:佔總條數比例不高的公眾號訊息,佔用了超過一半的資料庫空間;
- 2)經過對測試帳號訊息型別的分析:網頁卡片類訊息是公眾號訊息的主要型別,其平均訊息體大小是文字訊息的幾十倍。
4.3 分析2:日常應用場景分析
眾所周知,我們日常使用微信,都是收發訊息,或者瀏覽最近的訊息。對於更早的訊息,我們一般很少會主動去瀏覽。
越早的訊息,瀏覽的概率越低。
所以:在大多數場景下,我們要讓最常訪問的訊息,不受老資料的影響。
5、解決方案
5.1 概述
針對前述問題並結合上述分析,我們從以下方面對微信Windows端本地SQLite資料庫的架構進行了演進和優化。
涉及的主要優化內容和手段有:
- 1)分庫改造;
- 2)建立訊息索引;
- 3)訊息體積優化;
- 4)提高資料庫健壯性。
下面我們將逐一詳細介紹。
5.2 分庫改造
基於以上分析,首先把公眾號訊息劃分出去,存到單獨的一個數據庫,跟使用者的普通訊息隔離,同時也可以大幅減少普通訊息資料庫的體積。
基於日常使用場景的分析,大部分老資料讀取的頻率很低,所以應該提高最近一段時間的讀寫效率。
對於上述這種情況,我們採取了以時間和空間動態劃分資料庫的方案。初始預設值是每個資料庫存放半年的訊息,超過時間之後新建一個數據庫存放。對於大部分使用場景,我們只需要讀寫最新的資料庫就可以滿足需求,如果需要瀏覽更早的訊息,可以再開啟之前的資料庫進行讀取。
除了時間維度,我們還考慮了空間維度的劃分:如果半年內訊息普通訊息規模超過閾值,也會新建一個數據庫進行儲存,讓每個資料庫大小和資料規模不至於太大,能提升最近一段時間訊息的讀寫效率。
5.3 建立訊息索引
對於最廣泛的使用場景——檢視每一個聊天的訊息,這種場景需要對每一個聊天會話建立一個索引。
這裡的索引方案我們參考了安卓端:即將每一個聊天轉換成一個數值型的ID,從而減少每條索引的長度,提高索引的讀寫效率。(關於微信的移動端SQLite完整資料庫結構,可以參考:《微信本地資料庫破解版(含iOS、Android),僅供學習研究 [附件下載]》)
除此之外,我們還對一些經常訪問的內容,單獨提取成為一個欄位,並且增加索引。比如訊息的子型別(這個在老資料庫中是一個序列化欄位),它沒有索引,但這個欄位經常需要用到,所以單獨提出成為一列,並且加上索引,為訊息按型別查詢提供方便。
5.4 訊息體積優化
IM中訊息顯然總是會越來越多的,但如何能夠在不影響讀寫效率的同時,減少/壓縮訊息資料的體積,也是我們的優化方向。
從上面的資料看,部分訊息體積較大,已經超過了資料庫每頁的大小(Page Size)。
資料庫是按頁儲存資料的,Page Size是資料庫一頁能夠容納的資料。如果一條資料,一個頁放不下,就需要用到溢位頁,把多出來放不下的資料放到溢位頁中,溢位頁可以有多個。
這時候,如果讀取這條資料,就需要把溢位頁也全部讀出來,會增加IO的消耗。
如果壓縮資料,能夠把訊息體壓縮到一個頁能放得下,減少溢位頁的使用,是可以增加IO效能的。
SQLite資料庫溢位頁結構:
(上圖引用自書籍《The Definitive Guide to SQLite》第308頁)
PS:《The Definitive Guide to SQLite》這本書的電子版我也給你找到了,請從下面附件處下載:
The Definitive Guide to SQLite (2nd edition, 2010)-52im.net.pdf.zip (3.61 MB)
但是壓縮需要佔用CPU資源,這裡選擇一種能夠平衡效能和壓縮率的演算法是關鍵。
經過對比壓縮演算法的Benchmark,並且對訊息體壓縮性進行實測,最終選擇了一個高效能壓縮演算法:lz4。
經過對測試帳號的資料分析,不同型別的訊息體大小差異較大。
一般來說:文字訊息的長度不會特別大,但是網頁卡片型別的訊息,體積會較大。由於不同的訊息長度,獲得的壓縮率不一樣,太短的文字長度,壓縮起來並沒有意義。
所以經過訊息體長度、壓縮、,壓縮效能的分析,最終確定對網頁卡片等進行壓縮,在較低效能消耗的前提下,綜合壓縮率可達到40%,減少了IO次數 。
5.5 提高健壯性
如果資料庫檔案由於外部原因發生損壞,則會對體驗造成較大影響。降低損壞率和減少損壞帶來的資料損失,也是我們改進的方向。
按照時間維度劃分資料庫之後,相當於把訊息按時間分散儲存。最新的資料庫負責讀寫最近的訊息,其餘的資料庫只需要根據需求支援瀏覽檢視訊息。
對於老資料庫而言:可以做到按需載入,從而減少了對資料庫的讀寫,也減少了這些資料庫損壞的機率。一旦有資料庫出現損壞,即使無法恢復,也不會所有訊息全部丟失,只會丟失該資料庫對應時間段的訊息,這也可以減少部分資料庫損壞帶來的損失。
在早期使用的單資料庫架構中,由於資料會越攢越多,資料庫體積會持續變大,很難去做備份。分庫之後,每個資料庫體積變小,因而資料庫備份變得更為可行。因為最新的資料庫存在頻繁的訊息讀寫,發生損壞的概率遠高於老資料庫,所以這裡對最新的一個數據庫做定期的備份。
預設配置下,我們每間隔一段時間會對最新的資料庫進行一次備份,該備份是最新的一個數據庫的完整拷貝。若最新的資料庫在讀寫時發生損壞,會先嚐試從備份資料恢復。若恢復成功,則最多丟失從備份到恢復這段時間的資料,進一步降低損壞造成的損失。
6、優化對比
經過對比,對於一個在測試帳號中原始的訊息資料庫,壓縮後大小可以減少接近一半,同時溢位頁數和需要使用溢位頁的記錄數減少也超過一半。
對於讀寫效能,對比壓縮前,壓縮後的讀取和解壓縮效能比之前有接近10%的提升。
7、未來展望
後續我們微信客戶端團隊將繼續研究資料庫修復相關的實踐,持續關注資料庫相關的效能資料,提升可靠性,打造更好的使用者體驗!
以下是相關技術文章,有興趣的讀者可以一併閱讀:
- 微信客戶端SQLite資料庫損壞修復實踐
- 微信移動端的全文檢索優化之路
- 微信移動端的全文檢索多音字問題解決方案
- 微信iOS端的最新全文檢索技術優化實踐
- 微信本地資料庫破解版(含iOS、Android),僅供學習研究 [附件下載]
學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架原始碼:http://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步釋出於:http://www.52im.net/thread-4034-1-1.html)
- 微信Windows端IM訊息資料庫的優化實踐:查詢慢、體積大、檔案損壞等
- IM聊天系統安全手段之傳輸內容端到端加密技術
- 基於Netty,從零開發IM(二):編碼實踐篇(im單聊功能)
- 基於Netty,從零開發IM(二):編碼實踐篇(im單聊功能)
- 基於Netty,徒手擼IM(一):IM系統設計篇
- 基於Netty,徒手擼IM(一):IM系統設計篇
- 一套十萬級TPS的IM綜合訊息系統的架構實踐與思考
- 一套十萬級TPS的IM綜合訊息系統的架構實踐與思考
- 解密抖音春節紅包背後的技術設計與實踐
- B站基於微服務的API閘道器從0到1的演進之路
- 微信團隊分享:微信後臺在海量併發請求下是如何做到不崩潰的
- 微信團隊分享:微信後臺在海量併發請求下是如何做到不崩潰的
- 視訊直播技術乾貨:一文讀懂主流視訊直播系統的推拉流架構、傳輸協議等
- 不為人知的網路程式設計(十一):從底層入手,深度分析TCP連線耗時的祕密
- 萬字長文:手把手教你實現一套高效的IM長連線自適應心跳保活機制
- 萬字長文:手把手教你實現一套高效的IM長連線自適應心跳保活機制
- SpringBoot整合開源IM框架MobileIMSDK,實現即時通訊IM聊天功能
- 直播技術乾貨分享:千萬級直播系統後端架構設計的方方面面
- IM全文檢索技術專題(四):微信iOS端的最新全文檢索技術優化實踐
- 網路程式設計懶人入門(十四):到底什麼是Socket?一文即懂!