TurboKV:速度惊人的 Rust 键值存储
一个用 Rust 编写的、速度极快的嵌入式键值存储
TurboKV 是一个异步嵌入式键值数据库,具备原子批量写入、有序范围扫描、可配置持久化、压缩与后台 compaction 能力。
安装
cargo add turbokv cargo add tokio --features full
或直接添加依赖:
[dependencies]
turbokv = "0.6"
tokio = { version = "1", features = ["full"] }
TurboKV 持久化的 Bloom filter 格式使用硬件 AES。x86/x86_64 目标请用 RUSTFLAGS="-C target-feature=+aes,+sse2" 构建,ARM/AArch64 目标请用 RUSTFLAGS="-C target-feature=+aes,+neon" 构建。若二进制只会运行在相同 CPU 型号或其指令集超集上,也可以改用 -C target-cpu=native。
快速上手
use turbokv::{Db, DbOptions, WriteBatch};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Db::open_with_options("./my-database", DbOptions::durable()).await?;
db.insert(b"user:1", b"Ada").await?;
assert_eq!(db.get(b"user:1").await?, Some(b"Ada".to_vec()));
let mut batch = WriteBatch::new();
batch.put(b"user:2", b"Grace");
batch.put(b"user:3", b"Linus");
batch.delete(b"user:1");
db.write_batch(&batch).await?;
for (key, value) in db.scan_prefix(b"user:").await? {
println!(
"{} = {}",
String::from_utf8_lossy(&key),
String::from_utf8_lossy(&value)
);
}
db.close().await?;
Ok(())
}
可运行的示例:
basic:插入、读取、更新与删除batch_writes:原子化的 put 与 deleterange_queries:有序范围扫描与前缀扫描concurrent:多个 Tokio 任务间的共享访问persistence:paranoid 模式的 WAL 恢复configuration:缓存、memtable 与压缩选项
API 一览
持久化预设
| 预设 | 确认边界 | 使用场景 |
|---|---|---|
DbOptions::fast() |
内存中立即可见;不使用 WAL | 缓存与可重现数据 |
DbOptions::durable() |
追加写入 WAL,但每次写入不做 sync | 进程崩溃可恢复;推荐默认值 |
DbOptions::paranoid() |
WAL 组在返回前完成 sync_all |
最强模式,受文件系统/设备保证的约束 |
每个已打开的 Db 或 Engine 都独占地拥有其数据目录。请使用 close() 或 close_with_status() 进行干净关闭;直接丢弃句柄并不构成干净关闭的契约。
数据库操作
键和值是通过 AsRef<[u8]> 传入的任意字节序列;字符串需由调用方自行编码。所有修改类 API 在返回前会复制其输入。点读与收集式读取返回拥有所有权的 Vec<u8> 值。空值是有效数据,与已删除的键不同。
打开与配置
| API | 参数 | 结果与行为 |
|---|---|---|
Db::open(path) |
path: AsRef<Path> |
以 DbOptions::durable() 打开或创建目录。打开的句柄独占该目录。 |
Db::open_with_options(path, options) |
数据库路径和一个 DbOptions 值 |
以显式的持久化、内存、缓存与压缩设置打开。会拒绝相互矛盾的设置,例如 sync_writes = true 与禁用 WAL 并存。 |
DbOptions::fast() |
无 | 返回无 WAL 预设。 |
DbOptions::durable() |
无 | 返回进程崩溃可恢复的 WAL 预设。 |
DbOptions::paranoid() |
无 | 返回同步完成后才确认的预设。 |
options.with_compression(compression) |
一个 Compression 变体 |
Builder 风格的更新,返回修改后的选项。 |
所有预设均以 64 MiB memtable、64 MiB block cache 和 LZ4 压缩起步。它们的公开字段可以在打开数据库前调整:
DbOptions 字段 |
含义 |
|---|---|
wal_enabled: bool |
将变更追加到 WAL。禁用它意味着在成功 flush 或 close 之前,进程崩溃可能导致数据丢失。 |
sync_writes: bool |
在确认每个变更组之前等待 WAL sync 屏障。需要启用 wal_enabled。 |
memtable_size: usize |
触发 memtable 轮换与后台 flush 的近似内存字节阈值。 |
block_cache_size: usize |
解压后 SSTable block cache 的字节预算。设为 0 可禁用缓存。 |
compression: Compression |
新写入数据的 SSTable 压缩方式:Lz4、Snappy、Zstd 或 None。已有的表保留其编码格式。 |
点操作、批量操作与原子批
| API | 参数 | 返回值与语义 |
|---|---|---|
insert(key, value) |
字节类型的键和值 | Result<()>。插入或替换该键。成功返回前已达到所选的持久化边界。 |
insert_many(entries) |
任意 (key, value) 对的迭代器 |
Result<()>。完整复制迭代器并按顺序应用各条目;重复键以最后一个为准。这是批量 API,不是单次原子可见性切换。 |
get(key) |
字节类型的键 | Result<Option<Vec<u8>>>。缺失或已删除的键返回 None,已存储的空值返回 Some(Vec::new())。 |
remove(key) |
字节类型的键 | Result<()>。写入墓碑(tombstone);允许删除不存在的键。 |
contains_key(key) |
字节类型的键 | Result<bool>。判定结果与 get 一致,目前也会产生与 get 相同的值分配开销。 |
write_batch(batch) |
&WriteBatch |
Result<()>。原子地发布全部操作;读者要么看到批次之前的状态,要么看到完整批次。重复键以最后一个操作为准。 |
启用 WAL 时,单条记录或完整批次必须能装进 WAL 的 u32 载荷长度。失败或被取消的变更可能已经写入 WAL;重试非幂等操作前,请先检查对应键或重新打开数据库。
WriteBatch 持有每个键和值的副本:
| API | 参数 | 效果 |
|---|---|---|
WriteBatch::new() |
无 | 创建空批次。 |
WriteBatch::with_capacity(capacity) |
预期操作数量 | 预分配操作槽位,但不预分配键或值的字节。 |
batch.put(key, value) |
字节类型的键和值 | 追加一个拥有所有权的 put 操作。 |
batch.delete(key) |
字节类型的键 | 追加一个拥有所有权的 delete 操作。 |
batch.ops() |
无 | 借用有序的 &[BatchOp] 操作列表。 |
batch.len() / batch.is_empty() |
无 | 报告当前操作数量。 |
batch.clear() |
无 | 清空所有操作,同时保留批次已分配的内存以便复用。 |
范围扫描与前缀扫描
按键的原始字节进行字典序排序。每次扫描都会捕获一个一致的时间点视图。创建扫描可能冻结非空的活跃 memtable,因此频繁的小型扫描可能增加后续 flush 的工作量。
| API | 参数 | 返回值与分配 |
|---|---|---|
range(start, end) |
含起始键(含)与结束键(不含) | Result<Vec<(Vec<u8>, Vec<u8>)>>;急切地为返回的每个键和值分配内存。 |
scan_prefix(prefix) |
字节前缀;空前缀匹配所有条目 | 按顺序急切收集所有匹配的键值对。 |
range_iter(start, end) |
相同的 [start, end) 边界 |
创建 RangeIter。迭代项为 Result<EntryGuard, ScanError>,因为在推进过程中可能发现数据损坏。 |
scan_prefix_iter(prefix) |
字节前缀 | 创建 PrefixIter,它是同一流式实现的别名。 |
推进流式迭代器是同步操作,可能执行 mmap 读取、校验和验证、解压以及缓存加锁。请及时丢弃迭代器:它会固定持有其快照读取器和数据库目录的所有权。
| 迭代器或 guard API | 参数 | 结果 |
|---|---|---|
iter.count() |
无 | 消耗迭代器并返回 Result<usize, ScanError>。 |
iter.keys() |
无 | 消耗迭代器并收集拥有所有权的键,不物化 memtable 中的值。 |
iter.collect_pairs() |
无 | 消耗迭代器并收集拥有所有权的键值对。 |
iter.paginate(offset, limit) |
要跳过的条目数和最多返回的条目数 | 返回惰性迭代器;被跳过的条目会被遍历,但其 memtable 值不会被复制。 |
guard.key() |
无 | 借用键而不加载值。 |
guard.value() / guard.value_len() |
无 | 借用值或报告其长度;memtable 中的值只在首次调用 value() 时才会被复制。 |
guard.into_pair() / into_key() / into_value() |
无 | 消耗 guard 并返回所请求的拥有所有权的字节。 |
持久化、维护与统计
| API | 参数 | 返回值与开销 |
|---|---|---|
flush() |
无 | Result<()>。排空待处理写入,安装 SSTable 与 manifest,同步 WAL,并回收符合条件的 WAL 段。并发开始的写入可能需要之后再 flush 一次。 |
compact() |
无 | Result<CompactionResult>。排空所捕获的 compaction 范围,并报告实际处理的文件数、字节数、耗时、回收的墓碑数,以及是否仍有剩余工作。 |
status() |
无 | 开销极小的 DatabaseStatus 快照,涵盖维护故障、重试与写反压。 |
logical_stats() |
无 | 精确的 Result<LogicalStats>,统计唯一存活键和字节数。它会扫描物理版本,可能产生 I/O。 |
physical_stats() |
无 | 开销极小的 PhysicalStats 度量值与进程生命周期计数器,涵盖 WAL、memtable、SSTable、缓存、停顿与写放大。 |
stats() |
无 | 已弃用的混合物理计数器,为源码兼容性保留。 |
close() |
消耗 Db |
刷写待处理写入,停止维护任务,成功时释放目录所有权。直接丢弃 Db 不构成干净关闭保证。 |
close_with_status() |
消耗 Db |
结构化的关闭形式;可区分存储错误与未解决的 flush 或 compaction 健康问题。 |
大多数数据库方法返回 DbError。流式迭代器的创建返回 DbError,而之后发现的失败则以 ScanError 形式产生。更底层的 Engine 与组件配置类型属于受支持的高级 API;其完整的字段和方法契约见 crate 文档。
基准测试
基准测试使用 TurboKV 0.6.0、fjall 2.11.2 和 redb 2.6.3,每组运行三次重复。吞吐量为每秒确认的键数;数值越高越好。
| 工作负载 | TurboKV Fast | TurboKV Durable | TurboKV Paranoid | fjall Buffer | redb Eventual |
|---|---|---|---|---|---|
| 顺序填充(1 键/事务) | 2,989,537 | 1,774,574 | 213 | 485,252 | 1,397(macOS barrier/事务) |
| 随机填充(1 键/事务) | 1,217,087 | 906,806 | 226 | 456,924 | 1,549(macOS barrier/事务) |
| 覆盖写(1 键/事务) | 1,278,894 | 929,340 | 210 | 446,733 | 1,516(macOS barrier/事务) |
| 顺序批量(100 键/事务) | 3,856,202 | 2,277,031 | 20,670 | 511,600 | 80,197 |
| 顺序批量(1,000 键/事务) | 3,724,635 | 2,380,390 | 162,938 | 572,671 | 134,636 |
Fast 模式禁用 WAL。Durable 模式写入可恢复的 WAL 记录,但不为每次确认同步到持久存储。Paranoid 模式在返回前执行该同步;因此其单键吞吐量受存储同步延迟限制,而显式批次可以把一次 barrier 摊薄到多个键上。
测试协议:200,000 个确定性的 20 字节键、400 字节值(84 MB 逻辑输入,超过 64 MiB memtable),单一调用方,所示处使用原子批次,禁用压缩与 block cache,且不清空 OS page cache。redb 2.6.3 的 Durability::Eventual 会为每个事务执行一次 macOS F_BARRIERFSYNC,而 TurboKV Recoverable 与 fjall Buffer 模式止步于各自的进程崩溃可恢复的 OS 缓存边界。批量化能摊薄 redb 那次固定的 barrier;因此其单键行应理解为架构背景,而非同口径的持久化对比。不同引擎之间的最终落盘耗时未做比较。
测量时间为 2026-08-28 至 29,硬件为 Apple M4(Mac16,1)、32 GiB 内存、macOS 15.3.2(24D81)、APFS、rustc 1.88.0。三个 TurboKV 列的完整原始重复次数、延迟百分位、离散度、依赖版本、字节核算与写放大数据见 mode JSON artifact
及其 text report。
fjall 与 redb 列来自对应的留存 cross-engine artifact。
完整方法论与复现命令见 benchmarks/README.md。


