内联的力量:在 Rust 中使时间序列数据库缩小 2000 倍 | 博客 | Guillaume Endignoux

这是一篇关于在 Rust 中使用实习(interning)模式压缩巴黎公共交通网络开放数据的博客文章,主要内容总结如下:

  • 背景与目标:作者浏览巴黎公共交通网络的开放数据仓库,发现 RATP 状态网站利用该数据提供了历史中断的可视化界面。数据存储约 10GB,作者希望通过实习模式压缩数据,减少存储空间。
  • 导入数据(Importing the data):使用 Rust 的serde库将 JSON 数据导入程序,定义数据模式为 Rust 结构体和枚举,并实现反序列化。通过std::mem::size_of()函数估计对象在内存中的占用空间,发现导入 2024 年 5 月的文件使内存使用量增加了 35%。
  • 实习模式(Interning)

    • 字符串实习(Strings):受matklad的博客启发,作者实现了一个字符串实习器,将重复的字符串存储为索引,以减少内存使用。通过将字符串包装在Rc中避免内存重复,并解决了引用与内部可变性的冲突。实验表明,字符串实习使内存使用量减少了约 54%。
    • 任意类型实习(Arbitrary types):将实习技术扩展到任意实现EqHash trait 的类型,为不同类型创建各自的实习器,并在数据结构中使用实习后的类型。这使得内存使用量进一步减少约 87%。
    • 删除引用(Dropping the reference):简化实习对象的设计,去除对实习器的引用,通过比较索引实现比较和哈希操作。将实习索引从usize改为u32,使内存使用量减少约 69%。
  • 调整数据模式(Tuning the schema)

    • 排序集合(Sorting sets):由于 Rust 的Vec集合类型是有序的,而某些集合中的元素顺序无关紧要,导致内存使用增加。通过手动实现PartialOrdOrd trait 对集合进行排序,减少了内存使用约 67%。
    • 使用枚举(Using enums):将Data根结构体中的可选字段改为枚举类型,减少了内存使用约 4%。
    • 拆分结构体(Splitting structs):将ImpactedObject结构体中的固定字段和变化字段分离,创建新的Object结构体,并对其进行实习,减少了内存使用约 2%。
    • 专门化类型(Specializing types):对于字符串实习器中的不同类型,如 UUID 和时间戳,使用更紧凑的表示形式,进一步减少了内存使用约 28%。
  • 序列化(Serialization)

    • 使用 Serde 编写自定义序列化器(Writing custom (de)serializers with Serde):为实习器类型编写自定义的序列化和反序列化函数,以更高效地处理实习数据。比较不同序列化格式的编码大小和时间,发现 Postcard 格式在压缩后最小。
    • 压缩与处理 Rust 借用检查器(Compression and fighting the Rust borrow checker):通过线程处理压缩过程,避免 Linux 管道的死锁问题。压缩后,不同格式的文件大小差异减小,但更优化的格式在序列化和反序列化速度上更快。
    • 元组编码(Tuple encoding):使用serde_tuple crate 将结构体序列化为元组,减少了序列化后的数据大小,尤其是对于包含PhantomData的结构体。
    • 重新优化集合(Optimizing sets revisited):对集合进行差量编码和行程长度编码,减少了序列化后的数据大小,尤其是对于连续的 ID 序列。
  • 最终结果:轻量级追加数据库(Final result: a lightweight append-only database):通过实习和各种优化技术,将 1.1GB 的 JSON 数据压缩到 530KB 左右,实现了约 2000 倍的大小减少。文章还讨论了实习模式的应用和注意事项,以及如何根据具体需求选择是否使用现有的实习库或自行实现。

总的来说,实习模式在压缩 Rust 中的数据方面非常有效,可以显著减少内存使用和文件大小。通过对不同数据类型的实习和各种优化技术的组合,可以实现更高效的数据存储和处理。

阅读 8
0 条评论