100 个版本 ()

1.0.0-alpha.1212024 年 4 月 14 日
1.0.0-alpha.1202023 年 12 月 25 日
1.0.0-alpha.1182023 年 9 月 11 日
1.0.0-alpha.1032023 年 7 月 29 日
0.14.1 2017 年 9 月 9 日

#20 in 并发

Download history 31110/week @ 2024-04-25 23742/week @ 2024-05-02 29024/week @ 2024-05-09 26061/week @ 2024-05-16 24951/week @ 2024-05-23 28717/week @ 2024-05-30 22880/week @ 2024-06-06 29838/week @ 2024-06-13 26645/week @ 2024-06-20 22101/week @ 2024-06-27 22190/week @ 2024-07-04 32086/week @ 2024-07-11 37018/week @ 2024-07-18 43196/week @ 2024-07-25 42145/week @ 2024-08-01 38430/week @ 2024-08-08

166,204 每月下载量
395 个 Crates 中使用 (288 个直接使用)

MIT/Apache

400KB
5.5K SLoC

为我们买杯咖啡以将其转换为数据库
文档
与我们聊天关于数据库

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()?;

如果您想处理结构化数据而不支付昂贵的反序列化成本,请查看 结构化 示例!

功能

  • API 与线程安全的 BTreeMap<[u8], [u8]>
  • 可序列化(ACID)的(原子)事务,可在多个键空间中原子地读取和写入多个键。
  • 完全原子的单键操作,包括 比较和交换
  • 零拷贝读取
  • 写入批处理
  • 订阅键前缀的变化
  • 多个键空间
  • 合并运算符
  • 对项范围的迭代器(正向和反向)
  • 一个可生成每秒 75-125 百万唯一 ID 的崩溃安全单调 ID 生成器
  • zstd 压缩(使用 compression 构建功能,默认禁用)
  • 可扩展 CPU 的无锁实现
  • 针对闪存的日志结构化存储优化
  • 使用现代 B 树技术,如前缀编码和后缀截断,以降低具有共享前缀的长键的存储成本。如果键长度相同且顺序排列,则系统可以在大多数情况下避免存储 99%+ 的键数据,本质上就像是一个学习型索引。

期望、注意事项、建议

  • 可能第一件看起来奇怪的事情就是 IVec 类型。这是一个可内联的 Arc 切片,可以使某些事情更加高效。
  • 持久性:默认情况下,sled 会自动每 500 毫秒 fsync 一次,这可以通过 flush_every_ms 配置,或者您可以在操作后手动调用 flush / flush_async
  • 事务是乐观的 - 除非它是 幂等的,否则不要在事务闭包中与外部状态交互或执行 IO。
  • 内部树节点优化:sled 对具有相似前缀的、在范围中分组的长键进行前缀编码,以及后缀截断以进一步减少长键的索引成本。如果键或值都是相同长度,节点将跳过可能昂贵的长度和偏移量指针(键或值分别跟踪,不用担心将键的长度设置为与值的长度相同),因此如果您使用固定长度的键或值,可能会略微提高空间利用率。这也使得使用 结构化访问 更加容易。
  • 目前 sled 不支持多个打开实例。请在您进程的生命周期内保持 sled 打开。使用全局 lazy_static sled 实例完全安全,而且通常非常方便,尽管有正常的全局变量的权衡。每个操作都是线程安全的,大多数在底层使用无锁算法实现,以避免在热点路径上阻塞。

性能

  • 类似 LSM 树 的写入性能,具有类似 传统 B+ 树 的读取性能
  • 在 16 个核心上,小数据集上,95% 读取 5% 写入的情况下,不到一分钟就处理了超过十亿次操作
  • 自己衡量工作负载,而不是依赖某些营销的虚构工作负载

关于字典序和字节序的说明

如果您想以与 sled 的迭代器和有序操作兼容的方式存储数值键,请记住以大端形式存储您的数值项。小端(许多事物的默认值)在处理超过 256 个项(超过 1 个字节)之前通常会看起来做对了,但会导致序列化字节的字典序与它们反序列化数值形式的字典序不同。

  • Rust 整数类型具有内置的 to_be_bytesfrom_be_bytes 方法
  • bincode 可以配置 为以大端形式存储整数类型。

与异步交互

如果您的数据集完全位于缓存中(通过在启动时将缓存设置为足够大的值并执行完整迭代来实现),那么所有读取和写入都是非阻塞的,并且对异步友好,无需使用 Futures 或异步运行时。

为了在异步任务上挂起异步等待写入的持久性,我们支持 flush_async 方法,该方法返回一个 Future,如果您的异步任务需要高持久性保证并且您愿意承担 fsync 的延迟成本,则可以等待其完成。请注意,sled 会自动尝试在后台将所有数据同步到磁盘,每秒几次,而不会阻塞用户线程。

我们支持异步订阅发生在键前缀上的事件,因为 Subscriber 结构体实现了 Future<Output=Option<Event>>

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+树那样一次重写整个页面。在页面读取时,我们并发地在日志中执行散列收集读取,以从其碎片中实例化页面。请参阅架构概述,了解我们目前的位置以及我们的展望!

哲学

  1. 不要让用户思考。界面应该是直观的。
  2. 不要让用户惊讶于性能陷阱。
  3. 不要打扰操作员。将学术界的可靠性技术应用于实际应用。
  4. 不要使用过多的电力。我们的数据结构应该利用现代硬件的优势。

已知问题,警告

  • 如果您将可靠性作为主要约束,请使用SQLite。sled处于测试阶段。
  • 如果您将存储价格性能作为主要约束,请使用RocksDB。sled有时会占用过多空间。
  • 如果您有一个很少写入的多进程工作负载,请使用LMDB。sled是为使用长时间运行、高度并发的负载而设计的,例如有状态服务或高级数据库。
  • 相当年轻,目前应视为不稳定。
  • 磁盘上的格式将发生变化,需要在进行1.0.0版本发布之前进行手动迁移

优先级

  1. Sled的存储子系统正在模块化地重新编写,作为komora项目的一部分,特别是marble存储引擎。这将显著降低sled的磁盘空间使用(空间放大)和垃圾回收开销(写放大)。
  2. 树节点的内存布局正在完全重新编写,以减少碎片并消除序列化成本。
  3. 合并运算符功能将变为类似传统数据库触发器的触发器功能,允许在触发它的相同原子写入批次中修改状态,以保留具有反应式语义的可序列化性。

资金功能开发

喜欢我们正在做的事情吗?通过GitHub Sponsors帮助我们!

依赖关系

~6–12MB
~133K SLoC