6 个版本

0.3.2 2022年7月30日
0.3.1 2022年7月19日
0.2.0 2022年7月17日
0.1.1 2021年1月25日

#934数据库接口

LGPL-2.0-or-later

115KB
2.5K SLoC

sqlite-zstd

为 sqlite 提供透明字典行级压缩的扩展。这基本上允许您以与压缩整个数据库几乎相同的效果压缩 sqlite 数据库中的条目,同时保留随机访问。

根据数据的不同,这可以将数据库的大小减少 90%,同时保持性能基本不变(甚至可能提高,因为要从磁盘读取的数据更小)。

透明压缩

  • zstd_enable_transparent(配置)

    启用给定表上给定列的透明行级压缩。

    您可以在同一表上多次调用此函数,对不同的列进行压缩。

        SELECT
            zstd_enable_transparent('{"table": "objects", "column": "data1", "compression_level": 19, "dict_chooser": "''a''"}'),
            zstd_enable_transparent('{"table": "objects", "column": "data2", "compression_level": 19, "dict_chooser": "''a''"}')
    
    

    数据将被移动到 _table_name_zstd,而 table_name 将是一个可以像通常一样查询的视图,包括 SELECT、INSERT、UPDATE 和 DELETE 查询。此函数本身不会压缩任何数据,您需要在之后调用 zstd_incremental_maintenance

    config 是一个描述配置的 json 对象。有关详细信息,请参阅 TransparentCompressConfig

    当压缩处于活动状态时,以下差异适用

    • 压缩列可能只能包含 blobtext 数据,具体取决于声明的数据类型的亲和力(例如,VARCHAR(10) 是可以的,但 int 则不行)。
    • 对于任何行,主键不得为 null,否则更新可能无法按预期工作
    • sqlite3_changes() 对于修改查询将返回 0(《sqlite.org/c3ref/changes.html》中查看)。
    • 由于 blob 完全复制到内存中,SQLite 流式 blob 读取 API 将变得有些无用。
    • 不支持使用 ATTACH 'foo.db' 将包含压缩表的数据库附加。
    • DDL 语句(如 ALTER TABLE 和 CREATE INDEX)仅部分支持
  • zstd_incremental_maintenance(duration_seconds:float|null,db_load:float) -> bool

    执行大约给定时间量的增量维护操作。这将训练字典并根据TransparentCompressConfig中给出的分组压缩数据。

    duration_seconds:如果给定的时间量为0,则执行单个步骤并尽快退出。如果给定的时间量为null,则运行直到所有待处理的维护完成。

    db_load:指定db在写查询中锁定的时间比例。例如:如果设置为0.5,则每次写操作持续2秒后,维护函数将睡眠2秒,以便其他进程有时间运行针对数据库的写操作。如果设置为1,则维护不会睡眠。注意,这仅在您在其他逻辑之外单独的线程或进程中运行增量维护函数时才有用。注意,持续时间和db负载都是尽力而为的:没有关于数据库锁定时间的确切保证。

    返回 1表示还有工作要做,0表示所有数据都已按预期压缩。

    请注意,此函数每次调用都有相当于select * from table where dictid is null的启动时间成本,因此持续时间越长越高效。

    此函数可以在任何时间安全中断,每个压缩工作块都作为一个原子操作完成。

    示例

    • zstd_incremental_maintenance(null, 1):尽快压缩所有内容。如果数据库当前未使用,则非常有用。
    • zstd_incremental_maintenance(60, 0.5):花费60秒压缩待处理的数据,同时允许其他查询占用50%的时间。

    示例输出

    sqlite> select zstd_incremental_maintenance(null, 1);
      [2020-12-23T21:11:31Z WARN  sqlite_zstd::transparent] Warning: It is recommended to set `pragma busy_timeout=2000;` or higher
      [2020-12-23T21:11:40Z INFO  sqlite_zstd::transparent] events.data: Total 5.20GB to potentially compress.
      3[2020-12-23T21:13:22Z INFO  sqlite_zstd::transparent] Compressed 6730 rows with dictid=109. Total size of entries before: 163.77MB, afterwards: 2.12MB, (average: before=24.33kB, after=315B)
      [2020-12-23T21:13:43Z INFO  sqlite_zstd::transparent] Compressed 4505 rows with dictid=110. Total size of entries before: 69.28MB, afterwards: 1.60MB, (average: before=15.38kB, after=355B)
      [2020-12-23T21:14:06Z INFO  sqlite_zstd::transparent] Compressed 5228 rows with dictid=111. Total size of entries before: 91.97MB, afterwards: 1.41MB, (average: before=17.59kB, after=268B)
    

