LSM-Tree(Log-Structured Merge-Tree)是现代高性能存储引擎的核心数据结构,被 LevelDB、RocksDB、Cassandra、HBase 等广泛采用。本文用 Rust 从零实现一个完整的 LSM-Tree 存储引擎。
一、LSM-Tree 核心架构
LSM-Tree 将写入操作先缓存在内存中的 MemTable(通常使用跳表实现),当 MemTable 达到阈值后冻结为 Immutable MemTable,然后 Flush 到磁盘形成 SSTable。后台 Compaction 线程持续合并 SSTable 文件,清理过期数据。
pub struct LSMTree {
memtable: Arc, Option>>>,
immutable_memtables: Vec, Option>>>>,
levels: Vec>,
options: LsmOptions,
compaction_tx: Sender<()>,
}
pub struct LsmOptions {
pub memtable_size_limit: usize, // 默认 64MB
pub level0_file_num_limit: usize, // 默认 4
pub level_size_multiplier: usize, // 默认 10
pub block_size: usize, // 默认 4KB
pub bloom_filter_bits: usize, // 默认 10 bits per key
}
二、MemTable 与 WAL
MemTable 使用跳表实现,支持 O(log n) 的插入和查找。同时写入 WAL(Write-Ahead Log)保证持久性,崩溃恢复时从 WAL 重建 MemTable。
impl LSMTree {
pub fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
// 1. 先写 WAL
self.wal.append(&LogEntry::Put(key.to_vec(), value.to_vec()))?;
// 2. 写入 MemTable
self.memtable.insert(key.to_vec(), Some(value.to_vec()));
// 3. 检查是否需要 Flush
if self.memtable.approximate_size() >= self.options.memtable_size_limit {
self.trigger_flush()?;
}
Ok(())
}
pub fn delete(&self, key: &[u8]) -> Result<()> {
self.wal.append(&LogEntry::Delete(key.to_vec()))?;
self.memtable.insert(key.to_vec(), None); // Tombstone
Ok(())
}
}
三、SSTable 文件格式
SSTable 由多个 Data Block 组成,每个 Block 存储有序的键值对。文件末尾是 Index Block(记录每个 Data Block 的起始 key 和偏移)和 Footer(元信息)。
pub struct SSTable {
file: File,
index: Vec<(Vec, u64)>, // (start_key, block_offset)
bloom_filter: BloomFilter,
block_size: usize,
}
impl SSTable {
pub fn get(&self, key: &[u8]) -> Result
四、Compaction 策略
采用 Leveled Compaction 策略:Level 0 的 SSTable 之间 key 范围可能重叠,Level 1+ 的 SSTable 之间 key 范围不重叠。当某层 SSTable 数量或大小超过阈值时,选择该层的一个 SSTable 与下一层重叠的 SSTable 进行合并。
fn compact_level(&mut self, level: usize) -> Result<()> {
let upper = self.levels[level].pick_compaction_candidate();
let overlapping: Vec<_> = self.levels[level + 1].iter()
.filter(|sst| sst.overlaps(&upper))
.collect();
let mut iter = MergeIterator::new(upper, overlapping);
let mut new_sstables = Vec::new();
while let Some((key, value)) = iter.next() {
// 跳过 Tombstone 且底层无此 key 的记录
if value.is_none() && !self.has_key_in_lower_levels(&key, level + 2) {
continue;
}
new_builder.add(&key, &value);
}
Ok(())
}
五、Bloom Filter 优化
每个 SSTable 附带一个 Bloom Filter,以极小的内存开销(约 1.2 字节/key)避免 99% 以上的无效磁盘读取。使用多个哈希函数和位数组实现。
完整的 LSM-Tree 实现约 3000 行 Rust 代码,支持 Put/Get/Delete/Scan 操作,吞吐量可达 10 万 QPS。理解 LSM-Tree 是深入数据库内核的必经之路。
← 返回首页