10个版本
0.4.5 | 2024年3月17日 |
---|---|
0.4.4 | 2023年2月7日 |
0.4.2 | 2023年1月21日 |
0.3.0 | 2023年1月18日 |
0.1.1 | 2023年1月12日 |
#511 in 数据库接口
每月46次下载
100KB
1.5K SLoC
MenhirKV
MenhirKV 是基于 RocksDB 和用 Rust 实现的另一个本地KV存储。
简而言之,这个库允许您在本地存储键值对,前提是数据是可序列化的。它还保证条目最终会过期,以便磁盘空间使用得到控制。存储您的数据,永远不用担心空间。只有您从未访问过的无趣数据会自动消失。
大多数底层键值存储提供 &[u8], &[u8]
或类似接口,并让库的用户弄清楚(反)序列化的细节。这是提供通用接口、能够处理任何事物的最佳方式,您只需在此之上构建您的用例即可。
MenhirKV 脱离了纯KV通用性,解决了某些实现细节,并做出了一些有争议的选择,具体如下
- 使用 Serde,这是Rust的事实标准,但最终,几乎所有人都在Rust中存储高级对象时都会使用它... 然而,MenhirKV 也选择依赖 bincode,这可能是另一个有争议的选择。
- 使用 Bloom Filter 来过期键,它提供了类似于 LRU 的语义,同时资源效率更高。内部使用一个 自定义的过滤器。
- 本系统依赖于RocksDB进行存储。它有一个明显的缺点,那就是它不是纯Rust编写的,所以您需要安装clang + llvm,这是由于存在C++依赖的后果。在Rust生态系统中存在许多替代方案,包括但不限于sled、Persy、redb、sankirja、lmdb、sqlite等。所有这些都是非常好的替代方案,RocksDB的独特优势在于其(非常)广泛的使用和测试,而且我个人觉得它的API非常容易理解。但关键因素是RocksDB支持自定义压缩过滤器。有了这个特性,条目的过期会在自然的压缩过程中发生。这是一个非常具体且相当高级的特性,它在当前实现中非常有用。
实际上,MenhirKV提供了以下功能
- 透明(反)序列化,只需使用
#[derive(Serialize, Deserialize)
您的类型,即可直接使用。 - 错误处理。您可以使用
?;
语法!多亏了Sled维护者写的关于错误处理的这篇优秀的博客文章。 - 磁盘空间控制,只需将数据扔进去,旧的不用数据最终会消失,无需担心。
- 友好的API,包括put、get、peek、delete、迭代器和命名空间以及其他语法糖。
- 线程安全,用并行和并发请求轰炸它,RocksDB会为您处理魔法。
但事实上,太阳之下无新鲜事,MenhirKV只是将Rust + RocksDB + Bincode + Bloom过滤器混合在一起。
它旨在速度和简洁。
状态
据我所知,尽管目前没有在“真实”的生产环境中使用,但它只是基于经过充分测试、广泛使用的包的薄层。因此,它应该是可以使用。再次声明,使用风险自担。
此外,它需要使用较新的Rust(1.67)进行构建,因为存在一些类型推断问题。
使用方法
use menhirkv::Store;
// Example with a usize store. Both keys and values are of type usize.
// Feel free to use your own types, they just need to have Serde support.
let store: Store<usize, usize> = Store::open_temporary(100).unwrap();
store.put(&123, &456).unwrap();
assert_eq!(Some(456), store.get(&123).unwrap());
基准测试
来自随机CI作业
running 6 tests
test tests::bench_extern_crate_kv_blob ... bench: 124,820 ns/iter (+/- 41,822)
test tests::bench_extern_crate_kv_bool ... bench: 6,693 ns/iter (+/- 970)
test tests::bench_menhirkv_blob_1k ... bench: 76,003 ns/iter (+/- 99,393)
test tests::bench_menhirkv_blob_max ... bench: 82,169 ns/iter (+/- 114,962)
test tests::bench_menhirkv_bool_1k ... bench: 9,214 ns/iter (+/- 879)
test tests::bench_menhirkv_bool_max ... bench: 9,108 ns/iter (+/- 707)
test result: ok. 0 passed; 0 failed; 0 ignored; 6 measured; 0 filtered out; finished in 37.70s
这并不是广泛的、彻底的基准测试的结果,只是开发历史某个时刻的随机快照。
TL;DR -> 序列化有代价,RocksDB很快。
上述测试是在Gitlab CI上使用普通虚拟硬件进行的,因此在真实的生产硬件上可能会更快。或者也可能不会。
要运行基准测试
cd bench
rustup default nightly
cargo bench
关于容量
这是MenhirKV做出的最不寻常和最具争议的选择之一,让我们深入了解。
您设置了一个capacity
,这是一个最低限制。
这是一个必选参数,通常一个基本的商店开启需要两个参数:路径 + 容量。
例如,如果您将capacity
设置为10k(一万),那么您可以保证有10k条条目存储,并且没有过期。您可能最终在磁盘上存储多达50k甚至100k条条目。但是,在某个时刻,根据RocksDB如何运行其内部压缩以及Bloom过滤器如何表现,这两者都是不可预测的,数据将被过滤、压缩,并移除“旧”键。
"旧"指的是键被访问的最后时间。条目可能是最先写入数据库的,如果它持续被访问,无论是读取还是写入,它都会保留在键列表的顶部以保存。
LRU缓存以非常可预测的方式执行此操作,但维护成本很高,尤其是在持久存储方面。我围绕这一点做了一个玩具项目(**)
,并且可以说它表现不佳。大多数时候,上述描述的模糊策略已经足够好。它确保了两件事
- 热数据始终可用
- 磁盘空间保持受控
使其高效实现的实现细节技巧是,通过使用钩子自定义压缩过滤器,未使用条目的过期成本几乎为零。这些数据块本来就会被RocksDB处理。MenhirKV所做的是仅在RocksDB试图确定如何压缩数据并重新组织磁盘上的数据时给出提示 -> "哦,你知道的,我们不需要这个,就把它扔在地上吧"。
(*)
好吧,几乎,在边缘情况下,保留条目的数量可能会略低于计划的capacity
。这是因为Bloom过滤器实现和使用细节,但从统计上讲,商店保留的条目比请求的capacity
更多。将此capacity
设置视为一个模糊限制。如果您确实需要一个精确的数字,MenhirKV可能不适合您。
(**)
DiskLRU,一个关于持久LRU的实验性玩具项目。在制作MenhirKV的过程中,我对它的工作非常有帮助。
链接
许可证
MenhirKV采用MIT许可证。
依赖关系
~26–37MB
~672K SLoC