项目介绍

类型 语言 star 简介
sled rust 4.6k 嵌入式数据库,全新设计,beta尚未稳定
LevelDB c++ 23.3k google开源,快速键值存储库,LSM树(牺牲了部分读性能,用来大幅提高写性能。)
skade/leveldb rust 118 leveldb的rust封装,即通过rust调用leveldb c++ api,key仅支持i32
leveldb-rs rust 22 用rust重写leveldb,完全兼容
RocksDB c++ 19.4k facebook开源,在LevelDB之上做了改进
rust-rocksdb rust 900 rocksdb的rust封装,即通过rust调用rocksdb c++ api
LMDB c 1.7k 即Lightning Memory-Mapped Database Manager 闪电内存映射数据库管理器。是一个基于B+ tree的数据库管理库,使用mmap访问存储
lmdb-rs rust 91 LMDB的rust封装,即通过rust调用lmdb c api
TiKV rust 9k 一个分布式 Key-Value store,采用 Raft 一致性协议保证数据的强一致性,以及稳定性,同时通过 Raft 的 Configuration Change 机制实现了系统的可扩展性,存储引擎使用了RocksDB

skade/leveldb局限性太大,key仅支持i32。TiKV功能强大,其分布式特性我们这里暂时用不到。由于要在rust中使用,这里主要考虑sled、rust-rocksdb、leveldb-rs、lmdb-rs这四种实现。

sled

已知问题

  • 如果可靠性是您的主要制约因素,请使用SQLite。sled是beta。
  • 如果存储价格性能是您的主要限制因素,请使用RocksDB。sled有时会占用太多空间。
  • 如果您有很少写入的多进程工作负载,请使用LMDB。底座设计用于长时间运行的高并发工作负载,例如有状态服务或更高级别的数据库。
  • 还很年轻,暂时应该被认为是不稳定的。
  • 磁盘格式将发生变化,在1.0.0发行版之前进行手动迁移!

如何使用


use sled::{Result,Config, Mode};

fn main() -> Result<()> {
	//LowSpace在这种模式下,数据库将做出有利于使用更少空间而不是支持尽可能高的写入吞吐量的决策。此模式还将努力减少碎片,因此会更频繁地重写数据
	//HighThroughput在这种模式下,数据库将尝试最大程度地提高写入吞吐量,同时潜在地使用更多的磁盘空间。
    let db = Config::new()
    .mode(Mode::HighThroughput)
    .path("mydir")
    .open()?;

    let k = b"k".to_vec();
    let v1 = b"v1".to_vec();
    let v2 = b"v2".to_vec();

    // set and get
    db.insert(k.clone(), v1.clone())?;
    assert_eq!(db.get(&k).unwrap().unwrap(), (v1));

    // compare and swap
    match db.compare_and_swap(k.clone(), Some(&v1), Some(v2.clone()))? {
        Ok(()) => println!("it worked!"),
        Err(sled::CompareAndSwapError { current: cur, proposed: _ }) => {
            println!("the actual current value is {:?}", cur)
        }
    }

    // scan forward
    let mut iter = db.range(k.as_slice()..);
    let (k1, v1) = iter.next().unwrap().unwrap();
    assert_eq!(v1, v2);
    assert_eq!(k1, k);
    assert_eq!(iter.next(), None);

    // deletion
    db.remove(&k)?;
    
    Ok(())
}

数据目录如下

mydir
├── DO_NOT_USE_THIS_DIRECTORY_FOR_ANYTHING
├── conf
├── db
├── heap
└── snap.0000000000000204

leveldb-rs

如何使用

use rusty_leveldb::{LdbIterator, Options, DB};

use std::env::args;
use std::io::{self, Write};
use std::iter::FromIterator;

fn get(db: &mut DB, k: &str) {
    match db.get(k.as_bytes()) {
        Some(v) => {
            if let Ok(s) = String::from_utf8(v.clone()) {
                println!("{} => {}", k, s);
            } else {
                println!("{} => {:?}", k, v);
            }
        }
        None => println!("{} => <not found>", k),
    }
}

