前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >微信移动端数据库组件WCDB系列(二) — 数据库修复三板斧

微信移动端数据库组件WCDB系列(二) — 数据库修复三板斧

作者头像
腾讯Bugly
发布于 2023-04-23 08:10:28
发布于 2023-04-23 08:10:28
2K0
举报
文章被收录于专栏:腾讯Bugly的专栏腾讯Bugly的专栏

本文来自:微信移动客户端开发团队公众号(WeMobileDev)

前言

长久以来SQLite DB都有损坏问题,从AndroidiOS等移动系统,到WindowsLinux 等桌面系统都会出现。由于微信所有消息都保存在DB,服务端不保留备份,一旦损坏将导致用户消息被清空,显然不能接受。

我们即将开源的移动数据库组件 WCDB (WeChat Database),致力于解决 DB 损坏导致数据丢失的问题。

之前一篇文章《微信 SQLite 数据库修复实践》介绍了微信对SQLite数据库修复以及降低损坏率的实践, 这次再深入介绍一下微信数据库修复的具体方案和发展历程。

我们的需求

具体来说,微信需要一套满足以下条件的DB恢复方案:

  • 恢复成功率高。 由于牵涉到用户核心数据,“姑且一试”的方案是不够的,虽说 100% 成功率不太现实,但 90% 甚至 99% 以上的成功率才是我们想要的。
  • 支持加密DB。 Android 端微信客户端使用的是加密 SQLCipher DB,加密会改变信息 的排布,往往对密文一个字节的改动就能使解密后一大片数据变得面目全非。这对于数据恢复 不是什么好消息,我们的方案必须应对这种情况。
  • 能处理超大的数据量。 经过统计分析,个别重度用户DB大小已经超过2GB,恢复方案 必须在如此大的数据量下面保证不掉链子。
  • 不影响体验。 统计发现只有万分之一不到的用户会发生DB损坏,如果恢复方案 需要事先准备(比如备份),它必须对用户不可见,不能为了极个别牺牲全体用户的体验。

经过多年的不断改进,微信先后采用出三套不同的DB恢复方案,离上面的目标已经越来越近了。

官方的Dump恢复方案

Google 一下SQLite DB恢复,不难搜到使用.dump命令恢复DB的方法。.dump命令的作用是将 整个数据库的内容输出为很多 SQL 语句,只要对空 DB 执行这些语句就能得到一个一样的 DB。

.dump命令原理很简单:每个SQLite DB都有一个sqlite_master表,里面保存着全部table 和index的信息(table本身的信息,不包括里面的数据哦),遍历它就可以得到所有表的名称和 CREATE TABLE ...的SQL语句,输出CREATE TABLE语句,接着使用SELECT * FROM ... 通过表名遍历整个表,每读出一行就输出一个INSERT语句,遍历完后就把整个DB dump出来了。 这样的操作,和普通查表是一样的,遇到损坏一样会返回SQLITE_CORRUPT,我们忽略掉损坏错误, 继续遍历下个表,最终可以把所有没损坏的表以及损坏了的表的前半部分读取出来。将dump 出来的SQL语句逐行执行,最终可以得到一个等效的新DB。由于直接跑在SQLite上层,所以天然 就支持加密SQLCipher,不需要额外处理。

(图:dump输出样例)

这个方案不需要任何准备,只有坏DB的用户要花好几分钟跑恢复,大部分用户是不感知的。 数据量大小,主要影响恢复需要的临时空间:先要保存dump 出来的SQL的空间,这个 大概一倍DB大小,还要另外一倍 DB大小来新建 DB恢复。至于我们最关心的成功率呢?上线后,成功率约为30%。这个成功率的定义是至少恢复了一条记录,也就是说一大半用户 一条都恢复不成功!

研究一下就发现,恢复失败的用户,原因都是sqlite_master表读不出来,特别是第一页损坏, 会导致后续所有内容无法读出,那就完全不能恢复了。恢复率这么低的尴尬状况维持了好久, 其他方案才渐渐露出水面。

备份恢复方案

损坏的数据无法修复,最直观的解决方案就是备份,于是备份恢复方案被提上日程了。备份恢复这个 方案思路简单,SQLite 也有不少备份机制可以使用,具体是:

  • 拷贝: 不能再直白的方式。由于SQLite DB本身是文件(主DB + journal 或 WAL), 直接把文件复制就能达到备份的目的。
  • Dump: 上一个恢复方案用到的命令的本来目的。在DB完好的时候执行.dump, 把 DB所有内容输出为 SQL语句,达到备份目的,恢复的时候执行SQL即可。
  • Backup API SQLite自身提供的一套备份机制,按 Page 为单位复制到新 DB, 支持热备份。

