6个版本

0.0.14 2024年7月23日
0.0.13 2024年7月22日

#43 in 数据库实现

Download history 227/week @ 2024-07-14 366/week @ 2024-07-21 49/week @ 2024-07-28

642 每月下载量

MIT 协议

465KB
7.5K SLoC

codecov Tests Crates.io Documentation Clippy Tests Contributor Covenant License: MIT

VelarixDB是一个基于LSM的存储引擎,旨在显著减少IO放大,从而提高存储设备的性能和耐用性。

简介

VelarixDB:设计用于减少IO放大

VelarixDB是一个持续进行中的项目(非生产就绪),旨在优化加载时间和压缩过程中的数据移动。受到WiscKey论文的启发,WiscKey:在SSD意识存储中将键与值分开,velarixdb旨在在传统键值存储中显著提高性能。

问题

在LevelDB或RocksDB的压缩过程中,在最坏的情况下,需要读取、排序和重写多达10个SSTable文件,因为不允许在所有从第1级开始的sstables中键重叠。假设在合并一个级别的SSTables之后,下一个级别超过了其阈值,压缩可以从第0级级联到第6级,这意味着整体写放大可以达到50(忽略第一个压缩级别)。[参考资料 -> 官方LevelDB压缩过程文档]。这种重复的数据移动会导致SSD的磨损加剧,由于写周期数高而缩短其寿命。目标是尽量减少压缩过程中移动的数据量,从而减少重写的数据量,并延长设备的寿命。

解决方案

为了解决这个问题,我们关注一个键是否已被删除或更新。在压缩过程中包含值(通常比键大)是不必要的,这会放大读取和写入的数据量。因此,我们将键和值分开存储。具体来说,我们使用32位整数将值偏移量映射到键上。

这种方法减少了压缩过程中读取、写入和移动的数据量,从而提高了性能并减少了存储设备(尤其是SSD)的磨损。通过最小化数据移动,我们不仅提高了数据库的效率,还显著延长了底层存储硬件的寿命。

性能优势

根据WiscKey论文中提供的基准测试,实现可以比LevelDB和RocksDB

  • 2.5倍到111倍提高数据库加载性能
  • 1.6倍到14倍提高随机查找性能

设计用于异步运行时

基于操作系统的内核级别异步IO的介绍和效率,例如Linux内核的io_uring,VelarixDB被设计为异步运行时。在这种情况下,是Tokio运行时。Tokio允许进行高效和可扩展的异步操作,充分利用了现代的多核处理器。坦白说,大多数操作系统文件系统目前还没有提供异步API,但Tokio使用线程池来卸载阻塞的文件系统操作。这意味着尽管文件系统操作本身在操作系统级别是阻塞的,但Tokio可以处理它们而不阻塞主异步任务执行器。
未来Tokio可能会采用io_uring

免责声明

请注意,velarixdb仍在开发中,尚未准备好投入生产。

基本功能

  • 原子操作:Put、Get、Delete和Update
  • 100%安全稳定的Rust
  • 将键与值分离,减少压缩过程中移动的数据量(即,减少IO放大)
  • 垃圾回收器
  • 使用Crossbeam SkipMap的无锁memtable(无Mutex
  • Tokio运行时,用于高效线程管理
  • 布隆过滤器,用于快速内存中键搜索
  • 使用Value Log进行崩溃恢复
  • 索引,用于改进排序字符串表(SSTs)上的搜索
  • 键范围,用于存储SST中的最大和最小键
  • 尺寸分层压缩策略(STCS)

待办事项

  • 快照隔离
  • 块缓存
  • 批量写入
  • 范围查询
  • Snappy压缩
  • 值缓冲区,以保持值在内存中,并仅批量刷新以减少IO(正在研究)
  • 校验和,用于检测数据损坏
  • 分层压缩(LCS)、时间窗口压缩(TCS)和统一压缩(UCS)
  • 监控模块,用于持续监控和生成报告

不是

  • 独立的服务器
  • 关系数据库
  • 宽列数据库:它没有列的概念

约束

  • 键限制为65,536字节,值限制为2^32字节。更大的键和值会对性能产生更大的影响。
  • 类似于任何典型的键值存储,键按字典顺序存储。如果您存储整数键(例如,时间序列数据),请使用大端形式以保持局部性。

基本用法

cargo add velarixdb
use velarixdb::db::DataStore;
# use tempfile::tempdir;

#[tokio::main]
async fn main() {
    let root = tempdir().unwrap();
    let path = root.path().join("velarix");
    let mut store = DataStore::open("big_tech", path).await.unwrap(); // handle IO error

    store.put("apple", "tim cook").await;
    store.put("google", "sundar pichai").await;
    store.put("nvidia", "jensen huang").await;
    store.put("microsoft", "satya nadella").await;
    store.put("meta", "mark zuckerberg").await;
    store.put("openai", "sam altman").await;


    let entry1 = store.get("apple").await.unwrap(); // Handle error
    let entry2 = store.get("google").await.unwrap();
    let entry3 = store.get("nvidia").await.unwrap();
    let entry4 = store.get("microsoft").await.unwrap();
    let entry5 = store.get("meta").await.unwrap();
    let entry6 = store.get("openai").await.unwrap();
    let entry7 = store.get("***not_found_key**").await.unwrap();

    assert_eq!(std::str::from_utf8(&entry1.unwrap().val).unwrap(), "tim cook");
    assert_eq!(std::str::from_utf8(&entry2.unwrap().val).unwrap(), "sundar pichai");
    assert_eq!(std::str::from_utf8(&entry3.unwrap().val).unwrap(), "jensen huang");
    assert_eq!(std::str::from_utf8(&entry4.unwrap().val).unwrap(), "satya nadella");
    assert_eq!(std::str::from_utf8(&entry5.unwrap().val).unwrap(), "mark zuckerberg");
    assert_eq!(std::str::from_utf8(&entry6.unwrap().val).unwrap(), "sam altman");
    assert!(entry7.is_none());

    // Remove an entry
    store.delete("apple").await.unwrap();

    // Update an entry
    let success = store.update("microsoft", "elon musk").await;
    assert!(success.is_ok());
}

存储JSON

use serde::{Deserialize, Serialize};
use serde_json;
use velarixdb::db::DataStore;
# use tempfile::tempdir;

#[tokio::main]
async fn main() {
    let root = tempdir().unwrap();
    let path = root.path().join("velarix");
    let mut store = DataStore::open("big_tech", path).await.unwrap(); // handle IO error

    #[derive(Serialize, Deserialize)]
    struct BigTech {
        name: String,
        rank: i32,
    }
    let new_entry = BigTech {
        name: String::from("Google"),
        rank: 50,
    };
    let json_string = serde_json::to_string(&new_entry).unwrap();

    let res = store.put("google", json_string).await;
    assert!(res.is_ok());

    let entry = store.get("google").await.unwrap().unwrap();
    let entry_string = std::str::from_utf8(&entry.val).unwrap();
    let big_tech: BigTech = serde_json::from_str(&entry_string).unwrap();

    assert_eq!(big_tech.name, new_entry.name);
    assert_eq!(big_tech.rank, new_entry.rank);
}

示例

有关实际示例,请参阅此处

依赖项

~12–25MB
~368K SLoC