微信 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 官方操做系统,鹅厂的工程师都在使用,快来加入咱们吧!

相关文章
相关标签/搜索