这么多的方案孰优孰劣?作为一个移动APP,我们关心的无非就是 备份大小、备份性能、 恢复性能 几个指标。微信作为一个重度DB使用者,备份大小和备份性能是主要关注点, 原本用户就可能有2GB 大的 DB,如果备份数据本身也有2GB 大小,用户想必不会接受; 性能则主要影响体验和备份成功率,作为用户不感知的功能,占用太多系统资源造成卡顿 是不行的,备份耗时越久,被系统杀死等意外事件发生的概率也越高。

对以上方案做简单测试后,备份方案也就基本定下了。测试用的DB大小约 50MB, 数据条目数大约为 10万条

(图:备选方案性能对比)

可以看出,比较折中的选择是 Dump + 压缩,备份大小具有明显优势,备份性能尚可, 恢复性能较差但由于需要恢复的场景较少,算是可以接受的短板。

微信在Dump + gzip方案上再加以优化,由于格式化SQL语句输出耗时较长,因此使用了自定义 的二进制格式承载Dump输出。第二耗时的压缩操作则放到别的线程同时进行,在双核以上的环境 基本可以做到无额外时间消耗。由于数据保密需要,二进制Dump数据也做了加密处理。 采用自定义二进制格式还有一个好处是,恢复的时候不需要重复的编译SQL语句,编译一次就可以 插入整个表的数据了,恢复性能也有一定提升。优化后的方案比原始的Dump + 压缩, 每秒备份行数提升了 150%,每秒恢复行数也提升了 40%

(图: 性能优化效果)

即使优化后的方案,对于特大DB备份也是耗时耗电,对于移动APP来说,可能未必有这样的机会 做这样重度的操作,或者频繁备份会导致卡顿,这也是需要开发者衡量的。比如Android微信会 选择在 充电并灭屏 时进行DB备份,若备份过程中退出以上状态,备份会中止,等待下次机会。

备份方案上线后,恢复成功率达到72%,但有部分重度用户DB损坏时,由于备份耗时太久, 始终没有成功,而对DB数据丢失更为敏感的也恰恰是这些用户,于是新方案应运而生。

解析B-tree恢复方案(RepairKit)

备份方案的高消耗迫使我们从另外的方案考虑,于是我们再次把注意力放在之前的Dump方案。 Dump 方案本质上是尝试从坏DB里读出信息,这个尝试一般来说会出现两种结果:

  • DB的基本格式仍然健在,但个别数据损坏,读到损坏的地方SQLite返回 SQLITE_CORRUPT错误, 但已读到的数据得以恢复。
  • 基本格式丢失(文件头或sqlite_master损坏),获取有哪些表的时候就返回SQLITE_CORRUPT, 根本没法恢复。

第一种可以算是预期行为,毕竟没有损坏的数据能 部分恢复。从之前的数据看, 不少用户遇到的是第二种情况,这种有没挽救的余地呢?

要回答这个问题,先得搞清楚sqlite_master是什么。它是一个每个SQLite DB都有的特殊的表, 无论是查看官方文档Database File Format,还是执行SQL语句 SELECT * FROM sqlite_master;,都可得知这个系统表保存以下信息: 表名、类型(table/index)、 创建此表/索引的SQL语句,以及表的RootPage。sqlite_master的表名、表结构都是固定的, 由文件格式定义,RootPage 固定为 page 1。

(图:sqlite_master表)

正常情况下,SQLite 引擎打开DB后首次使用,需要先遍历sqlite_master,并将里面保存的SQL语句再解析一遍, 保存在内存中供后续编译SQL语句时使用。假如sqlite_master损坏了无法解析,“Dump恢复”这种走正常SQLite 流程的方法,自然会卡在第一步了。为了让sqlite_master受损的DB也能打开,需要想办法绕过SQLite引擎的逻辑。 由于SQLite引擎初始化逻辑比较复杂,为了避免副作用,没有采用hack的方式复用其逻辑,而是决定仿造一个只可以 读取数据的最小化系统。

虽然仿造最小化系统可以跳过很多正确性校验,但sqlite_master里保存的信息对恢复来说也是十分重要的, 特别是RootPage,因为它是表对应的B-tree结构的根节点所在地,没有了它我们甚至不知道从哪里开始解析对应的表。

sqlite_master信息量比较小,而且只有改变了表结构的时候(例如执行了CREATE TABLEALTER TABLE 等语句)才会改变,因此对它进行备份成本是非常低的,一般手机典型只需要几毫秒到数十毫秒即可完成,一致性也容易保证, 只需要执行了上述语句的时候重新备份一次即可。有了备份,我们的逻辑可以在读取DB自带的sqlite_master失败的时候 使用备份的信息来代替。

