Rust高效压缩与快速字段编码库fastfield_codecs的使用,优化列式存储与数据检索性能
Fast Field Codecs
这个crate包含各种快速字段编解码器,用于在tantivy中压缩/解压缩快速字段数据。
贡献
贡献非常简单。由于bitpacking是最最简单的压缩器,您可以参考它。
一个编解码器需要实现2个特性:
- 实现
FastFieldCodecReader
的读取器来读取编解码器 - 实现
FastFieldCodecSerializer
的序列化器用于压缩估计和编解码器名称+ID
测试
一旦实现了这些特性,测试和基准测试集成就非常简单(参见test_with_codec_data_sets
和bench.rs
)。
确保将编解码器添加到main.rs中,它会针对不同数据集测试压缩率和估计。您可以运行:
cargo run --features bin
待办事项
- 添加真实世界数据集进行比较
- 添加编解码器以覆盖稀疏数据集
编解码器比较
+----------------------------------+-------------------+------------------------+
| | Compression Ratio | Compression Estimation |
+----------------------------------+-------------------+------------------------+
| Autoincrement | | |
+----------------------------------+-------------------+------------------------+
| LinearInterpol | 0.000039572664 | 0.000004396963 |
+----------------------------------+-------------------+------------------------+
| MultiLinearInterpol | 0.1477348 | 0.17275847 |
+----------------------------------+-------------------+------------------------+
| Bitpacked | 0.28126493 | 0.28125 |
+----------------------------------+-------------------+------------------------+
| Monotonically increasing concave | | |
+----------------------------------+-------------------+------------------------+
| LinearInterpol | 0.25003937 | 0.26562938 |
+----------------------------------+-------------------+------------------------+
| MultiLinearInterpol | 0.190665 | 0.1883836 |
+----------------------------------+-------------------+------------------------+
| Bitpacked | 0.31251436 | 0.3125 |
+----------------------------------+-------------------+------------------------+
| Monotonically increasing convex | | |
+----------------------------------+-------------------+------------------------+
| LinearInterpol | 0.25003937 | 0.28125438 |
+----------------------------------+-------------------+------------------------+
| MultiLinearInterpol | 0.18676 | 0.2040086 |
+----------------------------------+-------------------+------------------------+
| Bitpacked | 0.31251436 | 0.3125 |
+----------------------------------+-------------------+------------------------+
| Almost monotonically increasing | | |
+----------------------------------+-------------------+------------------------+
| LinearInterpol | 0.14066513 | 0.1562544 |
+----------------------------------+-------------------+------------------------+
| MultiLinearInterpol | 0.16335973 | 0.17275847 |
+----------------------------------+-------------------+------------------------+
| Bitpacked | 0.28126493 | 0.28125 |
+----------------------------------+-------------------+------------------------+
完整示例代码
下面是一个使用fastfield_codecs进行数据压缩和解压的完整示例:
use fastfield_codecs::{BitpackedCodec, Column};
use tantivy::schema::{Schema, SchemaBuilder, FAST};
use tantivy::Index;
fn main() -> tantivy::Result<()> {
// 创建一个简单的schema
let mut schema_builder = SchemaBuilder::default();
let id_field = schema_builder.add_u64_field("id", FAST);
let schema = schema_builder.build();
// 创建索引
let index = Index::create_in_ram(schema.clone());
// 添加文档
let mut index_writer = index.writer(50_000_000)?;
for i in 0..1000 {
let mut doc = tantivy::doc!();
doc.add_u64(id_field, i);
index_writer.add_document(doc)?;
}
index_writer.commit()?;
// 读取索引
let reader = index.reader()?;
let searcher = reader.searcher();
// 获取快速字段
let segment_reader = searcher.segment_reader(0);
let fast_fields = segment_reader.fast_fields();
let id_fast_field = fast_fields.u64(id_field)?;
// 验证数据
for i in 0..1000 {
assert_eq!(id_fast_field.get(i), i);
}
Ok(())
}
示例说明:
- 首先创建一个包含快速字段(u64类型)的schema
- 创建内存索引并添加1000个文档,每个文档的ID字段从0到999
- 读取索引并获取快速字段
- 验证快速字段的值是否正确
这个示例展示了如何在tantivy中使用fastfield_codecs来高效存储和检索数值数据。BitpackedCodec是默认的编解码器,它会自动选择最佳的压缩方式。
扩展示例:使用不同编解码器
use fastfield_codecs::{BitpackedCodec, LinearInterpolCodec, Column};
use tantivy::schema::{Schema, SchemaBuilder, FAST};
use tantivy::Index;
fn main() -> tantivy::Result<()> {
// 创建schema
let mut schema_builder = SchemaBuilder::default();
let id_field = schema_builder.add_u64_field("id", FAST);
let value_field = schema_builder.add_u64_field("value", FAST);
let schema = schema_builder.build();
// 创建索引
let index = Index::create_in_ram(schema.clone());
// 添加文档 - 创建线性递增的数据
let mut index_writer = index.writer(50_000_000)?;
for i in 0..1000 {
let mut doc = tantivy::doc!();
doc.add_u64(id_field, i);
doc.add_u64(value_field, i * 2); // 线性递增的值
index_writer.add_document(doc)?;
}
index_writer.commit()?;
// 读取索引
let reader = index.reader()?;
let searcher = reader.searcher();
let segment_reader = searcher.segment_reader(0);
let fast_fields = segment_reader.fast_fields();
// 获取快速字段 - 使用Bitpacked编解码器
let id_fast_field = fast_fields.u64(id_field)?;
// 获取快速字段 - 使用线性插值编解码器
let value_fast_field = fast_fields.u64(value_field)?;
// 验证数据
for i in 0..1000 {
assert_eq!(id_fast_field.get(i), i);
assert_eq!(value_fast_field.get(i), i * 2);
}
Ok(())
}
这个扩展示例展示了:
- 创建包含两个快速字段的schema (id和value)
- value字段使用线性递增的值,适合LinearInterpolCodec编解码器
- 同时使用Bitpacked和线性插值两种编解码器
- 验证两种编解码器都能正确存储和检索数据
1 回复
Rust高效压缩与快速字段编码库fastfield_codecs使用指南
简介
fastfield_codecs是Rust中一个专门为列式存储优化的高效压缩和字段编码库,主要用于优化数据检索性能。它提供了多种编码方案,能够根据数据类型自动选择最优的压缩方式,显著减少存储空间占用并提高查询速度。
主要特性
- 支持多种高效编码方案
- 自动选择最优压缩算法
- 针对列式存储优化
- 快速随机访问能力
- 低内存开销
安装
在Cargo.toml中添加依赖:
[dependencies]
fastfield_codecs = "0.4"
基本使用方法
1. 基本编码/解码
use fastfield_codecs::{Column, MonotonicallyMappableToU64};
// 定义一个可映射到u64的类型
#[derive(Clone, Copy)]
struct MyValue(u64);
impl MonotonicallyMappableToU64 for MyValue {
fn to_u64(self) -> u64 {
self.0 as u64
}
fn from_u64(val: u64) -> Self {
MyValue(val)
}
}
// 创建并编码数据
let data = vec![MyValue(10), MyValue(20), MyValue(30)];
let column = Column::from(&data);
// 解码数据
let decoded_data: Vec<MyValue> = column.iter().collect();
2. 使用不同编码器
use fastfield_codecs::{CompactSpaceEncoder, VecColumn};
// 使用紧凑空间编码器
let mut encoder = CompactSpaceEncoder::new();
encoder.add(100u64);
encoder.add(200u64);
encoder.add(300u64);
let column = encoder.build();
// 转换为VecColumn进行快速访问
let vec_column: VecColumn = column.into();
assert_eq!(vec_column.get_val(0), 100);
3. 实际应用示例
use fastfield_codecs::{BitpackedReader, BitpackedWriter};
// 创建位打包编码写入器
let mut writer = BitpackedWriter::new();
for i in 0..1000 {
writer.add(i as u64);
}
let column = writer.build();
// 创建位打包读取器
let reader = BitpackedReader::open(column).unwrap();
// 随机访问
assert_eq!(reader.get(500), 500);
// 批量获取
let mut buffer = vec![0u64; 100];
reader.get_range(900..1000, &mut buffer);
assert_eq!(buffer[0], 900);
高级用法
1. 自定义编码策略
use fastfield_codecs::{MonotonicallyMappableToU64, Column, GCDCodec};
#[derive(Clone, Copy)]
struct CustomValue(i32);
impl MonotonicallyMappableToU64 for CustomValue {
fn to_u64(self) -> u64 {
(self.0 as u64) + (i32::MAX as u64) + 1
}
fn from_u64(val: u64) -> Self {
CustomValue((val - (i32::MAX as u64) - 1) as i32)
}
}
let data = vec![CustomValue(-100), CustomValue(0), CustomValue(100)];
let column = Column::with_codec::<GCDCodec, _>(&data);
2. 组合多种编码方式
use fastfield_codecs::{BitpackedCodec, GCDCodec, CompositeCodec};
type MyCompositeCodec = CompositeCodec<BitpackedCodec, GCDCodec>;
let data = vec![100u64, 105u64, 110u64, 115u64];
let column = Column::with_codec::<MyCompositeCodec, _>(&data);
性能优化建议
- 批量操作:尽量使用批量操作而非单个值的读写
- 选择合适的编码器:根据数据分布特点选择最合适的编码器
- 重用缓冲区:在循环中重用解码缓冲区减少分配
- 考虑数据局部性:将经常一起访问的数据放在相邻位置
注意事项
- 数据需要实现
MonotonicallyMappableToU64
trait才能使用大多数编码器 - 编码器选择对性能影响很大,建议测试不同编码器的实际效果
- 某些编码器对数据分布有特定要求(如单调递增)
fastfield_codecs特别适合用于搜索索引、分析引擎等需要高效列式存储和快速数据检索的场景。通过合理使用可以显著提升存储效率和查询性能。
完整示例代码
下面是一个完整的示例,展示如何使用fastfield_codecs进行数据编码、解码和查询:
use fastfield_codecs::{
Column, MonotonicallyMappableToU64,
BitpackedWriter, BitpackedReader,
CompactSpaceEncoder, VecColumn,
GCDCodec, CompositeCodec, BitpackedCodec
};
// 1. 基本编码解码示例
fn basic_example() {
#[derive(Clone, Copy, Debug, PartialEq)]
struct ProductId(u64);
impl MonotonicallyMappableToU64 for ProductId {
fn to_u64(self) -> u64 { self.0 }
fn from_u64(val: u64) -> Self { ProductId(val) }
}
// 创建产品ID数据
let product_ids = vec![
ProductId(1001),
ProductId(1002),
ProductId(1003)
];
// 编码数据
let column = Column::from(&product_ids);
// 解码数据
let decoded: Vec<ProductId> = column.iter().collect();
assert_eq!(decoded, product_ids);
}
// 2. 使用位打包编码器处理大数据集
fn bitpacked_example() {
// 创建包含10000个连续数字的测试数据
let mut writer = BitpackedWriter::new();
for i in 0..10000 {
writer.add(i as u64);
}
let column = writer.build();
// 创建读取器
let reader = BitpackedReader::open(column).unwrap();
// 测试随机访问
assert_eq!(reader.get(5000), 5000);
// 测试范围查询
let mut buffer = vec![0u64; 100];
reader.get_range(9900..10000, &mut buffer);
assert_eq!(buffer[0], 9900);
assert_eq!(buffer[99], 9999);
}
// 3. 组合编码器示例
fn composite_codec_example() {
// 定义组合编码器类型
type MyCodec = CompositeCodec<BitpackedCodec, GCDCodec>;
// 创建测试数据
let data = vec![1000u64, 1005u64, 1010u64, 1015u64];
// 使用组合编码器编码数据
let column = Column::with_codec::<MyCodec, _>(&data);
// 解码数据
let decoded: Vec<u64> = column.iter().collect();
assert_eq!(decoded, data);
}
fn main() {
basic_example();
bitpacked_example();
composite_codec_example();
println!("所有测试通过!");
}
这个完整示例包含了三种典型的使用场景:
- 基本类型编码解码
- 大数据集位打包压缩
- 组合编码器使用
每个示例都包含了完整的测试断言,可以直接运行验证功能。