微信 SQLite 數據庫修復實踐

一、前言

衆所周知,微信在後臺服務器不保存聊天記錄,微信在移動客戶端全部的聊天記錄都存儲在一個 SQLite 數據庫中,一旦這個數據庫損壞,將會丟失用戶多年的聊天記錄。而咱們監控到現網的損壞率是0.02%,也就是每 1w 個用戶就有 2 個會遇到數據庫損壞。考慮到微信這麼龐大的用戶基數,這個損壞率就很嚴重了。更嚴重的是咱們用的官方修復算法,修復成功率只有 30%。損壞率高,修復率低,這兩個問題都須要咱們着手解決。html

二、SQLite 損壞緣由及其優化

咱們首先來看 SQLite 損壞的緣由,SQLite官網(http://www.sqlite.org/howtocorrupt.html)上列出如下幾點算法

  • 文件錯寫
  • 文件鎖 bug
  • 文件 sync 失敗
  • 設備損壞
  • 內存覆蓋
  • 操做系統 bug
  • SQLite bug

可是咱們經過收集到的大量案例和日誌,分析出實際上移動端數據庫損壞的真正緣由其實就3個:sql

  • 空間不足
  • 設備斷電
  • 文件 sync 失敗

咱們須要針對這些緣由一一進行優化。數據庫

2.一、優化空間佔用

首先咱們來優化微信的空間佔用問題。在這以前微信的部分業務也作了空間清理,例如朋友圈會自動刪除7天前緩存的圖片。可是總的來講對文件空間的使用缺少一個全局把控,全靠各個業務自覺。咱們須要作得更積極主動,要讓開發人員意識到用戶的存儲空間是寶貴的。咱們採起如下措施:數組

  • 業務文件先申請後使用,若是某個文件沒有申請就使用了,會被自動掃描出來並刪除;
  • 每一個業務文件都要申明有效期,是一天、一個星期、一個月仍是永久存儲;
  • 過時文件會被自動清理。

對於微信以外的空間佔用,例如相冊、視頻、其餘App的空間佔用,微信自己是作不了什麼事情的,咱們能夠提示用戶進行空間清理:緩存

2.二、優化文件 sync

2.2.一、synchronous = FULL

設置SQLite的文件同步機制爲全同步,亦即要求每一個事物的寫操做是真的flush到文件裏去。服務器

2.2.一、fullfsync = 1

經過與蘋果工程師的交流,咱們發如今 iOS 平臺下還有 fullfsync (https://www.sqlite.org/pragma.html#pragma_fullfsync) 這個選項,能夠嚴格保證寫入順序跟提交順序一致。設備開發商爲了測評數據好看,每每會對提交的數據進行重排,再統一寫入,亦即寫入順序跟App提交的順序不一致。在某些狀況下,例如斷電,就可能致使寫入文件不一致的狀況,致使文件損壞。微信

2.三、優化效果

多管齊下以後,咱們成功將損壞率下降了一半多;DB損壞仍是沒法徹底避免,咱們仍是得提升修復成功率。架構

三、SQLite 修復邏輯優化

3.一、master 表

首先咱們來看 SQLite 的架構。SQLite 使用 B+樹 存儲一個表,整個 SQLite 數據庫就是這些 B+樹 組成的森林。對於每一個表的元數據(表名、根節點地址、表 scheme 等),都記錄在一個叫 sql_master 的表中。這個 sql_master 表(下簡稱 master 表) 自己也是一個 B+樹 存儲的普通表。併發

3.二、官方修復算法率低下緣由

官方修復算法是這樣一個流程:從 master 表中讀出一個個表的信息,根據根節點地址和創表語句來 select 出表裏的數據,能 select 多少是多少,而後插入到一個新 DB 中。要注意的是 master 表他自己也是一個 B+樹 形式的普通表,DB 第0頁就是他的根節點。那麼只要 master 表某個節點損壞,這個節點下面記錄的表就都恢復不了。更壞的狀況是 DB 第0頁損壞,那麼整個 master 表都讀不出來,就致使整個DB都恢復失敗。這就是官方修復算法成功率這麼低的緣由,太依賴 master 表了。

3.三、備份 master 表

那麼最天然的想法,天然是另外備份一份 master 表了,也不須要用B+樹,直接用數組序列化存儲就好。咱們只須要每隔一段時間輪詢 master 表,看看最近有沒有增刪 table,有的話就全量備份。

3.3.一、備份時機

這裏有個擔心,就是普通數據表的插入會不會致使表的根節點發生變化,也就是說 master 表會不會頻繁變化,若是變化很頻繁的話,咱們就不能簡單地進行輪詢方案了。經過分析源碼,咱們發現 SQLite 裏面 B+樹 算法的實現是 向下分裂 的,也就是說當一個葉子頁滿了須要分裂時,原來的葉子頁會成爲內部節點,而後新申請兩個頁做爲他的葉子頁。這就保證了根節點一旦定下來,是不再會變更的。實際的代碼調試也證明了咱們這個推論。因此說 master 表只會在新建立表或者刪除一個表時纔會發生變化,咱們徹底能夠採用定時輪詢方案。

3.3.二、備份文件有效性

接下來的難題是既然 DB 能夠損壞,那麼這個備份文件也會損壞,怎麼辦呢?咱們採用了 雙備份 的機制。具體來講就是會有新舊兩個備份文件,每一個文件頭都加上 CRC 校驗;每次備份時,從兩個備份文件中選出一個進行覆蓋。具體怎麼選呢?優先選損壞那個備份文件,若是兩個都有效,那麼就選相對較舊的。這就保證了即便本次寫入致使文件損壞,還有另一份備份能夠用。這個作法跟 Realm 標榜的 MVCC(多版本併發控制)的作法有殊途同歸之妙,至關於確認新寫入的文件有效以後,才使用新寫入的文件,不然仍是繼續用舊的有效的文件。

前面提到 DB 損壞的一個常見場景是空間不足,這種狀況下還要分配文件空間給備份文件也是會失敗的。爲了解決這個問題,咱們採起 預先分配空間 的作法,初始值是 32K,大約可存 750 個表的元信息,後續則按照32K的倍數進行增加。

3.四、優化效果

經過備份 master 表,咱們成功將修復成功率提升了一倍多。

四、其餘

經過這些優化,咱們提升了微信聊天記錄存儲的可靠性。這些優化實踐,會同以前在併發性能方面的優化實踐(微信iOS SQLite源碼優化實踐),將會合併到微信即將開源的 WCDB(WeChat Database)組件中。咱們正在進行緊張的代碼整理工做,爭取在 2017 年年中開源 WCDB。


更多精彩內容歡迎關注騰訊 Bugly的微信公衆帳號:

騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!

相關文章
相關標籤/搜索