#key-value #key-value-store #immutability #read-write #sorting #pair #merge

grenad

排序、合并、写入和读取不可变键值对的工具

12个版本

0.4.7 2024年7月2日
0.4.5 2023年11月1日
0.4.4 2022年11月16日
0.4.2 2022年6月1日
0.2.0 2021年7月28日

#68 in 压缩

Download history 2018/week @ 2024-04-27 2192/week @ 2024-05-04 2050/week @ 2024-05-11 2043/week @ 2024-05-18 1834/week @ 2024-05-25 2016/week @ 2024-06-01 1832/week @ 2024-06-08 2107/week @ 2024-06-15 2244/week @ 2024-06-22 2126/week @ 2024-06-29 1265/week @ 2024-07-06 1098/week @ 2024-07-13 1026/week @ 2024-07-20 1073/week @ 2024-07-27 835/week @ 2024-08-03 782/week @ 2024-08-10

3,846次每月下载
grenad中使用

MIT许可协议

1MB
3K SLoC

Grenad

License Crates.io Docs dependency status Build

排序、合并、写入和读取不可变键值对的工具。


lib.rs:

这个库提供了排序、合并、写入和读取不可变键值对的工具。grenad文件中的条目是不可变的,唯一修改它们的方式是创建一个包含更改的新文件

功能

您可以选择支持哪些压缩方案,目前有几种可供选择,这些决定了上面模块中可用的类型

如果您需要更好的性能,可以启用 rayon 功能,这将启用一系列新设置,例如使 Sorter 能够并行排序。

示例

使用 WriterReader 结构体

您可以使用 Writer 结构体将键值对存储到指定的 std::io::Write 类型。然后可以使用 Reader 类型来读取条目。

提供给 Writer 结构体的条目必须以字典序给出。

use std::io::Cursor;

use grenad::{Reader, Writer};

let mut writer = Writer::memory();

// We insert our key-value pairs in lexicographic order.
writer.insert("first-counter", 119_u32.to_ne_bytes())?;
writer.insert("second-counter", 384_u32.to_ne_bytes())?;

// We create a reader from our writer.
let cursor = writer.into_inner().map(Cursor::new)?;
let mut cursor = Reader::new(cursor)?.into_cursor()?;

// We can see that the sum of u32s is valid here.
assert_eq!(cursor.move_on_next()?, Some((&b"first-counter"[..], &119_u32.to_ne_bytes()[..])));
assert_eq!(cursor.move_on_next()?, Some((&b"second-counter"[..], &384_u32.to_ne_bytes()[..])));
assert_eq!(cursor.move_on_next()?, None);

// We can also jum on any given entry.
assert_eq!(cursor.move_on_key_greater_than_or_equal_to("first")?, Some((&b"first-counter"[..], &119_u32.to_ne_bytes()[..])));
assert_eq!(cursor.move_on_key_equal_to("second-counter")?, Some((&b"second-counter"[..], &384_u32.to_ne_bytes()[..])));
assert_eq!(cursor.move_on_key_lower_than_or_equal_to("abracadabra")?, None);

使用 Merger 结构体

在这个示例中,我们展示了如何使用合并函数在遇到冲突时合并多个 Reader

Merger 结构体生成的条目以字典序返回,将它们写回一个新的 Writer 是一个好方法。

use std::array::TryFromSliceError;
use std::borrow::Cow;
use std::convert::TryInto;
use std::io::Cursor;

use grenad::{MergerBuilder, Reader, Writer};

// This merge function:
//  - parses u32s from native-endian bytes,
//  - wrapping sums them and,
//  - outputs the result as native-endian bytes.
fn wrapping_sum_u32s<'a>(
 _key: &[u8],
 values: &[Cow<'a, [u8]>],
) -> Result<Cow<'a, [u8]>, TryFromSliceError>
{
    let mut output: u32 = 0;
    for bytes in values.iter().map(AsRef::as_ref) {
        let num = bytes.try_into().map(u32::from_ne_bytes)?;
        output = output.wrapping_add(num);
    }
    Ok(Cow::Owned(output.to_ne_bytes().to_vec()))
}

