spacejam/sled

GitHub: spacejam/sled

一款用 Rust 编写的高性能嵌入式键值数据库,提供 ACID 事务支持和无锁并发访问。

Stars: 8958 | Forks: 421

key value
documentation
chat about databases with us

# sled - ~~从这里开始下坡吧!!!~~ 一个嵌入式数据库。 ``` let tree = sled::open("/tmp/welcome-to-sled")?; // insert and get, similar to std's BTreeMap let old_value = tree.insert("key", "value")?; assert_eq!( tree.get(&"key")?, Some(sled::IVec::from("value")), ); // range queries for kv_result in tree.range("key_1".."key_9") {} // deletion let old_value = tree.remove(&"key")?; // atomic compare and swap tree.compare_and_swap( "key", Some("current_value"), Some("new_value"), )?; // block until all operations are stable on disk // (flush_async also available to get a Future) tree.flush()?; ``` $${\color{red}本 \space README \space 与 \space 主分支 \space 不同步,\space 主分支 \space 包含 \space 正在进行 \space 大规模 \space 重写 }$$ 如果您想使用结构化数据,同时避免昂贵的反序列化成本,请查看 [structured](examples/structured.rs) 示例! # 功能特性 * 类似于线程安全 `BTreeMap<[u8], [u8]>` 的 [API](https://docs.rs/sled) * 可序列化 (ACID) [事务](https://docs.rs/sled/latest/sled/struct.Tree.html#method.transaction) 用于原子性地在多个 keyspace 中读取和写入多个键。 * 完全原子性的单键操作,包括 [比较并交换](https://docs.rs/sled/latest/sled/struct.Tree.html#method.compare_and_swap) * 零拷贝读取 * [写入批次](https://docs.rs/sled/latest/sled/struct.Tree.html#method.apply_batch) * [订阅键前缀的变更](https://docs.rs/sled/latest/sled/struct.Tree.html#method.watch_prefix) * [多个 keyspace](https://docs.rs/sled/latest/sled/struct.Db.html#method.open_tree) * [合并运算符](https://docs.rs/sled/latest/sled/doc/merge_operators/index.html) * 支持范围项的正向和反向迭代器 * 一个崩溃安全的单调 [ID 生成器](https://docs.rs/sled/latest/sled/struct.Db.html#method.generate_id) 每秒能够生成 7500 万到 1.25 亿个唯一 ID * [zstd](https://github.com/facebook/zstd) 压缩(使用 `compression` 构建特性,默认禁用) * cpu 可扩展的无锁实现 * 针对闪存优化的日志结构化存储 * 使用现代 B 树技术,如前缀编码和后缀 截断,以减少具有共享 前缀的长键的存储成本。如果键长度相同且 连续,系统在大多数情况下可以避免 存储 99% 以上的键数据, 本质上就像一个学习型索引 # 预期、注意事项与建议 * 也许看起来最奇怪的事情之一是 `IVec` 类型。 这是一个可内联的 `Arc` 切片,使某些事情更高效。 * 持久性:**sled 默认每 500ms 自动 fsync**, 这可以通过 `flush_every_ms` 配置,或者您可以 在操作后手动调用 `flush` / `flush_async`。 * **事务是乐观的** - 除非是 [幂等](https://en.wikipedia.org/wiki/Idempotent)的,否则不要从事务闭包中 与外部状态交互或执行 IO。 * 内部树节点优化:sled 对 在范围内分组在一起的具有相似前缀的 长键执行前缀编码, 以及后缀截断以进一步减少长键的 索引成本。如果键或值 长度全部相同(单独跟踪,不必 担心让键与值长度相同),节点将跳过潜在昂贵的长度和偏移指针,因此如果您使用固定长度的键或值,可能会稍微改善空间使用。这也使得 更容易使用 [结构化访问](examples/structured.rs)。 * sled 暂时不支持多个打开的实例。请 在进程的生命周期内保持 sled 打开。使用全局 lazy_static sled 实例是完全 安全的,而且通常非常方便, 除了正常的全局变量权衡之外。每个操作都是线程安全的, 并且大多数在底层使用无锁算法实现,避免 在热路径中阻塞。 # 性能 * 类似于 [LSM tree](https://en.wikipedia.org/wiki/Log-structured_merge-tree) 的写入性能 以及类似于 [传统 B+ tree](https://en.wikipedia.org/wiki/B%2B_tree) 的读取性能 * 在 16 核的小数据集上,95% 读取 5% 写入的情况下,一分钟内完成超过 10 亿次操作 * 测量您自己的工作负载,而不是依赖一些针对人为工作负载的营销宣传 # 关于字典序和字节序的说明 如果您想以一种能很好地配合 sled 迭代器和有序操作的方式存储数字键,请记住以 big-endian 形式存储您的数字项。Little endian(许多事物的默认设置)通常看起来在做正确的事情,直到您开始处理超过 256 个项(超过 1 个字节),导致序列化字节的字典序与其反序列化的数字形式的字典序发生偏离。 * Rust 整数类型具有内置的 `to_be_bytes` 和 `from_be_bytes` [方法](https://doc.rust-lang.org/std/primitive.u64.html#method.from_be_bytes)。 * bincode [可以配置](https://docs.rs/bincode/1.2.0/bincode/struct.Config.html#method.big_endian) 为以 big-endian 形式存储整数类型。 # 与 Async 的交互 如果您的数据集完全驻留在缓存中(可在启动时通过将缓存设置为足够大的值并执行完整迭代来实现),那么所有读取和写入都是非阻塞且对异步友好的,无需使用 Futures 或异步运行时。 为了在写入的持久性上异步挂起您的异步任务,我们支持 [`flush_async` 方法](https://docs.rs/sled/latest/sled/struct.Tree.html#method.flush_async), 它返回一个 Future,如果您的异步任务需要高持久性保证,并且您愿意支付 fsync 的延迟成本,则可以等待其完成。 请注意,sled 会自动尝试在后台每秒多次 将所有数据同步到磁盘,而不会阻塞用户线程。 我们支持对键前缀上发生的事件进行异步订阅,因为 `Subscriber` 结构体实现了 `Future>`: ``` let sled = sled::open("my_db").unwrap(); let mut sub = sled.watch_prefix(""); sled.insert(b"a", b"a").unwrap(); extreme::run(async move { while let Some(event) = (&mut sub).await { println!("got event {:?}", event); } }); ``` # 最低支持的 Rust 版本 (MSRV) 我们支持 Rust 1.62 及更高版本。 # 架构 无锁日志之上的无锁页缓存之上的无锁树。页缓存将 部分页片段分散在日志中,而不是像历史上用于 旋转磁盘的 B+ 树那样一次重写整个页。在读取页时,我们并发地 在日志中进行分散-聚集读取,以从其片段中具体化该页。 请查看 [架构展望](https://github.com/spacejam/sled/wiki/sled-architectural-outlook) 以获取有关我们所处位置以及我们对事物发展方向的更详细概述! # 理念 1. 不要让用户思考。界面应该是显而易见的。 2. 不要用性能陷阱让用户感到惊讶。 3. 不要唤醒运维人员。将学术界的可靠性技术带入现实世界的实践中。 4. 不要消耗太多电力。我们的数据结构应该发挥现代硬件的优势。 # 已知问题与警告 * 如果可靠性是您的主要约束,请使用 SQLite。sled 是测试版。 * 如果存储性价比是您的主要约束,请使用 RocksDB。sled 有时会使用过多空间。 * 如果您有一个很少写入的多进程工作负载,请使用 LMDB。sled 的架构专为长时间运行、高度并发的工作负载而设计,例如有状态服务或更高级别的数据库。 * 相当年轻,暂时应被视为不稳定。 * 磁盘上的格式将以需要 [手动迁移](https://docs.rs/sled/latest/sled/struct.Db.html#method.export) 的方式发生变化,在 `1.0.0` 版本发布之前! # 优先事项 1. 作为 [komora 项目](https://github.com/komora-io) 的一部分,sled 的存储子系统正在以模块化为基础进行完全重写,特别是 marble 存储引擎。这将大大降低 sled 的磁盘空间使用(空间放大)和垃圾回收开销(写放大)。 2. 树节点的内存布局正在完全重写,以减少碎片化并消除序列化成本。 3. 合并运算符功能将转变为类似于传统数据库触发器的触发器功能,允许状态作为触发它的同一原子 writebatch 的一部分进行修改,以便通过响应式语义保持可序列化性。 # 资助功能开发 喜欢我们正在做的事情吗?通过 [GitHub Sponsors](https://github.com/sponsors/spacejam) 帮助我们!
标签:ACID事务, BTreeMap, HTTP工具, KV数据库, Rust, sled, 原子操作, 可视化界面, 嵌入式存储, 嵌入式数据库, 数据存储引擎, 本地存储, 磁盘持久化, 结构化数据, 网络流量审计, 通知系统, 键值存储