DB初始化的问题除了文件头和sqlite_master完整性外,还有加密。SQLCipher加密数据库,对应的恢复逻辑还需要加上 解密逻辑。按照SQLCipher的实现,加密DB 是按page 进行包括头部的完整加密,所用的密钥是根据用户输入的原始密码和 创建DB 时随机生成的 salt 运算后得出的。可以猜想得到,如果保存salt错了,将没有办法得出之前加密用的密钥, 导致所有page都无法读出了。由于salt 是创建DB时随机生成,后续不再修改,将它纳入到备份的范围内即可。

到此,初始化必须的数据就保证了,可以仿造读取逻辑了。我们常规使用的读取DB的方法(包括dump方式恢复), 都是通过执行SQL语句实现的,这牵涉到SQLite系统最复杂的子系统——SQL执行引擎。我们的恢复任务只需要遍历B-tree所有节点, 读出数据即可完成,不需要复杂的查询逻辑,因此最复杂的SQL引擎可以省略。同时,因为我们的系统是只读的, 写入恢复数据到新 DB 只要直接调用 SQLite 接口即可,因而可以省略同样比较复杂的B-tree平衡、Journal和同步等逻辑。 最后恢复用的最小系统只需要:

  • VFS读取部分的接口(Open/Read/Close),或者直接用stdio的fopen/fread、Posix的open/read也可以
  • SQLCipher的解密逻辑
  • B-tree解析逻辑

即可实现。

(图:最小化系统)

Database File Format 详细描述了SQLite文件格式, 参照之实现B-tree解析可读取 SQLite DB。加密 SQLCipher 情况较为复杂,幸好SQLCipher 加密部分可以单独抽出,直接套用其解密逻辑。

实现了上面的逻辑,就能读出DB的数据进行恢复了,但还有一个小插曲。我们知道,使用SQLite查询一个表, 每一行的列数都是一致的,这是Schema层面保证的。但是在Schema的下面一层——B-tree层,没有这个保证。 B-tree的每一行(或者说每个entry、每个record)可以有不同的列数,一般来说,SQLite插入一行时, B-tree里面的列数和实际表的列数是一致的。但是当对一个表进行了ALTER TABLE ADD COLUMN操作, 整个表都增加了一列,但已经存在的B-tree行实际上没有做改动,还是维持原来的列数。 当SQLite查询到ALTER TABLE前的行,缺少的列会自动用默认值补全。恢复的时候,也需要做同样的判断和支持, 否则会出现缺列而无法插入到新的DB。

解析B-tree方案上线后,成功率约为78%。这个成功率计算方法为恢复成功的 Page 数除以总 Page 数。 由于是我们自己的系统,可以得知总 Page 数,使用恢复 Page 数比例的计算方法比人数更能反映真实情况。 B-tree解析好处是准备成本较低,不需要经常更新备份,对大部分表比较少的应用备份开销也小到几乎可以忽略, 成功恢复后能还原损坏时最新的数据,不受备份时限影响。 坏处是,和Dump一样,如果损坏到表的中间部分,比如非叶子节点,将导致后续数据无法读出。

不同方案的组合

由于解析B-tree恢复原理和备份恢复不同,失败场景也有差别,可以两种手段混合使用覆盖更多损坏场景。 微信的数据库中,有部分数据是临时或者可从服务端拉取的,这部分数据可以选择不修复,有些数据是不可恢复或者 恢复成本高的,就需要修复了。

如果修复过程一路都是成功的,那无疑使用B-tree解析修复效果要好于备份恢复。备份恢复由于存在 时效性,总有部分最新的记录会丢掉,解析修复由于直接基于损坏DB来操作,不存在时效性问题。 假如损坏部分位于不需要修复的部分,解析修复有可能不发生任何错误而完成。

若修复过程遇到错误,则很可能是需要修复的B-tree损坏了,这会导致需要修复的表发生部分或全部缺失。 这个时候再使用备份修复,能挽救一些缺失的部分。

最早的Dump修复,场景已经基本被B-tree解析修复覆盖了,若B-tree修复不成功,Dump恢复也很有可能不会成功。 即便如此,假如上面的所有尝试都失败,最后还是会尝试Dump恢复。

(图: 恢复方案组合)

上面说的三种修复方法,原理上只涉及到SQLite文件格式以及基本的文件系统,是跨平台的。 实际操作上,各个平台可以利用各自的特性做策略上的调整,比如 Android 系统使用 JobScheduler 在充电灭屏状态下备份。