// We create our writers in memory to insert our key-value pairs.
let mut writera = Writer::memory();
let mut writerb = Writer::memory();
let mut writerc = Writer::memory();

// We insert our key-value pairs in lexicographic order
// and mix them between our writers.
writera.insert("first-counter", 32_u32.to_ne_bytes())?;
writera.insert("second-counter", 64_u32.to_ne_bytes())?;
writerb.insert("first-counter", 23_u32.to_ne_bytes())?;
writerb.insert("second-counter", 320_u32.to_ne_bytes())?;
writerc.insert("first-counter", 64_u32.to_ne_bytes())?;

// We create readers from our writers.
let cursora = writera.into_inner().map(Cursor::new)?;
let cursorb = writerb.into_inner().map(Cursor::new)?;
let cursorc = writerc.into_inner().map(Cursor::new)?;
let readera = Reader::new(cursora)?.into_cursor()?;
let readerb = Reader::new(cursorb)?.into_cursor()?;
let readerc = Reader::new(cursorc)?.into_cursor()?;

// We create a merger that will sum our u32s when necessary,
// and we add our readers to the list of readers to merge.
let merger_builder = MergerBuilder::new(wrapping_sum_u32s);
let merger = merger_builder.add(readera).add(readerb).add(readerc).build();

// We can iterate over the entries in key-order.
let mut iter = merger.into_stream_merger_iter()?;

// We can see that the sum of u32s is valid here.
assert_eq!(iter.next()?, Some((&b"first-counter"[..], &119_u32.to_ne_bytes()[..])));
assert_eq!(iter.next()?, Some((&b"second-counter"[..], &384_u32.to_ne_bytes()[..])));
assert_eq!(iter.next()?, None);

使用 Sorter 结构体

在这个例子中,我们展示了如何通过定义一个合并函数,我们可以插入具有相同键的多个条目,并以字典顺序输出它们。

Sorter接受任何给定顺序的条目,将在内存中重新排序它们,并在需要时使用合并函数合并它们。它在构建过程中有权拥有内存预算,并会尽可能遵循该预算。

use std::array::TryFromSliceError;
use std::borrow::Cow;
use std::convert::TryInto;

use grenad::{CursorVec, SorterBuilder};

// This merge function:
//  - parses u32s from native-endian bytes,
//  - wrapping sums them and,
//  - outputs the result as native-endian bytes.
fn wrapping_sum_u32s<'a>(
 _key: &[u8],
 values: &[Cow<'a, [u8]>],
) -> Result<Cow<'a, [u8]>, TryFromSliceError>
{
    let mut output: u32 = 0;
    for bytes in values.iter().map(AsRef::as_ref) {
        let num = bytes.try_into().map(u32::from_ne_bytes)?;
        output = output.wrapping_add(num);
    }
    Ok(Cow::Owned(output.to_ne_bytes().to_vec()))
}

// We create a sorter that will sum our u32s when necessary.
let mut sorter = SorterBuilder::new(wrapping_sum_u32s).chunk_creator(CursorVec).build();

// We insert multiple entries with the same key but different values
// in arbitrary order, the sorter will take care of merging them for us.
sorter.insert("first-counter", 32_u32.to_ne_bytes())?;
sorter.insert("first-counter", 23_u32.to_ne_bytes())?;
sorter.insert("first-counter", 64_u32.to_ne_bytes())?;

sorter.insert("second-counter", 320_u32.to_ne_bytes())?;
sorter.insert("second-counter", 64_u32.to_ne_bytes())?;

// We can iterate over the entries in key-order.
let mut iter = sorter.into_stream_merger_iter()?;

// We can see that the sum of u32s is valid here.
assert_eq!(iter.next()?, Some((&b"first-counter"[..], &119_u32.to_ne_bytes()[..])));
assert_eq!(iter.next()?, Some((&b"second-counter"[..], &384_u32.to_ne_bytes()[..])));
assert_eq!(iter.next()?, None);

依赖关系

~2–12MB
~148K SLoC