Skip to content

Java|Kotlin 数据压缩

qiuwenchen edited this page Mar 7, 2024 · 1 revision

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

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

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

数据压缩能力

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

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

database.setCompression(new Database.CompressionFilter() {
    @Override
    public void filterCompress(Database.CompressionInfo info) {
        // 数据库中的每个表都会回调,先判断表名
        if(info.table.equals("sampleTable")){
            // 1. 使用Zstd的默认压缩方式来压缩表中的 content 字段
            info.addZSTDNormalCompress(DBSample.content);
            
            // 2. 配置使用注册的字典来压缩 content 字段
            // 字典需要使用 trainDictWithString 或 trainDictWithData 来训练,并使用 registerDict 方法来提前注册
            info.addZSTDDictCompress(DBSample.content, dictId);
            
            // 3. 配置使用多种字典来压缩 content 字段
            // 其中 id 字段为 0 时使用 dictId1 对应的字典,为 1 时使用 dictId2,其他情况使用 dictId3。
            info.addZSTDMultiDictCompress(DBSample.content, DBSample.id, new HashMap<long, byte>(){{
                put(0, dictId1);
                put(1, dictId2);
                put(Database.DictDefaultMatchValue, dictId3);
            }});
        }
    }
});

以上代码都可以用 Kotlin 实现,因为篇幅限制,就不单独演示了,下同。

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

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

assert !database.isCompressed();
final String[] compressedTable = {null};
database.setNotificationWhenCompressed(new Database.CompressionNotification() {
    @Override
    // 每压缩完一个存量表格都会回调
    // 当前数据库的全部存量表格都压缩完时还会额外回调一次,这次的tableName参数就会为null或空字符串
    public void onCompressed(Database database, String tableName) {
        if(tableName != null && !tableName.empty()){
          compressedTable[0] = tableName;
        }
    }
});

while(!database.isCompressed()) {
    database.stepCompression();
}

assert compressedTable[0].equals("sampleTable");

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

Clone this wiki locally