Skip to content

C++ 数据压缩

qiuwenchen edited this page Mar 31, 2024 · 2 revisions

用户使用应用的时间越多,其相关的数据库一般也越大。过大的数据库不仅会占用磁盘空间,而且还会给数据备份和读写等操作带来性能压力。终端数据库的内容中,占用空间大的主要是各种xml、json之类的序列化内容。我们在实际的业务场景中,一个xml长达10k的情况比比皆是。

解决数据库内容过大的直接方法,就是先将数据压缩一下再写入数据库,这样开发者就需要解决下面三个问题:

  • 选择一个合适的压缩算法。
  • 在所有读写环节都需要引入数据的加解压逻辑。
  • 压缩存量数据。

数据压缩能力

为了解决上面提到的数据压缩时会遇到的共性问题,WCDB引入了数据压缩能力。开发者可以使用简单的配置就可以实现数据压缩了。

首先 WCDB 使用的压缩组件是 Zstd,Zstd采用的压缩算法是[ANS+FSE][]算法,是现在已知压缩率和加解压性能综合最优的算法,而且Zstd还实现了字典压缩模式,可以更好得压缩xml或json中公共标签这部分内容,进一步提高压缩率和性能。WCDB把Zstd的普通压缩和字段压缩都支持了,还支持根据某个字段的不同值使用不同的字典来压缩。下面是不同模式的配置示例:

database.setCompression([](WCDB::Database::CompressionInfo &info){
    // 数据库中的每个表都会回调,先判断表名
    if(!info.getTableName().equal("sampleTable")){
        return;
    }
    
    // 1. 使用Zstd的默认压缩方式来压缩表中的 content 字段
    info.addZSTDNormalCompressField(WCDB_FIELD(Sample::content));
    
    // 2. 配置使用注册的字典来压缩 content 字段
    // 字典需要使用 Database::trainDict 函数来训练,并使用 Database::registerZSTDDict 函数来提前注册
    info.addZSTDDictCompressField(WCDB_FIELD(Sample::content), dictId);
    
    // 3. 配置使用多种字典来压缩 content 字段
    // 其中 identifier 字段为 0 时使用 dictId1 对应的字典,为 1 时使用 dictId2,其他情况使用 dictId3。
    info.addZSTDDictCompressField(WCDB_FIELD(Sample::content), WCDB_FIELD(Sample::identifier), {
        {0, dictId1},
        {1, dictId2},
        {WCDB::Database::DictDefaultMatchValue, dictId3}
    });
});

配置好之后,开发就不用关注数据加解压的实现细节了,可以照常使用这些配置了压缩的表。WCDB会自动把新写入的数据按照配置的压缩方式来压缩,读数据的时候也会自动把数据解压了之后在给出来,对原有的读写逻辑无侵入。

如果WCDB是通过Cocoapod安装的,开发者还需要在编译WCDB时配置WCDB_ZSTD=1,并且在项目中编译链接Zstd,才能正常使用数据压缩能力。

对于存量数据,开发者如果也要压缩,可以使用Database::enableAutoCompression函数来开启 WCDB 自动压缩逻辑逻辑。WCDB 会每隔 2 秒压缩 100 条存量数据,而且这个处理通过锁监控机制,会避免影响到数据库的写性能。开发者也可以使用Database::stepCompression函数手动压缩存量数据,自己控制数据压缩的节奏。可以使用Database::setNotificationWhenCompressed()接口注册压缩进度的监听,每次迁移完一个存量表格都会回调。下面是压缩过程的使用示例:

assert(!database.isCompressed());
    
WCDB::StringView compressedTable;
database.setNotificationWhenCompressed([&](WCDB::Database &database, WCDB::Optional<WCDB::StringView> tableName){
  	// 每压缩完一个存量表格都会回调
    // 当前数据库的全部存量表格都压缩完时还会额外回调一次,这次的tableName参数就会为null或空字符串
    if(tableName.hasValue() && !tableName.value().empty()){
        compressedTable = tableName.value();
    }
});
    
while (!database.isCompressed()) {
    database.stepCompression();
}

assert(compressedTable.equal("sampleTable"));

同时,数据压缩可以和数据迁移可以一起配置到同一个表的,两个操作将会独立生效,互不干扰,可以实现一边迁表或拆表,一边压缩目标表的效果,这些复杂玩法就交给开发者去探索了。

Clone this wiki locally