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次下载

MIT 许可证

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生态系统中存在许多替代方案,包括但不限于sledPersyredbsankirjalmdbsqlite等。所有这些都是非常好的替代方案,RocksDB的独特优势在于其(非常)广泛的使用和测试,而且我个人觉得它的API非常容易理解。但关键因素是RocksDB支持自定义压缩过滤器。有了这个特性,条目的过期会在自然的压缩过程中发生。这是一个非常具体且相当高级的特性,它在当前实现中非常有用。

实际上,MenhirKV提供了以下功能

  • 透明(反)序列化,只需使用#[derive(Serialize, Deserialize)您的类型,即可直接使用。
  • 错误处理。您可以使用?;语法!多亏了Sled维护者写的关于错误处理的这篇优秀的博客文章
  • 磁盘空间控制,只需将数据扔进去,旧的不用数据最终会消失,无需担心。
  • 友好的API,包括putgetpeekdelete迭代器命名空间以及其他语法糖。
  • 线程安全,用并行和并发请求轰炸它,RocksDB会为您处理魔法。

但事实上,太阳之下无新鲜事,MenhirKV只是将Rust + RocksDB + Bincode + Bloom过滤器混合在一起。

它旨在速度和简洁。

MenhirKV icon

状态

据我所知,尽管目前没有在“真实”的生产环境中使用,但它只是基于经过充分测试、广泛使用的包的薄层。因此,它应该是可以使用。再次声明,使用风险自担。

此外,它需要使用较新的Rust(1.67)进行构建,因为存在一些类型推断问题

Build Status Crates.io Gitlab License

使用方法

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上使用普通虚拟硬件进行的,因此在真实的生产硬件上可能会更快。或者也可能不会。

还有与kv的比较,这是一个类似的包,尽管它依赖于sled

要运行基准测试

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