基本功能

  • zstd_compress(数据:文本|blob,级别:整数= 3,字典:blob|整数|null=null,紧凑: bool = false) ->blob

    以压缩级别(1 - 22,默认3)压缩给定的数据

    • 如果字典是一个blob,它将被直接使用
    • 如果字典是一个整数i,它功能上等同于zstd_compress(data, level, (select dict from _zstd_dict where id = i))
    • 如果字典不存在,为null或-1,则数据将不使用字典进行压缩。

    如果紧凑为true,则输出将不带魔数头,不带校验和,不带dictids。这在不使用字典时可以节省4字节,在使用字典时可以节省8字节。这也意味着数据无法用标准工具解码为正常的zstd存档。必须将相同的紧凑参数传递给解压缩函数。

  • zstd_decompress(数据:blob,is_text: bool,字典:blob|整数|null=null,紧凑: bool = false) ->文本|blob

    解压缩给定的数据。如果字典错误,结果是不确定的

    • 如果字典是一个blob,它将被直接使用
    • 如果字典是一个整数i,它功能上等同于zstd_decompress(data, (select dict from _zstd_dict where id = i))
    • 如果字典不存在,为null或-1,则假定数据未使用字典进行压缩。

    请注意,建议将字典作为整数传递,因为这样字典只需准备一次。

    is_text指定是否将数据以文本形式或以blob形式输出。注意,当以文本形式输出时,编码取决于sqlite数据库的编码。sqlite-zstd仅与UTF-8进行了测试。

    当压缩函数也使用compact调用时,必须指定compact。

  • zstd_train_dict(agg,dict_size:整数,sample_count:整数) ->blob

    聚合函数(如sum()或count())用于在给定聚合数据的sample_count个样本上训练zstd字典

    示例用法:以下代码select zstd_train_dict(tbl.data, 100000, 1000) from tbl将从tbl中返回一个大小为100kB的字典,该字典在1000个样本上进行了训练

    建议的样本数量是目标字典大小的100倍。例如,您可以按以下方式使用“最佳”样本数量来训练一个100kB的字典

    select zstd_train_dict(data, 100000, (select (100000 * 100 / avg(length(data))) as sample_count from tbl))
                    as dict from tbl
    

    请注意,dict_size和sample_count被认为是常数。

  • zstd_train_dict_and_save(agg,dict_size:整数,sample_count:整数) ->整数

    zstd_train_dict相同,但字典将保存到_zstd_dicts表中,并返回id。

编译

本项目可以以两种模式构建:(a)作为Rust库和(b)作为纯SQLite扩展(使用--features build_extension)。

您可以从GitHub发布中获取SQLite扩展的二进制文件。或者,您可以手动构建该扩展

cargo build --release --features build_extension
# should give you target/release/libsqlite_zstd.so

使用方法

您可以将此库作为SQLite扩展或作为Rust库加载。请注意,sqlite扩展不是持久的,因此每次连接到数据库时都需要加载它。

Sqlite CLI

在REPL中加载

$ sqlite3 file.db
SQLite version 3.34.0 2020-12-01 16:14:00
sqlite> .load .../libsqlite_zstd.so
[2020-12-23T21:30:02Z INFO  sqlite_zstd::create_extension] [sqlite-zstd] initialized
sqlite>

或者

sqlite3-cmd'.加载libsqlite_zstd.so' '选择*从foo'

C API

int success = sqlite3_load_extension(db, "libsqlite_zstd.so", NULL, NULL);

更多信息请参阅此处

Rust

建议的方法是将sqlite_zstd添加到您的项目中作为依赖项,然后使用以下方式加载它

let conn: rusqlite::Connection;
sqlite_zstd::load(&conn)?;

或者,您也可以像加载其他扩展一样加载扩展

let conn: rusqlite::Connection;
conn.load_extension("libsqlite_zstd.so", None)?;

更多信息请参阅此处

详细程度/调试

您可以通过设置环境变量SQLITE_ZSTD_LOG=error来减少日志记录,对于更多日志记录,请使用SQLITE_ZSTD_LOG=debug

未来工作/想法/待办事项

待办事项:描述

  • 修复关于全局字典缓存的多个打开连接问题
  • 数据使用统计
  • 性能影响
  • 它是如何工作的
  • 调查没有字典的启动成本
  • 正确处理压缩列上的索引
  • 为了性能,在多个线程中执行压缩(例如,使用.zstd(1))
  • 类型亲和力干扰整数传递 - insert into compressed (col) values (1)将导致typeof(col) = text而不是整数,如果列的类型声明为text - 这反过来会导致解压缩失败,错误信息为“获得了字符串,但zstd压缩数据始终是blob”
    • 或者更改压缩列的类型为blob或类似类型,或者禁止整数传递

select zstd_enable_transparent('{"table": "events", "column": "data", "compression_level": 19, "dict_chooser": "case when date(timestamp, ''weekday 0'') < date(''now'', ''weekday 0'') then data_type || ''.'' || date(timestamp, ''weekday 0'') else null end"}');

select case when date(timestamp, 'weekday 0') < date('now', 'weekday 0') then data_type || '.' || date(timestamp, 'weekday 0') else null END from events limit 10000

依赖项

~18-30MB
~491K SLoC