18 个版本 (10 个破坏性更新)

0.11.0 2020年9月30日
0.10.0 2019年11月3日
0.9.0 2019年9月18日
0.8.3 2019年7月13日
0.1.0 2019年2月28日

#faster 中排名 24

Download history 1/week @ 2024-04-07 3/week @ 2024-05-19 1/week @ 2024-06-16

每月下载量 68
2 crate 中使用

MIT 许可证

1.5MB
32K SLoC

C# 27K SLoC // 0.2% comments C++ 4K SLoC // 0.1% comments Visual Studio Solution 278 SLoC Visual Studio Project 170 SLoC Rust 51 SLoC // 0.0% comments

License Cargo Build Status

实验性的 Rust FASTER 包装器

[dependencies]
faster-rs = "0.11.0"

包括实验性的 FASTER C 接口。这是一个允许任意 Key-Value 对存储的泛型实现。此包装器只关注 Linux 支持。

安装依赖项(Ubuntu)

$ add-apt-repository -y ppa:ubuntu-toolchain-r/test
$ apt update
$ apt install -y g++-7 libaio-dev uuid-dev libtbb-dev

请确保也克隆了子模块,这最好通过使用 git clone --recurse-submodules 克隆来完成。

接口

此包装器试图保持对原始 FASTER 设计的忠诚,通过提供一个与原始 C++ 版本提供的类似接口。用户可以定义自己的 Key-Value 类型,并为 Read-Modify-Write 操作提供自定义逻辑。

ReadUpsertRMW 操作都需要一个单调序列号来形成 FASTER 将要持久化的操作序列。Read 操作需要一个序列号,以便在 CPR 检查点边界处,FASTER 保证在该点之前读取的读取没有访问检查点之后的数据更新。如果持久性不重要,可以将序列号安全地设置为 1 以执行所有操作(如上述示例中所示)。

以下示例中提供了有关检查点和恢复的更多信息。

基本示例

以下示例显示了创建 FASTER Key-Value 存储,并在 u64 值上执行基本操作。

通过运行 cargo run --example basic 来尝试。

extern crate faster_rs;

use faster_rs::{status, FasterKv};
use std::sync::mpsc::Receiver;

fn main() {
    // Create a Key-Value Store
    let store = FasterKv::default();
    let key0: u64 = 1;
    let value0: u64 = 1000;
    let modification: u64 = 5;

    // Upsert
    for i in 0..1000 {
        let upsert = store.upsert(&(key0 + i), &(value0 + i), i);
        assert!(upsert == status::OK || upsert == status::PENDING);
    }

    // Read-Modify-Write
    for i in 0..1000 {
        let rmw = store.rmw(&(key0 + i), &(5 as u64), i + 1000);
        assert!(rmw == status::OK || rmw == status::PENDING);
    }

    assert!(store.size() > 0);

    // Read
    for i in 0..1000 {
        // Note: need to provide type annotation for the Receiver
        let (read, recv): (u8, Receiver<u64>) = store.read(&(key0 + i), i);
        assert!(read == status::OK || read == status::PENDING);
        let val = recv.recv().unwrap();
        assert_eq!(val, value0 + i + modification);
        println!("Key: {}, Value: {}", key0 + i, val);
    }

    // Clear used storage
    match store.clean_storage() {
        Ok(()) => {}
        Err(_err) => panic!("Unable to clear FASTER directory"),
    }
}

使用自定义键

支持使用 serde 进行(反)序列化的 struct 可以用作键。为了使用这样的 struct,需要从 serde-derive 派生 SerializableDeserializable 的实现。所有实现了这两个特质的类型将自动实现 FasterKey,因此可以用作键。

以下示例展示了如何将一个基本的 struct 用作键。尝试运行 cargo run --example custom_keys

extern crate faster_rs;
extern crate serde_derive;

use faster_rs::{status, FasterKv};
use serde_derive::{Deserialize, Serialize};
use std::sync::mpsc::Receiver;

// Note: Debug annotation is just for printing later
#[derive(Serialize, Deserialize, Debug)]
struct MyKey {
    foo: String,
    bar: String,
}