fn put(db: &mut DB, k: &str, v: &str) {
    db.put(k.as_bytes(), v.as_bytes()).unwrap();
    db.flush().unwrap();
}

fn delete(db: &mut DB, k: &str) {
    db.delete(k.as_bytes()).unwrap();
    db.flush().unwrap();
}

fn iter(db: &mut DB) {
    let mut it = db.new_iter().unwrap();
    let (mut k, mut v) = (vec![], vec![]);
    let mut out = io::BufWriter::new(io::stdout());
    while it.advance() {
        it.current(&mut k, &mut v);
        out.write_all(&k).unwrap();
        out.write_all(b" => ").unwrap();
        out.write_all(&v).unwrap();
        out.write_all(b"\n").unwrap();
    }
}

fn compact(db: &mut DB, from: &str, to: &str) {
    db.compact_range(from.as_bytes(), to.as_bytes()).unwrap();
}

fn main() {
    let args = Vec::from_iter(args());

    if args.len() < 2 {
        panic!(
            "Usage: {} [get|put/set|delete|iter|compact] [key|from] [val|to]",
            args[0]
        );
    }

    let mut opt = Options::default();
    opt.reuse_logs = false;
    opt.reuse_manifest = false;
    opt.compression_type = rusty_leveldb::CompressionType::CompressionNone;
    let mut db = DB::open("tooldb", opt).unwrap();

    match args[1].as_str() {
        "get" => {
            if args.len() < 3 {
                panic!("Usage: {} get key", args[0]);
            }
            get(&mut db, &args[2]);
        }
        "put" | "set" => {
            if args.len() < 4 {
                panic!("Usage: {} put key val", args[0]);
            }
            put(&mut db, &args[2], &args[3]);
        }
        "delete" => {
            if args.len() < 3 {
                panic!("Usage: {} delete key", args[0]);
            }
            delete(&mut db, &args[2]);
        }
        "iter" => iter(&mut db),
        "compact" => {
            if args.len() < 4 {
                panic!("Usage: {} compact from to", args[0]);
            }
            compact(&mut db, &args[2], &args[3]);
        }
        _ => unimplemented!(),
    }
}

rust-rocksdb

如何使用

use std::{mem, sync::Arc, thread, time::Duration};

use pretty_assertions::assert_eq;

use rocksdb::{
    perf::get_memory_usage_stats, BlockBasedOptions, BottommostLevelCompaction, Cache,
    CompactOptions, DBCompactionStyle, Env, Error, FifoCompactOptions, IteratorMode, Options,
    PerfContext, PerfMetric, ReadOptions, SliceTransform, Snapshot, UniversalCompactOptions,
    UniversalCompactionStopStyle, WriteBatch, DB,
};
use util::DBPath;


#[test]
fn external() {
    let path = DBPath::new("_rust_rocksdb_externaltest");

    {
        let db = DB::open_default(&path).unwrap();

        assert!(db.put(b"k1", b"v1111").is_ok());

        let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1");

        assert_eq!(r.unwrap().unwrap(), b"v1111");
        assert!(db.delete(b"k1").is_ok());
        assert!(db.get(b"k1").unwrap().is_none());
    }
}

lmdb-rs

如何使用

use lmdb::{EnvBuilder, DbFlags};

fn main() {
    let env = EnvBuilder::new().open("test-lmdb", 0o777).unwrap();

    let db_handle = env.get_default_db(DbFlags::empty()).unwrap();
    let txn = env.new_transaction().unwrap();
    {
        let db = txn.bind(&db_handle); // get a database bound to this transaction

        let pairs = vec![("Albert", "Einstein",),
                         ("Joe", "Smith",),
                         ("Jack", "Daniels")];

        for &(name, surname) in pairs.iter() {
            db.set(&surname, &name).unwrap();
        }
    }

    // Note: `commit` is choosen to be explicit as
    // in case of failure it is responsibility of
    // the client to handle the error
    match txn.commit() {
        Err(_) => panic!("failed to commit!"),
        Ok(_) => ()
    }

    let reader = env.get_reader().unwrap();
    let db = reader.bind(&db_handle);
    let name = db.get::<&str>(&"Smith").unwrap();
    println!("It's {} Smith", name);
}