我们的组件

WCDB - WeChat Database,微信的移动数据库组件,包含上面几种修复方案, 以及加密、连接池并发、ORM、性能优化等特性,将在近日开源,欢迎关注。

更多信息请看往期文章:

《微信移动端数据库组件WCDB系列(一)-iOS基础篇》

《微信 SQLite 数据库修复实践》


如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯Bugly 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
微信移动端数据库组件 WCDB 系列:数据库修复三板斧(二)
微信终端开发团队
2017/07/21
4.4K0
微信移动端数据库组件 WCDB 系列:数据库修复三板斧(二)
只看这一篇,你能够完全 get 微信移动端数据库 WCDB 的一切!
该文介绍了微信移动端数据库WCDB的代码开源、特性、性能优化、移动端数据库方案、SQLite、WCDB原理、版本变更等信息。
腾讯开源
2017/08/31
2.8K0
微信 WCDB 进化之路 - 开源与开始
本文来自:微信移动客户端开发团队公众号(WeMobileDev) 我们来了 ---- 今天,WCDB(WeChat Database)通过了公司的最终审核,作为腾讯微信的一个开源组件分享给大家。 从WCDB初建,到不断摸索、优化,再到整理代码、文档,最终看着她在 GitHub 上静静等待着“Make Public”被按下,心情犹如看着女儿出嫁的父亲。趁此机会,正好回顾一下 WCDB 这个“微信的数据库”的成长,分享我们的心路历程,也希望以此让大家更了解WCDB。 各自探索 ---- 最早期的微信,各个平台
腾讯Bugly
2023/04/23
1.8K0
微信 WCDB 进化之路 - 开源与开始
五年沉淀,微信全平台终端数据库WCDB迎来重大升级!
WCDB 是微信团队开源的一款基于 SQLite 的终端数据库。自 2017 年 6 月开源以来,它在业界得到了广泛认可并被大量应用,迄今已经推出了十多个版本。在这个过程中,WCDB 一直保持良好的向后兼容性,不断完善原有接口的细节并添加新功能。
微信终端开发团队
2024/04/09
1.8K1
五年沉淀,微信全平台终端数据库WCDB迎来重大升级!
微信 WCDB 进化之路:开源与开始
微信终端开发团队
2017/07/26
6K0
微信 WCDB 进化之路:开源与开始
微信移动端数据库组件WCDB系列(一)-iOS基础篇
本文来自:微信移动客户端开发团队公众号(WeMobileDev) 前言 ---- WCDB(WeChat DataBase)是微信官方的移动端数据库组件,致力于提供一个高效、易用、完整的移动端存储方案。 它包含三个模块: WCDB-iOS/Mac WCDB-Android 数据库损坏修复工具WCDBRepair 目前正在筹备开源中。 背景 ---- 对于iOS开发者来说,数据库的技术选型一直是个令人头痛的问题。 由于Apple提供的CoreData框架差强人意,使得开发者们纷纷将目光投向开源社区,寻找更好
腾讯Bugly
2023/04/02
2.8K0
微信移动端数据库组件WCDB系列(一)-iOS基础篇
微信团队开源的终端数据库WCDB有什么优势?
今天看到微信团队的一篇文章,说是自家的开源的终端数据库WCDB进行了重大升级 原文章在这里,感兴趣的朋友们可以围观一下:《五年沉淀,微信全平台终端数据库WCDB迎来重大升级》
小小鱼儿小小林
2024/05/25
4680
微信团队开源的终端数据库WCDB有什么优势?
一篇文章get微信开源移动端数据库组件WCDB的一切!
微信团队已于2017年06月09日正式开源了微信自用的移动端数据库组件 WCDB(WeChat Database),详见《[资讯] 微信正式开源移动端数据库组件WCDB!》。
JackJiang
2018/08/23
1.8K1
一篇文章get微信开源移动端数据库组件WCDB的一切!
微信移动端数据库组件 WCDB 系列:Android 特性篇(四)
本文介绍了WCDB在Android端数据库的一些特性,包括分表、事务支持、加密、数据迁移、全文搜索、分词和动态ICU加载,以及日志重定向和性能监控。WCDB还提供了SQLite和WCDB的集成方案,以及优化Cursor实现的方案。
微信终端开发团队
2017/07/25
4.9K0
微信移动端数据库组件 WCDB 系列:Android 特性篇(四)
【Dev Club 分享】微信 iOS SQLite 源码优化实践
Dev Club 是一个交流移动开发技术,结交朋友,扩展人脉的社群,成员都是经过审核的移动开发工程师。每周都会举行嘉宾分享,话题讨论等活动。 本期,我们邀请了腾讯 WXG iOS 开发工程师——张三华,为大家分享《微信 iOS SQLite 源码优化实践》。 分享内容简介: SQLite是微信iOS选用的数据库,随着微信iOS客户端业务的增长,在重度用户的场景下,性能瓶颈逐渐显现。靠单纯地修改SQLite的参数配置,已经不能彻底解决问题,因此我们尝试从源码开始做深入的优化。 内容大体框架: SQLite对于
腾讯Bugly
2018/03/23
1.5K0
微信移动端数据库组件 WCDB 系列:iOS 基础篇(一)
微信终端开发团队
2017/07/20
6.4K0
微信移动端数据库组件 WCDB 系列:iOS 基础篇(一)
Realm、WCDB与SQLite移动数据库性能对比测试
Realm 是由Y Combinator孵化的创业团队开源出来的一款可以用于iOS(同样适用于Swift&Objective-C)和Android的跨平台移动数据库。目前最新版是Realm 2.0.2,支持的平台包括Java,Objective-C,Swift,React Native,Xamarin。
Cloudox
2021/11/23
4.4K0
Realm、WCDB与SQLite移动数据库性能对比测试
Snova运维篇(三):GP数据库备份和恢复
本节主要从gp数据备份和恢复角度深入学习gp数据库。定期执行备份能确保在数据损坏或者系统失效发生时能恢复数据或者重建Greenplum数据库系统。用户还可以使用备份从一个Greenplum数据库系统迁移数据到另一个。
snova-最佳实践
2019/12/26
2.1K0
Snova运维篇(三):GP数据库备份和恢复
Windows微信:消息数据库架构演进
作者:Jon,来自微信客户端团队 前言 本文基于微信用户日常使用场景 & 数据分析,「通过分离重要 / 非重要数据、采用可靠的分库策略等」,对微信数据库架构进行优化 & 改造,并最终得到一个具备实践良好效果的改造方案。 背景说明 微信 for Windows自2014年上线以来,用户数稳步增长。随着时间的不断推移,用户积攒的消息量越来越大。最初的数据库设计秉着「遵循简单易用,方便管理」的原则,把用户收到的所有消息都统一存放在用户当前客户端本地的「同一个数据文件中。」 (注:微信不会保存聊天记录,聊天内容只
微信终端开发团队
2022/08/31
1.8K0
Windows微信:消息数据库架构演进
2020最新版MySQL数据库面试题(三)
select r.*,s.* from r full join s on r.c=s.c
码农编程进阶笔记
2021/07/20
6860
2020最新版MySQL数据库面试题(三)
微信技术团队的又一力作,WCDB 简单易用的数据库框架
WCDB WCDB是一个高效、完整、易用的移动数据库框架,基于SQLCipher,支持iOS, macOS和Android。 WCDB for iOS/macOS 基本功能 WINQ(WCDB语言集成
非著名程序员
2018/02/09
1.8K0
微信技术团队的又一力作,WCDB 简单易用的数据库框架
微信ANDROID客户端-会话速度提升70%的背后
image.png 背景 打开会话速度慢 在同一个会话有较多的历史消息下,各种查询,更新,删除等操作,速度明显下降。 在会话内有较大量历史消息情况下,进入速度/刷新速度明显降低。 分析阶段 整个优化我们分2个阶段进行: 第一阶段,针对历史记录较小的会话 通过Android自带的trace工具分析,我们发现较大的耗时分布在进入会话的几个关键点: 在打开会话过程中涉及的磁盘读写操作 加载会话UI所执行的inflate操作(inflate指的是创建View对象) 退出会话后,列表控件的数据适配器被重置,触发
微信终端开发团队
2018/01/29
3.9K0
微信ANDROID客户端-会话速度提升70%的背后
sqlcipher加密原理_sqlserver数据库加密
http://download.csdn.net/detail/zhanghw0917/7931759
全栈程序员站长
2022/09/20
2.6K0
微信 SQLite 数据库修复实践
微信终端开发团队
2017/08/04
9.4K5
微信 SQLite 数据库修复实践
利用SQLChiper对Android SQLite数据库加密
利用SQLChiper对Android SQLite数据库加密 前言: 上篇文章讲了Android studio+SQLCipher加密SQLite数据库的几个坑,跳过这几个坑,那么SQLCipher的前提就处理完成,本片文章接着讲如何使用SQLCiper加密数据库。
红目香薰
2022/11/29
1K0
推荐阅读
相关推荐
微信移动端数据库组件 WCDB 系列:数据库修复三板斧(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档