fn main() {
    // Create a Key-Value Store
    let store = FasterKv::default();
    let key = MyKey {
        foo: String::from("Hello"),
        bar: String::from("World"),
    };
    let value: u64 = 1;

    // Upsert
    let upsert = store.upsert(&key, &value, 1);
    assert!(upsert == status::OK || upsert == status::PENDING);

    assert!(store.size() > 0);

    // Note: need to provide type annotation for the Receiver
    let (read, recv): (u8, Receiver<u64>) = store.read(&key, 1);
    assert!(read == status::OK || read == status::PENDING);
    let val = recv.recv().unwrap();
    println!("Key: {:?}, Value: {}", key, val);

    // Clear used storage
    match store.clean_storage() {
        Ok(()) => {}
        Err(_err) => panic!("Unable to clear FASTER directory"),
    }
}

使用自定义值

支持使用 serde 进行(反)序列化的 struct 可以用作值。为了使用这样的 struct,需要从 serde-derive 派生 SerializableDeserializable 的实现。

为了在自定义类型上使用读写修改(Read-Modify-Write)操作,还需要实现 FasterRmw 特质,该特质暴露了一个 rmw() 函数。此函数可用于实现读写修改操作的定制逻辑。

以下示例展示了如何将一个基本的 struct 用作值。尝试运行 cargo run --example custom_values

extern crate faster_rs;
extern crate serde_derive;

use faster_rs::{status, FasterKv};
use serde_derive::{Deserialize, Serialize};
use std::sync::mpsc::Receiver;

// Note: Debug annotation is just for printing later
#[derive(Serialize, Deserialize, Debug)]
struct MyValue {
    foo: String,
    bar: String,
}

fn main() {
    // Create a Key-Value Store
    let store = FasterKv::default();
    let key: u64 = 1;
    let value = MyValue {
        foo: String::from("Hello"),
        bar: String::from("World"),
    };

    // Upsert
    let upsert = store.upsert(&key, &value, 1);
    assert!(upsert == status::OK || upsert == status::PENDING);

    assert!(store.size() > 0);

    // Note: need to provide type annotation for the Receiver
    let (read, recv): (u8, Receiver<MyValue>) = store.read(&key, 1);
    assert!(read == status::OK || read == status::PENDING);
    let val = recv.recv().unwrap();
    println!("Key: {}, Value: {:?}", key, val);

    // Clear used storage
    match store.clean_storage() {
        Ok(()) => {}
        Err(_err) => panic!("Unable to clear FASTER directory"),
    }
}

现成的 FasterRmw 实现

一些类型已经实现了 FasterRmw 并提供了读写修改逻辑。实现可以在 src/impls.rs 中找到,但它们的 RMW 逻辑在此简要总结:

  • 数值类型使用加法
  • 布尔型和字符型用新值替换旧值
  • 字符串和 Vec 追加修改
  • HashSet 执行并集操作

检查点和恢复

FASTER 的容错性由 并发前缀恢复(CPR)提供。它提供了以下语义:

如果操作 X 被持久化,则输入操作序列中 X 之前的所有操作也会被持久化(以及之后的所有操作)。

使用 checkpoint() 函数持久化操作。同样,定期调用 refresh() 函数也很重要,因为它是线程向系统报告进度的一种机制。

各个会话(访问 FASTER 的线程)将持久化不同数量的操作。通过 continue_session() 函数返回最近持久化的序列号,这允许推断哪些操作已被(未)持久化。它也是线程在恢复后应继续提供操作的运算序列号。

可以在 examples/sum_store_single.rs 中找到检查点/恢复的示例。试试看自己动手做吧!

$ cargo run --example sum_store_single -- populate
$ cargo run --example sum_store_single -- recover <checkpoint-token>

基准测试

可以基准测试 FASTER 的 C 包装器和 Rust 包装器。要构建和运行 C 基准测试,请遵循微软的说明 这里,然后运行二进制文件 benchmark-c。它采用与原始基准测试相同的参数和输入格式。

运行 Rust 基准测试

基准测试作为一个独立的crate存放在benchmark目录中。在目录内运行以下命令来查看可用选项:cargo run --release -- help

基准测试包括两个子命令:cargo run --release -- [process-ycsb|run]

  • process-ycsb将取供应的YCSB文件的输出,并生成一个只包含Rust & C基准测试期望格式的8字节键的输出文件。
  • run将使用提供的负载和运行密钥实际执行基准测试。工作负载和线程数可以自定义。

基准测试与原始的C++实现非常相似,因此最好遵循他们的说明来设置YCSB。

依赖项

~0–2.4MB
~46K SLoC