19个不稳定版本
0.11.0 | 2020年9月30日 |
---|---|
0.10.0 | 2019年11月3日 |
0.9.0 | 2019年9月18日 |
0.8.5 | 2019年7月13日 |
#422 in 编码
每月52次下载
1.5MB
32K SLoC
实验性的FASTER Rust封装
[dependencies]
faster-rs = "0.11.0"
包括FASTER的实验性C接口。这是一个通用的FASTER实现,允许存储任意键值对。此封装只关注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++版本类似的接口。用户可以定义自己的键值类型,并提供自定义的读写修改操作逻辑。
读取、更新和读写操作都需要一个单调的序列号来形成FASTER将持久化的操作序列。读取操作需要一个序列号,以便在CPR检查点边界之前,FASTER保证在该点之前的读取没有访问检查点之后的数据更新。如果持久化不重要,可以将序列号安全地设置为 1
以适用于所有操作(如上述示例中所示)。
以下示例中提供了有关检查点和恢复的更多信息。
基本示例
以下示例展示了创建FASTER键值存储以及对 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 进行(反)序列化,并作为键支持。为了使用这样的 结构体
,需要从 serde-derive
派生 Serializable
和 Deserializable
的实现。所有实现这两个特质的类型将自动实现 FasterKey
,从而可以用作键。
以下示例展示了如何将基本结构体用作键。可以通过运行以下命令进行测试: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 进行(反)序列化,并作为值支持。为了使用这样的 结构体
,需要从 serde-derive
派生 Serializable
和 Deserializable
的实现。
为了在自定义类型上使用读写操作,还需要实现 FasterRmw
特质,该特质公开了一个 rmw()
函数。此函数可用于实现读写操作的定制逻辑。
以下示例展示了如何将基本结构体用作值。可以通过运行以下命令进行测试: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 之前的所有操作也将被持久化(并且 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文件的输出,并生成一个只包含8字节键的输出文件,格式符合Rust和C基准测试的预期。run
实际上使用提供的加载和运行密钥执行基准测试。工作负载和线程数可以自定义。
基准测试与原始的C++实现非常相似,因此最好遵循他们的说明来设置YCSB。
依赖关系
~0.9–4MB
~78K SLoC