Named after VolcΓ‘n BarΓΊ, the highest peak in Panama (3,475m) and a symbol of strength and endurance from the ChiriquΓ region.
BarΓΊDB is a Rust implementation of a Log-Structured Merge (LSM) tree key-value store, originally developed as a research project for Harvard University's CS265 Data Systems course. The implementation provides a client-server architecture for managing key-value data with advanced optimizations including Monkey-optimized Bloom filters, multiple compaction strategies, and novel fence pointer designs.
Academic Origins: This project was developed as part of Harvard's CS265 Systems course, implementing and evaluating modern LSM tree optimization techniques. The research findings, including comparative analyses of Bloom filter implementations and fence pointer designs, are documented in the academic reports included in this repository.
You'll need Rust installed on your system. If you haven't installed Rust yet, see https://www.rust-lang.org/tools/install for installation options
The project requires Rust 1.81.0 or later.
Clone the repository and build using cargo:
git clone [repository-url]
cd barudb
cargo build --releaseThe project includes various types of tests to verify functionality and performance.
To run all regular tests (excluding long-running tests):
cargo testThis will run all tests except those marked with #[ignore].
The workload tests simulate real-world database usage but can take a long time to complete:
# Run the workload integration test
cargo test test_workload_execution -- --ignoredThe server command tests verify functionality of server commands like PrintStats and Load:
# Ensure server is running on port 8080 first
./run_server_tests.shThis script will:
- Build the server if needed
- Start the server if not already running
- Run tests that verify PrintStats and Load command functionality
- Shut down the server (if it started it)
Performance tests evaluate various aspects of the system but are excluded from regular test runs:
# Run all performance tests
cargo test -- --ignored
# Run a specific performance test
cargo test compare_storage_implementations -- --ignoredThe project includes several benchmarks for evaluating performance of different components.
To run all benchmarks:
cargo benchTo run a specific benchmark:
# Run bloom filter benchmarks
# Tests different bloom filter implementations (SpeedDB, RocksDB, etc.)
cargo bench --bench bloom_bench
# Run storage benchmarks (Criterion benchmark)
cargo bench --bench storage_bench
# Run simpler storage benchmark with clearer output
cargo run --release --bin storage_bench_simpleThere's a standalone benchmark for evaluating FastLane vs Standard fence pointers:
# Build and run the FastLane benchmark
cargo run --release --bin benchmark_fastlaneThis benchmark compares performance across three key distribution patterns:
- Sequential keys (e.g., timestamps, auto-incrementing IDs)
- Random keys (high entropy)
- Grouped keys (keys with common prefixes by group)
To launch the server:
cargo run --release --bin server [OPTIONS]| Option | Default | Description |
|---|---|---|
-e <error_rate> |
0.01 | Bloom filter error rate |
-n <num_pages> |
1024 | Size of the buffer by number of disk pages |
-f <fanout> |
4 | LSM tree fanout |
-l <level_policy> |
"tiered" | Compaction policy (options: tiered, leveled, lazy_leveled) |
-t <threshold> |
4 | Compaction threshold (runs for tiered/lazy_leveled) |
-p <port> |
8080 | Port number |
-h |
N/A | Print help message |
To launch the client:
cargo run --release --bin client [OPTIONS]| Option | Description |
|---|---|
-p <port> |
Port number (default: 8080) |
-q |
Quiet mode |
The database supports the following commands:
| Command | Description | Example |
|---|---|---|
p <key> <value> |
Put a key-value pair | p 10 42 |
g <key> |
Get value for key | g 10 |
r <start> <end> |
Range query | r 10 20 |
d <key> |
Delete key | d 10 |
l <filename> |
Load commands from file | l "data.txt" |
s |
Print stats (tree config, storage stats, etc.) | s |
f |
Force memtable flush to disk | f |
q |
Quit | q |
The l (Load) command processes a file containing multiple commands, with each command on a separate line. It supports p (Put) and d (Delete) commands in the file.
The s (PrintStats) command displays comprehensive statistics about the LSM tree, including:
- Storage type and compaction policy
- Buffer size and fanout configuration
- Total storage size and file count
- Level-by-level statistics (runs, blocks, entries)
While the server is running, you can enter these commands in the server terminal:
| Command | Description |
|---|---|
bloom |
Print Bloom Filter summary |
quit |
Quit server |
help |
Print help message |
barudb/
βββ Cargo.toml
βββ src/
β βββ bin/
β β βββ client.rs # Client implementation
β β βββ server.rs # Server implementation
β βββ bloom/ # Bloom filter implementations
β β βββ mod.rs # Core Bloom filter code
β β βββ rocks_db.rs # RocksDB filter implementation
β β βββ speed_db.rs # SpeedDB filter implementation
β βββ compaction/ # Compaction policy implementations
β β βββ mod.rs # Compaction framework & factory
β β βββ tiered.rs # Tiered compaction policy
β β βββ leveled.rs # Leveled compaction policy
β β βββ lazy_leveled.rs # Hybrid lazy leveled compaction
β β βββ partial_tiered.rs # Partial tiered compaction for optimization
β βββ run/ # Run (data block) implementations
β β βββ block.rs # Block structure
β β βββ block_cache.rs # Block caching implementation
β β βββ compression/ # Compression implementations
β β β βββ bit_pack.rs # Bit packing compression
β β β βββ delta.rs # Delta encoding
β β β βββ dictionary.rs # Dictionary compression
β β β βββ lz4.rs # LZ4 compression
β β β βββ mod.rs # Compression framework
β β β βββ noop.rs # No-op compression
β β β βββ snappy.rs # Snappy compression
β β βββ compressed_fence.rs # Prefix-compressed fence pointers
β β βββ fastlane_fence.rs # FastLane-optimized fence pointers
β β βββ fence.rs # Base fence pointers
β β βββ filter.rs # Block-level filters
β β βββ lsf.rs # Log-structured file storage
β β βββ mod.rs # Run module definitions
β β βββ standard_fence.rs # Standard fence pointers
β β βββ storage.rs # Storage interface
β β βββ two_level_fence.rs # Two-level fence pointers
β βββ command.rs # Command parsing
β βββ level.rs # Level management
β βββ lib.rs # Shared library code
β βββ lsm_tree.rs # Main LSM tree implementation
β βββ memtable.rs # In-memory buffer
β βββ test_helpers.rs # Testing utilities
β βββ types.rs # Type definitions
βββ benches/ # Performance benchmarks
β βββ block_cache_bench.rs # Block cache benchmarks
β βββ bloom_bench.rs # Bloom filter benchmarks
β βββ compressed_fence_bench.rs # Fence pointer compression benchmarks
β βββ compression_bench.rs # Data compression benchmarks
β βββ fastlane_bench.rs # FastLane fence pointer benchmarks
β βββ fence_bench.rs # Standard fence pointer benchmarks
β βββ storage_bench.rs # Storage implementation benchmarks
βββ tests/ # Integration tests
β βββ compaction_integration_test.rs # Tests for compaction policies
β βββ compaction_policy_test.rs # Unit tests for compaction
β βββ compressed_fence_test.rs # Tests for compressed fence pointers
β βββ compression_comparison_test.rs # Tests for compression algorithms
β βββ compression_test.rs # Basic compression tests
β βββ integration_test.rs # Basic LSM tree tests
β βββ lsf_storage_test.rs # Log-structured file tests
β βββ lsm_tree_compaction_test.rs # LSM tree with compaction
β βββ partial_compaction_test.rs # Partial compaction tests
β βββ recovery_reliability_test.rs # Recovery and crash resilience tests
β βββ run_storage_test.rs # Storage tests
β βββ server_commands_test.rs # Server command tests
β βββ storage_comparison_test.rs # Storage type comparison
β βββ workload_test.rs # Full workload tests
βββ README.md
The implementation includes:
- Full LSM tree implementation with multi-level storage
- Memory buffer (memtable) for fast writes
- Disk-based runs with sorted key-value pairs
- Optimized Bloom filters for fast negative lookups
- Optimized fence pointers for efficient range queries
- Cache-aligned memory layout for better CPU cache utilization
- Hardware prefetching for reduced memory latency
- Two-level sparse/dense indexing structure for memory efficiency
- Prefix compression for maximized memory efficiency with numeric keys
- FastLane memory layout for improved cache locality during lookups
- Monkey-optimized Bloom filters for memory efficiency
- Complete compaction framework with three policies:
- Tiered compaction: Multiple runs per level, compact when threshold reached
- Leveled compaction: Single run per level, compact on any new run
- Lazy Leveled compaction: Hybrid approach with tiered behavior at level 0
- Robust recovery mechanisms for crash resilience
- Deterministic recovery sequence with explicit flush points
- Comprehensive recovery tests for all compaction policies
- Support for both synchronous and asynchronous writes
- Pluggable component architecture for easy experimentation
Implemented Bloom filters that follow the optimization strategy described in the Monkey paper. This strategy allocates different numbers of bits per entry based on the level in the LSM tree:
- Higher levels (closer to the buffer) get more bits per entry
- Lower levels get fewer bits per entry
- The allocation follows an exponential decay pattern based on the fanout ratio
This optimization provides several benefits:
- Reduces the overall memory footprint of Bloom filters
- Maintains low false positive rates for frequently accessed levels
- Accepts slightly higher false positive rates for rarely accessed levels
- Scales appropriately based on the tree's fanout configuration
The implementation shows the following performance characteristics (with fanout=4):
| Level | Bits per Entry | Memory Usage (10K entries) | False Positive Rate |
|---|---|---|---|
| 0 | 52.43 | 65,536 bytes | 0.0000% |
| 1 | 26.21 | 32,768 bytes | 0.0000% |
| 2 | 13.11 | 16,384 bytes | 0.0000% |
| 3 | 6.55 | 8,192 bytes | 0.0000% |
| 4 | 3.28 | 4,096 bytes | 0.1900% |
The bit allocation formula follows an exponential decay: for a given level i and fanout T,
bits per entry = 32.0 / T^(i/2), with a minimum of 2 bits per entry.
This approach demonstrates a memory reduction of ~94% from level 0 to level 4, while maintaining excellent false positive rates even in the lowest levels.
The implementation provides three distinct compaction policies, each with different performance characteristics:
The tiered compaction policy allows multiple runs per level and triggers compaction when a level has accumulated a configured number of runs:
pub struct TieredCompactionPolicy {
/// Number of runs that trigger compaction
run_threshold: usize,
}Characteristics:
- Lower write amplification (fewer rewrites during compaction)
- Higher read amplification (must check multiple runs per level)
- Good for write-heavy workloads
- Configurable threshold for controlling when compaction occurs
The leveled compaction policy maintains exactly one run per level and triggers compaction whenever there would be more than one run at any level:
pub struct LeveledCompactionPolicy {
/// Size ratio threshold between levels (usually matches the fanout)
size_ratio_threshold: usize,
}Characteristics:
- Higher write amplification (more rewrites during compaction)
- Lower read amplification (at most one run per level)
- Good for read-heavy workloads
- More predictable query performance
A hybrid policy that combines tiered and leveled approaches:
pub struct LazyLeveledCompactionPolicy {
/// Threshold for number of runs in level 0 before compaction
run_threshold: usize,
}Characteristics:
- Level 0: Behaves like tiered compaction (multiple runs allowed)
- Higher levels: Behaves like leveled compaction (single run per level)
- Balances read and write performance
- Good for mixed workloads
- Reduces write amplification while maintaining good read performance
All policies are implemented as plugins that satisfy the CompactionPolicy trait, making it easy to switch between them or implement new strategies:
pub trait CompactionPolicy: Send + Sync {
fn should_compact(&self, level: &Level, level_num: usize) -> bool;
fn select_runs_to_compact(&self, level: &Level) -> Vec<usize>;
fn compact(...) -> Result<Run>;
fn box_clone(&self) -> Box<dyn CompactionPolicy>;
}The compaction policy can be selected when creating the LSM tree or through the command-line interface.
The implementation includes state-of-the-art fence pointer optimizations to improve both lookup performance and memory efficiency:
Implemented fence pointers with explicit memory alignment to improve CPU cache utilization:
#[repr(align(16))]
pub struct FencePointer {
pub min_key: Key,
pub max_key: Key,
pub block_index: usize,
}
#[repr(align(64))] // Align to typical cache line size
pub struct FencePointers {
pub pointers: Vec<FencePointer>,
}This alignment ensures that fence pointers are loaded efficiently from memory into CPU cache lines, reducing cache misses and improving overall lookup performance.
On x86_64 platforms, uses explicit prefetching to load data into cache before it's needed:
unsafe {
_mm_prefetch(
&self.pointers[prefetch_idx] as *const _ as *const i8,
_MM_HINT_T0,
);
}
Helps hide memory latency by anticipating what data will be needed next during searches.
For larger collections, implement a hierarchical index structure with:
- Sparse Index: A small top-level index that directs searches to the appropriate section
- Dense Index: A more detailed bottom-level index with the actual pointers
This approach reduces memory usage while maintaining good lookup performance, especially for large datasets. The sparse ratio (number of dense entries per sparse entry) is configurable and can be tuned based on the collection size and access patterns.
pub struct TwoLevelFencePointers {
pub sparse: SparseIndex,
pub dense: DenseIndex,
pub sparse_ratio: usize,
}The structure is automatically rebuilt as needed and maintains optimal performance across a wide range of collection sizes.
For maximizing memory efficiency, especially with numerical keys, the implementation includes bit-level prefix compression:
- Bit-Level Grouping: Groups keys with common high-order bits to maximize sharing
- Suffix Storage: Stores only the unique suffix bits for each key in a group
- Adaptive Optimization: Dynamically adjusts compression based on key distribution
pub struct PrefixGroup {
pub common_bits_mask: u64,
pub num_shared_bits: u8,
pub entries: Vec<(u64, u64, usize)>, // (min_key_suffix, max_key_suffix, block_index)
}
pub struct CompressedFencePointers {
pub groups: Vec<PrefixGroup>,
pub min_key: Key,
pub max_key: Key,
pub target_group_size: usize,
}This compression approach is particularly effective for:
- Sequential keys (e.g., timestamps or auto-incremented IDs)
- Keys with natural grouping patterns (e.g., data from different sources with distinct prefixes)
- Large fence pointer collections where memory efficiency is critical
The implementation shows significant memory reduction compared to standard fence pointers:
- Up to 70% memory reduction with sequential keys
- 30-50% reduction with grouped keys
- 10-20% reduction even with high-entropy random keys
An adaptive version (AdaptivePrefixFencePointers) periodically optimizes the compression based on the observed key distribution, further improving memory efficiency for dynamic workloads.
For optimizing lookup performance, especially for frequently traversed fence pointers, the implementation includes a FastLane memory layout:
- Lane Separation: Segregates comparison data from value data in separate memory regions
- Explicit Prefetching: Uses hardware prefetching for upcoming entries during binary search
- Cache-Friendly Access: Organizes memory layout for better CPU cache utilization
pub struct FastLaneGroup {
pub common_bits_mask: u64,
pub num_shared_bits: u8,
pub min_key_lane: Vec<u64>, // Lane for min_key values
pub max_key_lane: Vec<u64>, // Lane for max_key values
pub block_index_lane: Vec<usize>, // Lane for block indices
}
pub struct FastLaneFencePointers {
pub groups: Vec<FastLaneGroup>,
pub min_key: Key,
pub max_key: Key,
pub target_group_size: usize,
}The FastLane implementation shows the following performance characteristics:
Sequential Keys:
- 100% key coverage
- Memory usage: 21.24% less than standard implementation
- Performance: 44.12% slower than standard implementation
Random Keys:
- 100% key coverage
- Memory usage: 3.32% more than standard implementation
- Performance: 1308.48% slower than standard implementation
Grouped Keys:
- 100% key coverage
- Memory usage: 1.07% less than standard implementation
- Performance: 94.55% slower than standard implementation
While the FastLane implementation provides perfect key coverage and good memory characteristics, the current performance is not optimal due to:
- Basic Implementation: This is an initial implementation that prioritizes correctness and demonstrating the memory layout concept
- Missing Optimizations: More aggressive inlining and specialized binary search routines could improve performance
- Hardware Limitations: Optimal performance depends heavily on CPU features and cache characteristics
Future optimizations should focus on:
- Specialized Binary Search: Custom binary search algorithm optimized for the lane structure
- SIMD Vectorization: Using vector instructions to process multiple keys at once
- Memory Alignment: Ensuring optimal alignment for CPU cache lines
- Branch Prediction Hints: Adding hints to help CPU predict branch directions
The data-oriented layout provides these benefits through:
- Separating Comparison Data: Keeps min/max key values in separate lanes
- Explicit Prefetching: Loads data into cache before it's needed during binary search
- Cache-Friendly Grouping: Organizes related data in memory for better cache utilization
- Reduced Cache Misses: Lane-based layout minimizes cache misses during traversal
This optimization combines the memory efficiency of prefix compression with the performance benefits of data-oriented design. The implementation includes an adaptive variant (AdaptiveFastLaneFencePointers) that dynamically adjusts itself based on access patterns.
This project includes detailed academic reports documenting the research and evaluation:
final_report/final_report.md- Complete analysis of LSM tree optimizationsmidway_report/midway-report.md- Mid-project progress report
These reports provide in-depth analysis of Bloom filter implementations, compaction strategies, and fence pointer designs.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is open source and available under the MIT License.
If you use BarΓΊDB in your research, please cite:
Sam-Bodden, B. (2025). BarΓΊDB: A High-Performance LSM Tree Key-Value Store in Rust.
Harvard University CS265 Data Systems Course Project.
