Skip to content

Commit

Permalink
Merge pull request #52 from fjall-rs/v2
Browse files Browse the repository at this point in the history
V2
  • Loading branch information
marvin-j97 authored Sep 14, 2024
2 parents 188213b + 7a1fb4b commit 0a89c1a
Show file tree
Hide file tree
Showing 131 changed files with 9,365 additions and 5,130 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,21 @@ jobs:
examples/kv -> target
- name: Install cargo-all-features
run: cargo install cargo-all-features
- uses: taiki-e/install-action@nextest
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy
- name: Build permutations
run: cargo build-all-features
- name: Run tests
run: cargo test-all-features -r -v -- --nocapture
env:
RUST_LOG: debug
run: cargo nextest run --all-features
- name: Run doc tests
run: cargo test --doc
- name: Build & test LSM examples
run: node compile_examples.mjs
cross:
timeout-minutes: 20
timeout-minutes: 15
name: cross
strategy:
matrix:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Cargo.lock
/old_*
.test*
.block_index_test
.bench
46 changes: 34 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "lsm-tree"
description = "A K.I.S.S. implementation of log-structured merge trees (LSM-trees/LSMTs)"
license = "MIT OR Apache-2.0"
version = "1.5.0"
version = "2.0.0-pre.0"
edition = "2021"
rust-version = "1.74.0"
readme = "README.md"
Expand All @@ -18,24 +18,28 @@ path = "src/lib.rs"

[features]
default = []
bloom = ["dep:seahash"]
all = ["bloom"]
lz4 = ["dep:lz4_flex"]
miniz = ["dep:miniz_oxide"]
bloom = []
all = ["bloom", "lz4", "miniz"]

[dependencies]
byteorder = "1.5.0"
crc32fast = "1.4.2"
crossbeam-skiplist = "0.1.3"
double-ended-peekable = "0.1.0"
enum_dispatch = "0.3.13"
guardian = "1.1.0"
log = "0.4.21"
lz4_flex = "0.11.3"
log = "0.4.22"
lz4_flex = { version = "0.11.3", optional = true }
miniz_oxide = { version = "0.7.4", optional = true }
path-absolutize = "3.1.1"
quick_cache = { version = "0.6.0", default-features = false, features = [
"ahash",
] }
seahash = { version = "4.1.0", optional = true }
quick_cache = { version = "0.6.5", default-features = false, features = [] }
self_cell = "1.0.4"
tempfile = "3.10.1"
smallvec = { version = "1.13.2" }
tempfile = "3.12.0"
value-log = "1.0.0-pre.2"
varint-rs = "2.2.0"
xxhash-rust = { version = "0.8.12", features = ["xxh3"] }

