Rust高效压缩与快速字段编码库fastfield_codecs的使用,优化列式存储与数据检索性能

Fast Field Codecs

这个crate包含各种快速字段编解码器,用于在tantivy中压缩/解压缩快速字段数据。

贡献

贡献非常简单。由于bitpacking是最最简单的压缩器,您可以参考它。

一个编解码器需要实现2个特性:

  • 实现FastFieldCodecReader的读取器来读取编解码器
  • 实现FastFieldCodecSerializer的序列化器用于压缩估计和编解码器名称+ID

测试

一旦实现了这些特性,测试和基准测试集成就非常简单(参见test_with_codec_data_setsbench.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(())
}

示例说明:

  1. 首先创建一个包含快速字段(u64类型)的schema
  2. 创建内存索引并添加1000个文档,每个文档的ID字段从0到999
  3. 读取索引并获取快速字段
  4. 验证快速字段的值是否正确

这个示例展示了如何在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(())
}

这个扩展示例展示了:

  1. 创建包含两个快速字段的schema (id和value)
  2. value字段使用线性递增的值,适合LinearInterpolCodec编解码器
  3. 同时使用Bitpacked和线性插值两种编解码器
  4. 验证两种编解码器都能正确存储和检索数据

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);

性能优化建议

  1. 批量操作:尽量使用批量操作而非单个值的读写
  2. 选择合适的编码器:根据数据分布特点选择最合适的编码器
  3. 重用缓冲区:在循环中重用解码缓冲区减少分配
  4. 考虑数据局部性:将经常一起访问的数据放在相邻位置

注意事项

  • 数据需要实现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!("所有测试通过!");
}

这个完整示例包含了三种典型的使用场景:

  1. 基本类型编码解码
  2. 大数据集位打包压缩
  3. 组合编码器使用

每个示例都包含了完整的测试断言,可以直接运行验证功能。

回到顶部