# FlowDB 介绍:一个纯 Rust 的嵌入式 LSM 引擎与 JSON 文档数据库
## 前言
上周在优化一个 IoT 边缘网关的存储层时,我被 RocksDB 的 C 依赖和庞大体量困扰。搜索后发现 FlowDB 这个 Rust 原生项目——**零 C 依赖、零异步运行时、纯 Rust 实现**的嵌入式引擎,而且内置了一个 **IndexedDB 兼容的 JSON 文档层**。
一番测试下来,几个数字让我印象深刻:
- 点查 **600 万 ops/s**,是 RocksDB 的 **11 倍**
- 8 线程并发写 **940 万 ops/s**,是 RocksDB 的 **2 倍**
- 二进制仅 **~680KB**,零 C 依赖,零异步依赖
本文从产品能力、技术实现、性能优势三个角度介绍 FlowDB。如果你在 Rust 社区寻找一个可嵌入的存储引擎,或者对 LSM 的工程优化感兴趣,这篇文章应该对你有帮助。
---
## 一、产品能力:一个引擎,两种用法
FlowDB 提供两层 API:
### 底层:LSM-Tree Key-Value 引擎
```rust
use flowdb::{Engine, Config};
let engine = Engine::open(Config::new("/tmp/mydb"))?;
engine.put(b"key", b"value")?;
let val = engine.get(b"key")?; // Option<Vec<u8>>
engine.delete(b"key")?;
```
支持前缀扫描、范围扫描、批量写入、迭代器。这是一个完整的 LSM 引擎,但功能定位很克制——不像 RocksDB 那样有数百个配置项。
### 上层:JsonDB 文档层
JsonDB 是构建在 LSM 引擎之上的 JSON 文档数据库,兼容 **IndexedDB API 范式**(object store + index):
```rust
use flowdb::{Engine, Config, JsonDB, Transaction, QueryBuilder};
use serde_json::json;
let engine = Engine::open(Config::new("/tmp/myjsondb"))?;
let jsondb = JsonDB::open(engine)?;
// 创建 store 和索引
let store = jsondb.create_object_store("users", "id")?;
jsondb.create_index("users", "email", true)?; // unique index
// 写入文档
jsondb.put("users", json!({"id": "u1", "name": "Alice", "email": "alice@x.com", "age": 30}))?;
// 按主键查询
let doc = jsondb.get::<Value>("users", "u1")?;
// 按索引查询
let doc = jsondb.get_by_index::<Value>("users", "email", "alice@x.com")?;
// 范围扫描 + 排序
let results = jsondb.range_by_index::<Value>(
"users", "age", 20..=40, SortDir::Asc, None
)?;
// 声明式查询 (自动选索引)
let results = QueryBuilder::new("users")
.where_eq("email", "bob@x.com")
.collect(&jsondb)?;
```
**核心特性:**
- 多索引支持(唯一/非唯一,单字段/复合)
- 嵌套字段查询(`"address.city"` 点号路径)
- 事务支持(OCC + MVCC,批量原子提交,自动回滚)
- 自动递增主键
- 范围扫描、分页、排序
**典型场景:** 边缘计算、移动端、IoT 设备、嵌入式系统、浏览器引擎(WASM 兼容)。
---
## 二、技术实现:一个「做减法」的架构故事
FlowDB 有趣的地方不在于它加了什么,而在于它 **去掉了什么**。
### 第一刀:砍掉整个 Server 层
早期的 FlowDB 是一个完整的时间序列数据库——HTTP API、UDP 接收、Admin UI、Auth、TOML 配置……v0.2.1 一刀砍掉 40% 代码,回归「嵌入式引擎」定位。
### 第二刀:砍掉异步运行时
v0.3.0 之前,FlowDB 的公共 API 是 async fn,但作者逐一审查发现 **12 个 async 方法中 8 个没有真正 await**。最终全面移除 Tokio,改为 `std::thread::spawn` + `AtomicBool` 停止信号。
现在所有公共 API 都是同步 fn,无异步依赖,二进制缩小 **40%**。
### 砍掉之后留下的架构
```
写入路径:Client → 预编码(无锁) → WriteWorker(Mutex) → WAL + MemTable → (后台flush) SST
读取路径:Client → MemTable → 块索引 → Bloom过滤器 → SST(mmap, LRU缓存)
后台线程:flush / compact / gc / wal_sync (std::thread 循环)
```
### 几个值得一提的设计
**1. Vec 做活跃 memtable**
大部分 LSM(RocksDB、sled)用 BTreeMap 或 SkipList 做 memtable,写入时有排序开销。FlowDB 用 `Vec::push` **O(1) 追加**,freeze 时一次性排序 O(n log n)。延迟排序的代价在主写路径之外。
**2. 锁外预编码**
WAL 记录在**获取写锁之前**完成序列化和 checksum 计算。临界区内只做 buffer 追加 + memtable 插入。
**3. 五级读取级联**
点查穿透路径:MemTable → BlockMetaIndex(二维: 按key + 按时间) → BloomFilter → block 内二分 → LZ4/Zstd 解压。每级都是低成本的过滤。
**4. 按 key 粒度的 Bloom 过滤器**
时间序列场景下,一个 key 可能对应上千个时间戳。传统按 (key, ts) 建 bloom 命中率低,FlowDB 按唯一 key 建 bloom,效果显著提升。
**5. 双模压缩**
flush 用 LZ4(写路径,要快),compaction 用 Zstd(后台,要省)。
**6. 复合索引的类型标记编码**
索引值编码时带上类型 tag(数字 vs 字符串 vs 字节),保证 Lexicographic 排序正确,支持混合字段范围查询。
### JsonDB 的存储布局
JsonDB 和引擎共享 keyspace,通过 key 前缀区分:
```
0x01 {store} \x00 {pk} → JSON 文档
0x02 {store} \x00 {index} \x00 {val} \x00 {pk} → 索引条目
0x03 {store} → Schema (StoreDef)
0x04 {store} → 自增计数器
```
事务实现采用 **OCC + MVCC**——写入缓冲在 HashMap,commit 时通过 `write_internal` 单次原子提交,唯一约束在提交时校验,drop 自动回滚。
---
## 三、性能优势
### 与 RocksDB 的对比
M 系列芯片, 100K records, 128B value:
| 顺序写 | 4.5M ops/s | 3.1M ops/s | **1.42x** |
| 8线程并发写 | 9.4M ops/s | 4.7M ops/s | **2.02x** |
| 点查询 | 6.0M ops/s | 549K ops/s | **10.95x** |
| 前缀扫描 ~200条 | 72K ops/s | 11K ops/s | **6.39x** |
| 全表扫描 200K条 | 65 ops/s | 40 ops/s | **1.63x** |
### JsonDB 文档层性能
| 单文档写入 | ~121 docs/s |
| 批量写入 (100/批) | ~7,057 docs/s |
| 主键点查 | ~244,741 ops/s |
| 索引等值查找 | ~9,402 queries/s |
(写入瓶颈在 fsync,可用 SyncMode::IntervalMs 或批量提升)
### 为什么点查比 RocksDB 快 11 倍?
1. **Vec memtable** — 读路径上活跃数据无 BTreeMap 遍历开销
2. **Key 级 bloom** — 命中率远高于 (key, ts) 级
3. **无 CGo/CXX 跨语言开销** — 纯 Rust 调用,无需 FFI
4. **配置极简** — 不做 RocksDB 那种上百项调优,默认就快
5. **BlockMetaIndex 二维索引** — 按 key 和按时间双索引加速
---
## 四、适用场景与局限
### 适合
- 需要嵌入式存储的 Rust 应用(边缘计算、IoT、桌面 App)
- 对二进制体积敏感的场景(~680KB)
- 需要 JSON 文档 + 索引但不想引入 MongoDB / Couchbase
- 需要确定性性能,不想被 GC/异步运行时干扰
### 局限
- **单进程**,无网络层,无集群
- **无预写日志的事务性靠 fsync**,高频小事务 fsync 是瓶颈(批量可解)
- 项目较新(v0.6.0),生态尚在建设
---
## 五、快速上手
```toml
[dependencies]
flowdb = "0.6"
```
```rust
use flowdb::{Engine, Config, JsonDB, QueryBuilder};
use serde_json::{json, Value};
let engine = Engine::open(Config::new("/tmp/flowdb_demo"))?;
let db = JsonDB::open(engine)?;
db.create_object_store("notes", "id")?;
db.create_index("notes", "title", false)?;
db.put("notes", json!({"id": "1", "title": "Hello RustCC", "tags": ["rust", "database"]}))?;
let result = QueryBuilder::new("notes")
.where_eq("title", "Hello RustCC")
.collect::<Value>(&db)?;
println!("{:?}", result);
```
---
## 写在最后
FlowDB 给我的启发是:**做减法也是一种工程能力**。砍掉 Server 层、砍掉异步、砍掉复杂的配置选项,换来的是极简的 API、可预测的性能、以及真正「嵌入」的轻盈体态。
如果你在做一个需要嵌入式存储的 Rust 项目,或者对 LSM 引擎的 Rust 实现感兴趣,不妨试试 FlowDB。
**GitHub:** https://github.com/restsend/flowdb
**crates.io:** `flowdb`
欢迎 issue / PR / 讨论。