[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
Expand All @@ -46,17 +50,35 @@ test-log = "0.2.16"
[package.metadata.cargo-all-features]
denylist = ["all"]

[[bench]]
name = "tli"
harness = false
path = "benches/tli.rs"
required-features = []

[[bench]]
name = "memtable"
harness = false
path = "benches/memtable.rs"
required-features = []

[[bench]]
name = "bloom"
harness = false
path = "benches/bloom.rs"
required-features = ["bloom"]

[[bench]]
name = "block"
harness = false
path = "benches/block.rs"
required-features = ["lz4", "miniz"]

[[bench]]
name = "tree"
harness = false
path = "benches/tree.rs"
required-features = []
required-features = ["lz4", "miniz"]

[[bench]]
name = "level_manifest"
Expand Down
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[![docs.rs](https://img.shields.io/docsrs/lsm-tree?color=green)](https://docs.rs/lsm-tree)
[![Crates.io](https://img.shields.io/crates/v/lsm-tree?color=blue)](https://crates.io/crates/lsm-tree)
![MSRV](https://img.shields.io/badge/MSRV-1.74.0-blue)
[![dependency status](https://deps.rs/repo/github/fjall-rs/lsm-tree/status.svg)](https://deps.rs/repo/github/fjall-rs/lsm-tree)

A K.I.S.S. implementation of log-structured merge trees (LSM-trees/LSMTs) in Rust.

Expand All @@ -19,29 +20,47 @@ This is the most feature-rich LSM-tree implementation in Rust! It features:

- Thread-safe BTreeMap-like API
- 100% safe & stable Rust
- Block-based tables with LZ4 compression
- Block-based tables with compression support
- Range & prefix searching with forward and reverse iteration
- Size-tiered, (concurrent) Levelled and FIFO compaction
- Size-tiered, (concurrent) Leveled and FIFO compaction
- Multi-threaded flushing (immutable/sealed memtables)
- Partitioned block index to reduce memory footprint and keep startup time short [[1]](#footnotes)
- Block caching to keep hot data in memory
- Bloom filters to increase point lookup performance (`bloom` feature, disabled by default)
- Snapshots (MVCC)
- Key-value separation (optional) [[2]](#footnotes)
- Single deletion tombstones ("weak" deletion)

Keys are limited to 65536 bytes, values are limited to 2^32 bytes. As is normal with any kind of storage
engine, larger keys and values have a bigger performance impact.

## Feature flags

#### bloom
### lz4

Allows using `LZ4` compression, powered by [`lz4_flex`](https://github.com/PSeitz/lz4_flex).

*Disabled by default.*

### miniz

Allows using `DEFLATE/zlib` compression, powered by [`miniz_oxide`](https://github.com/Frommi/miniz_oxide).

*Disabled by default.*

### bloom

Uses bloom filters to reduce superfluous disk I/O during point reads, improving performance, but also increasing memory usage.

*Disabled by default.*

## Stable disk format

The disk format is stable as of 1.0.0. Future breaking changes will result in a major version bump and a migration path.
The disk format is stable as of 1.0.0.

2.0.0 uses a new disk format and needs a manual format migration (`Tree::export` and `Tree::import`).

Future breaking changes will result in a major version bump and a migration path.

## License

Expand All @@ -54,9 +73,11 @@ All contributions are to be licensed as MIT OR Apache-2.0.
### Run benchmarks

```bash
cargo bench --features bloom
cargo bench --features bloom --features lz4 --features miniz
```

## Footnotes

[1] https://rocksdb.org/blog/2017/05/12/partitioned-index-filter.html

[2] https://github.com/facebook/rocksdb/wiki/BlobDB
160 changes: 160 additions & 0 deletions benches/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use criterion::{criterion_group, criterion_main, Criterion};
use lsm_tree::{
segment::{
block::{header::Header as BlockHeader, ItemSize},
meta::CompressionType,
value_block::ValueBlock,
},
serde::Serializable,
Checksum, InternalValue,
};
use std::io::Write;

/* fn value_block_size(c: &mut Criterion) {
let mut group = c.benchmark_group("ValueBlock::size");
for item_count in [10, 100, 1_000] {
group.bench_function(format!("{item_count} items"), |b| {
let items = (0..item_count)
.map(|_| {
InternalValue::from_components(
"a".repeat(16).as_bytes(),
"a".repeat(100).as_bytes(),
63,
lsm_tree::ValueType::Value,
)
})
.collect();
let block = ValueBlock {
items,
header: BlockHeader {
compression: CompressionType::Lz4,
checksum: Checksum::from_raw(0),
data_length: 0,
previous_block_offset: 0,
uncompressed_length: 0,
},
};
b.iter(|| {
(&*block.items).size();
})
});
}
} */

fn value_block_find(c: &mut Criterion) {
let mut group = c.benchmark_group("ValueBlock::find_latest");

for item_count in [10, 100, 1_000, 10_000] {
let mut items = vec![];

for seqno in (0..(item_count - 2)).rev() {
items.push(InternalValue::from_components(
*b"a",
*b"a",
seqno,
lsm_tree::ValueType::Value,
));
}
for seqno in (0..2).rev() {
items.push(InternalValue::from_components(
*b"b",
*b"b",
seqno,
lsm_tree::ValueType::Value,
));
}

let block = ValueBlock {
items: items.into_boxed_slice(),
header: BlockHeader {
compression: CompressionType::Lz4,
checksum: Checksum::from_raw(0),
data_length: 0,
previous_block_offset: 0,
uncompressed_length: 0,
},
};

group.bench_function(format!("{item_count} items (linear)"), |b| {
b.iter(|| {
let item = block
.items
.iter()
.find(|item| &*item.key.user_key == b"b")
.cloned()
.unwrap();
assert_eq!(item.key.seqno, 1);
})
});

group.bench_function(format!("{item_count} items (binary search)"), |b| {
b.iter(|| {
let item = block.get_latest(b"b").unwrap();
assert_eq!(item.key.seqno, 1);
})
});
}
}

fn load_value_block_from_disk(c: &mut Criterion) {
let mut group = c.benchmark_group("Load block from disk");

for comp_type in [
CompressionType::None,
CompressionType::Lz4,
CompressionType::Miniz(6),
] {
for block_size in [1, 4, 8, 16, 32, 64, 128] {
let block_size = block_size * 1_024;

let mut size = 0;

let mut items = vec![];

for x in 0u64.. {
let value = InternalValue::from_components(
x.to_be_bytes(),
x.to_string().repeat(50).as_bytes(),
63,
lsm_tree::ValueType::Value,
);

size += value.size();

items.push(value);

if size >= block_size {
break;
}
}

// Serialize block
let (mut header, data) = ValueBlock::to_bytes_compressed(&items, 0, comp_type).unwrap();
header.checksum = Checksum::from_bytes(&data);

let mut file = tempfile::tempfile().unwrap();
header.encode_into(&mut file).unwrap();
file.write_all(&data).unwrap();

let expected_block = ValueBlock {
items: items.clone().into_boxed_slice(),
header,
};

group.bench_function(format!("{block_size} KiB [{comp_type}]"), |b| {
b.iter(|| {
let loaded_block = ValueBlock::from_file(&mut file, 0).unwrap();

assert_eq!(loaded_block.items.len(), expected_block.items.len());
assert_eq!(loaded_block.header.checksum, expected_block.header.checksum);
});
});
}
}
}

criterion_group!(benches, value_block_find, load_value_block_from_disk,);
criterion_main!(benches);
3 changes: 1 addition & 2 deletions benches/fd_table.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use criterion::{criterion_group, criterion_main, Criterion};
use std::sync::Arc;

fn file_descriptor_table(c: &mut Criterion) {
use std::fs::File;
Expand Down
Loading

0 comments on commit 0a89c1a

Please sign in to comment.