LevelDB 架构解析

2007 年,Google BigTable 论文发表,描述了一种名为 SSTable + MemTable 的存储结构。2009 年,两位 Google 工程师基于这个设计实现了 LevelDB——一个轻量级的嵌入式 KV 存储。

LevelDB 是 LSM Tree 的经典实现,理解它是理解 RocksDB 的基础。

LevelDB 架构

flowchart TB
    subgraph Memory["内存"]
        MemTable["MemTable (SkipList)"]
        Immutable["Immutable MemTable"]
    end
    
    subgraph Disk["磁盘"]
        WAL["WAL"]
        L0["L0: 4 个 SSTable"]
        L1["L1: ~10 个 SSTable"]
        L2["L2: ~100 个 SSTable"]
        L3["L3: ~1000 个 SSTable"]
    end
    
    subgraph Manifest["元数据"]
        Current["CURRENT"]
        Manifest["MANIFEST"]
    end
    
    MemTable --> |写满| Immutable
    Immutable --> |刷盘| L0
    L0 --> |合并| L1
    L1 --> |合并| L2
    L2 --> |合并| L3

核心组件

MemTable

内存中的有序数据结构,使用跳表(SkipList)实现:

// LevelDB MemTable 伪代码
class MemTable {
 private:
  SkipList<const Key&, KeyComparator> table_;
  
 public:
  void Add(SequenceNumber seq, ValueType type,
           const Slice& key, const Slice& value) {
    // 将操作编码为指定格式
    std::string encoded;
    AppendInternalKey(&encoded, ParsedKey{key, seq, type});
    table_.Insert(encoded);
  }
  
  Iterator* NewIterator() {
    return new MemTableIterator(table_);
  }
};

Immutable MemTable

MemTable 写满后,变为只读的 Immutable MemTable,同时后台线程开始刷盘:

void DBImpl::MaybeScheduleFlush() {
  if (bg_flush_scheduled_) return;
  
  bg_flush_scheduled_ = true;
  env_->Schedule(&DBImpl::BGWork, this);
}

void DBImpl::BGWork(void* db) {
  static_cast<DBImpl*>(db)->FlushMemTable();
}

Status DBImpl::FlushMemTable() {
  // 1. 构建 TableBuilder
  TableBuilder builder(output_file);
  
  // 2. 遍历 Immutable MemTable
  Iterator* iter = imm_->NewIterator();
  for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
    builder.Add(iter->key(), iter->value());
  }
  
  // 3. 写入 SSTable
  builder.Finish();
}

WAL(预写日志)

每次写入 MemTable 前,先写入 WAL:

void LogWriter::AddRecord(const Slice& slice) {
  // 追加写入日志文件
  // 日志格式: [Checksum | Length | Data]
  // 确保数据持久化后再写 MemTable
}

SSTable 结构

LevelDB 的 SSTable 结构:

┌─────────────────────────────────────┐
│ Data Block 1 (64KB)                 │
│ ┌─────────────────────────────────┐ │
│ │ key1 → value1                   │ │
│ │ key2 → value2                   │ │
│ │ ...                             │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────┤
│ Data Block 2                        │
├─────────────────────────────────────┤
│ ...                                  │
├─────────────────────────────────────┤
│ Meta Index Block (Bloom Filter)     │
├─────────────────────────────────────┤
│ Index Block                         │
│   key_last_in_block → offset, size   │
├─────────────────────────────────────┤
│ Footer                              │
│   Meta Index Handle                  │
│   Index Handle                       │
└─────────────────────────────────────┘

Manifest 文件

Manifest 记录每个 Level 的 SSTable 信息:

Manifest:
  Version 1:
    L0: [SSTable-A, SSTable-B]
    L1: [SSTable-C]
  Version 2:
    L0: [SSTable-A, SSTable-B, SSTable-C]
    L1: []

每次 Compaction 后生成新版本 Manifest:

void VersionSet::Finalize() {
  // 计算每个 Level 的文件数量限制
  // L0: 由刷盘触发,无固定上限
  // L1+: 由公式计算: TargetSize(Lk) = 10^(k) * 10MB
  // 例如: L1 = 100MB, L2 = 1GB, L3 = 10GB
}

Compaction 流程

触发条件

  • L0 文件数达到 4 个
  • L1+ 的总大小超过限制

Compaction 策略

void DBImpl::BackgroundCompaction() {
  // 1. 选择需要合并的文件
  Compaction* c = versions_->PickCompaction();
  
  // 2. 合并选中的 SSTable 和下一层的 SSTable
  Iterator* input = NewCompactionIterator(c);
  
  // 3. 创建新的 SSTable
  TableBuilder builder(output_file);
  
  while (input->Valid()) {
    builder.Add(input->key(), input->value());
    input->Next();
  }
  
  // 4. 写入新 SSTable
  builder.Finish();
  
  // 5. 删除旧 SSTable
  DeleteObsoleteFiles();
}

Snapshots

LevelDB 支持创建只读快照,读取快照时看到的是创建时的数据状态:

// 创建快照
const ReadOptions options;
options.snapshot = db->GetSnapshot();

// 读取(基于快照)
Iterator* iter = db->NewIterator(options);
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
  // 读取快照中的数据
}

db->ReleaseSnapshot(options.snapshot);

快照实现

class Version {
 public:
  // 每个 Version 持有当前所有 SSTable 的引用
  std::vector<FileMetaData*> files_[config::kNumLevels];
};

class VersionSet {
  std::vector<Version*> versions_;  // 所有历史版本
  Version* current_;                // 当前版本
};

// 快照只是指向某个 Version 的指针
// 该 Version 不会被 Compaction 回收

错误处理与恢复

崩溃恢复

void DBImpl::RecoverLogFile(uint64_t log_number) {
  // 1. 读取 WAL
  SequentialFile* log_file;
  env_->NewSequentialFile(LogFileName(...), &log_file);
  LogReader reader(log_file);
  
  // 2. 重放每条日志
  while (reader.ReadRecord(&record)) {
    // 应用到 MemTable
    memtable_->Add(record.seq, record.type, 
                   record.key, record.value);
  }
  
  // 3. 刷盘并清理日志
  FlushMemTable();
  DeleteFile(log_number);
}

LevelDB vs RocksDB

特性LevelDBRocksDB
线程安全否(需要外部锁)是(内置锁)
并发 Compaction是(多线程)
Column Family不支持支持
Compaction Filter不支持支持
Iterator Bounds不支持支持
统计信息有限完整
适用场景单线程、简单场景生产环境

使用示例

#include <leveldb/db.h>

leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;

leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
if (!status.ok()) {
  // 处理错误
}

// 写入
db->Put(leveldb::WriteOptions(), "key1", "value1");

// 读取
std::string value;
status = db->Get(leveldb::ReadOptions(), "key1", &value);

// 遍历
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
  std::cout << it->key().ToString() << ": " 
            << it->value().ToString() << std::endl;
}
delete it;

delete db;

设计哲学:LevelDB 的设计理念是「简单而正确」。它只做一件事:提供一个高效的嵌入式 KV 存储。它没有分布式、没有副本、没有复杂的事务——这些都在上层实现。如果你需要这些功能,可以基于 LevelDB 构建(如 RocksDB),或者使用其他系统。