17 个稳定版本 (3 个主要版本)
3.5.0 | 2024年2月14日 |
---|---|
3.4.0 | 2023年9月17日 |
3.3.0 | 2023年5月16日 |
3.2.0 | 2023年2月22日 |
0.3.0 | 2019年9月8日 |
#104 在 数据库接口
每月91 次下载
105KB
2K SLoC
mokuroku
概述
这个 Rust 包旨在在 RocksDB 键值存储之上提供二级索引,类似于 PouchDB 为 LevelDB 所做的工作。您的应用程序将提供 Document
特质的实现,以适应数据库中存储的各种数据类型,而这个库将调用这些 Document
实例的映射函数以生成索引键值对。
该库的行为与 PouchDB 类似,但 API 更适合该语言。与 PouchDB 不同,该库不对数据库记录的格式施加任何约束。因此,库依赖于应用程序提供反序列化记录和调用带有二级键和可选值的 emit()
函数的功能。为了避免不必要的反序列化,每当应用程序在 Database
实例上调用 put()
函数时,库将使用每个定义的索引名称调用 Document.map()
。
分类
该库确实做什么:由该库管理的索引是“独立的”,这意味着它们不是嵌入在数据库文件中(例如区域图或布隆过滤器)。此外,索引以懒惰的方式更新,这意味着更改是追加的,而不是在更新时积极合并。这个描述很好地概括了整体性能。
在这里,二级索引中的每个条目都是一个由(二级键+主键)组成的复合键。二级查找是对二级键的前缀搜索,可以使用索引表的常规范围搜索来实现。在这里,写入和压缩比“懒惰”更快,但二级属性查找可能较慢,因为它需要在索引表上执行范围扫描。
与简单的复合索引相比,这个库允许应用程序在辅助键中发出一个值,而不是通常在辅助索引中为null
分配的值。
欢迎贡献
尽管作者阅读了一些相关的研究论文,但他在数据库技术方面并不算专家。同样,他的Rust技能可能也并不那么令人印象深刻。如果您想贡献,请尽请随意。提前感谢。
构建和测试
先决条件
- Rust稳定版(2018版)
构建和测试
以下命令将构建库并运行测试。
$ cargo clean
$ cargo build
$ cargo test
Apple M1 支持
从 rust-rocksdb 的 0.16 版本开始,此库支持从 macOS 平台上的 2.5.0 版本开始使用 ARM64 目标构建。
示例
请参阅 examples/tagged.rs
中的完整示例,它创建了一个RocksDB数据库,添加了一些记录,建立了一个辅助索引,并使用特定的键值查询该索引。
cargo run --example tagged
演示数字索引的示例,examples/numdex.rs
,利用了base32hex的位排序顺序保留功能,这也使得数字键对于辅助索引的复合键格式来说是“安全”的。在 numdex
索引中的键是UTC毫秒数,查询是在特定日期范围内更新的资产。索引键和查询键都必须进行编码,并且将数字按大端顺序排列会有所帮助。
cargo run --example numdex
快速示例
此代码片段是从上述示例中提取的。它展示了打开数据库、添加记录和查询索引的最基本用法。生成索引键和值的函数示例在 examples/tagged.rs
示例代码中。
let db_path = "my_database";
let views = vec!["tags".to_owned()];
let dbase = Database::open_default(Path::new(db_path), views, Box::new(mapper)).unwrap();
let documents = [
Asset {
key: String::from("asset/blackcat"),
location: String::from("hawaii"),
tags: vec![
String::from("cat"),
String::from("black"),
String::from("tail"),
],
},
// ...
];
for document in documents.iter() {
let key = document.key.as_bytes();
let _ = dbase.put(&key, document);
}
// querying the "tags" index for keyword "cat"
let result = dbase.query_by_key("tags", b"cat");
let iter = result.unwrap();
let results: Vec<QueryResult> = iter.collect();
for result in results {
let doc_id = str::from_utf8(&result.doc_id).unwrap().to_owned();
println!("query result key: {:}", doc_id);
}
功能
可选功能
Mokuroku 支持几个可选功能,以减轻与其他流行crate使用此crate的负担。
anyhow
:启用自动将anyhow::Error
转换为mokuroku::Error
serde_cbor
:启用自动将serde_cbor::Error
转换为mokuroku::Error
性能功能
multi-threaded-cf
:传递给rocksdb
,以允许从多个线程并发创建和删除列族。
设计
术语
关于本项目使用的术语的简要说明。您可能会在这里和那里看到“视图”一词。这是 CouchDB 和 PouchDB 在其文档中称为索引的术语。鉴于这个crate试图以类似的方式运行,使用相同的术语似乎是自然而然的。函数名 emit
也来自 CouchDB 的“map/reduce” API,与其他任何术语一样合理。
用法
应用程序将使用 mokuroku::Database
结构体代替 rocksb::DB
,因为这个 crate 将创建和管理那个 DB
实例。由于这个 crate 管理次要索引,应用程序调用 put()
函数在 Database
上,而不是直接调用 DB
。对于那些不会影响任何索引的操作,应用程序可以自由地使用 Database.db()
函数来获取对 DB
的直接引用。
在启动时,应用程序将创建一个 Database
实例并提供三个参数。
- 数据库文件的路径,就像使用
DB::open()
- 要传递给
Document.map()
的索引名称集合 - 一个类型为
mokuroku::ByteMapper
的封装函数
索引名称集合是库每次调用 Database.put()
时将更新的索引。也就是说,传递给 put()
调用的 Document
实现将使用提供的每个索引名称调用其 map()
。并非每个 Document
都会为每个索引发出索引键/值对。实际上,没有任何要求发出任何内容,这完全取决于应用程序。同样,单个映射调用可能会发出多个值,这在 tagged
示例中有演示。
当库需要重建索引时需要 ByteMapper
。由于库将读取默认列族中的每个记录,它不知道如何将记录反序列化到适当的 Document
实现中。因此,应用程序必须提供此函数来识别和反序列化记录,然后发出索引键/值对。
在设置数据库之后,应用程序可能希望对每个命名索引调用数据库上的 query()
。这将导致库在缺失时构建索引,这将提高后续对 query()
的调用响应时间。
数据模型
应用程序定义数据库记录格式;这个库不对键或值的格式施加任何限制。这也是为什么与 PouchDB 等相比,使用稍微复杂的原因之一。
该库在单独的列族中维护二级索引,列族名称以mrview-
开头,以避免与应用程序可能创建的任何列族发生冲突。例如,如果应用程序创建一个名为tags的索引,那么该库将创建一个名为mrview-tags
的列族,并用传递给Document.map()
和应用程序定义的ByteMapper
实现的Emitter
提供的值填充它。
应用程序输出的索引键不需要是唯一的。库将附加数据记录主键以确保没有索引条目会覆盖另一个(两个键由一个空字节分隔;如果您需要更改此设置,请使用Database.separator()
函数)。应用程序可以为索引条目发出一个可选值,其格式完全由应用程序决定。
参考文献
在设计该库时,以下论文按照发表顺序被引用,其中第二篇是最相关的。
依赖关系
~27MB
~541K SLoC