功能对比

类别 sled leveldb-rs rust-rocksdb lmdb-rs
事务(批量写) 支持 支持 支持 支持
压缩算法 zstd snappy snappy、lz4、zstd、zlib、bzip2 \
数据结构 lsm写入,b+tree读取 lsm lsm B+ tree
rust异步/多线程 支持 不支持 支持 支持

性能测试对比

测试数据仅供参考
macbook pro( Core i7 /16G),使用rust性能测试框架criterion,时间取中位值

插入不同的长度的key/value

类型(单位bytes) sled dermesser/leveldb-rs rust-rocksdb lmdb-rs
key/value 10/128 6.3429 us 7.4070 us 25.551 us 1.2887 us
key/value 10/1024 12.612 us 21.162 us 35.959 us 12.389 us
key/value 10/4096 7.7579 ms 67.716 us 83.034 us 30.920 us
key/value 10/8192 7.6942 ms 110.58 us 126.85 us 31.935 us
key/value 10/262144 36.024 ms 2.8490 ms 2.6729 ms 659.74 us
key/value 128/128 6.4928 us 10.405 us 25.168 us 1.9674 us
key/value 128/1024 14.181 us 23.435 us 36.920 us 9.4915 us
key/value 128/4096 7.6899 ms 71.338 us 90.285 us 20.711 us
key/value 128/8192 7.8685 ms 122.77 us 125.80 us 32.952 us
key/value 128/262144 36.234 ms 2.8922 ms 2.4643 ms 761.65 us
key/value 256/128 6.7232 us 12.599 us 28.777 us 2.4083 us
key/value 256/1024 13.955 us 25.972 us 39.344 us 7.7123 us
key/value 256/4096 7.6926 ms 74.074 us 88.930 us 29.819 us
key/value 256/8192 7.8424 ms 116.25 us 83.962 us 31.714 us
key/value 256/262144 36.820 ms 2.8694 ms 1.4018 ms 650.39 us
key/value 512/128 7.9187 us 15.932 us 16.031 us 2.8804 us
key/value 512/1024 2.6200 ms 31.438 us 20.967 us 17.665 us
key/value 512/4096 7.6336 ms 73.891 us 38.338 us 35.919 us
key/value 512/8192 7.7780 ms 128.78 us 53.688 us 37.614 us
key/value 512/262144 36.271 ms 2.9039 ms 1.1011 ms 635.37 us

lmdb MDB_MAXKEYSIZE值默认最大值为511,这里为了测试修改了源码默认值

monotonic insert/get/remove

key值固定,value为空

类型(单位条) sled dermesser/leveldb-rs rust-rocksdb lmdb-rs
inserts 1 661.11 ns 2.0548 us 6.0926 us 840.68 ns
get 1 630.05 ns 4.0336 us 1.3458 us 899.93 ns
remove 1 578.18 ns 1.8852 us 6.4212 us 867.66 ns
inserts 100w 1.5521 s 1.8899 s 4.9668 s 666.38 ms
get 100w 1.5316 s 5.3495 s 1.5867 s 663.98 ms
remove 100w 1.1838 s 1.8561 s 4.8582 s 255.59 ms
inserts 1000w 16.365 s 19.965 s 44.013 s 6.9606 s

random insert/get/remove

key值大小随机,1~65536bytes,value为空

类型 sled dermesser/leveldb-rs rust-rocksdb lmdb-rs
inserts 1.5588 us 3.0858 us 7.2210 us 464.96 ns
get 1.4756 us 15.385 us 2.1107 us 369.62 ns
remove 1.1267 us 2.7740 us 7.1509 us 23.588 ns

其他性能测试报告参考

  • http://www.lmdb.tech/bench/inmem/
  • https://www.influxdata.com/blog/benchmarking-leveldb-vs-rocksdb-vs-hyperleveldb-vs-lmdb-performance-for-influxdb/