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
每月下载量 68
在 2 crate 中使用
1.5MB
32K SLoC
实验性的 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 操作提供自定义逻辑。
Read
、Upsert
和 RMW
操作都需要一个单调序列号来形成 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
派生 Serializable
和 Deserializable
的实现。所有实现了这两个特质的类型将自动实现 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
派生 Serializable
和 Deserializable
的实现。
为了在自定义类型上使用读写修改(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