RocksDB WAL日志 #

一、WAL概述 #

1.1 什么是WAL #

WAL(Write-Ahead Log)是RocksDB的预写日志机制,用于保证数据的持久性和崩溃恢复能力。

text
WAL核心原则:
先写日志,再写数据

写入流程:
写入请求 → WAL日志 → MemTable → 返回成功

崩溃恢复:
读取WAL → 重放日志 → 恢复MemTable数据

1.2 WAL的作用 #

作用 说明
持久性保证 数据写入WAL后即使崩溃也能恢复
崩溃恢复 通过重放WAL恢复未持久化的数据
原子性支持 批量操作的原子性保证
复制支持 作为复制的数据源

二、WAL结构 #

2.1 WAL文件结构 #

text
WAL文件命名:<number>.log

示例:
000001.log
000002.log

文件内容:
┌────────────────────────────────────────────────────┐
│                    Record 1                         │
│  ┌──────────────────────────────────────────────┐ │
│  │ Header | Type | Key | Value | Checksum       │ │
│  └──────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────┤
│                    Record 2                         │
├────────────────────────────────────────────────────┤
│                     ...                             │
└────────────────────────────────────────────────────┘

2.2 Record格式 #

text
Record结构:

┌────────────────────────────────────────────────────┐
│ Header (7 bytes)                                   │
│  ┌──────────────────────────────────────────────┐ │
│  │ CRC (4B) │ Length (2B) │ Type (1B)           │ │
│  └──────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────┤
│ Payload                                            │
│  ┌──────────────────────────────────────────────┐ │
│  │ LogRecord (序列化的写操作)                    │ │
│  └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘

Type类型:
- kZeroType: 无效记录
- kFullType: 完整记录
- kFirstType: 分片记录第一片
- kMiddleType: 分片记录中间片
- kLastType: 分片记录最后一片

2.3 LogRecord格式 #

text
LogRecord内容:

┌────────────────────────────────────────────────────┐
│ Header                                             │
│  ┌──────────────────────────────────────────────┐ │
│  │ Type (1B) │ Checksum (4B)                    │ │
│  └──────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────┤
│ Key                                                │
│  ┌──────────────────────────────────────────────┐ │
│  │ Column Family ID | Sequence Number | Key     │ │
│  └──────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────┤
│ Value                                              │
│  ┌──────────────────────────────────────────────┐ │
│  │ Value Length | Value                         │ │
│  └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘

三、WAL配置 #

3.1 基本配置 #

cpp
#include <rocksdb/options.h>

rocksdb::Options GetWALOptions() {
    rocksdb::Options options;
    
    // WAL目录(默认与数据库同目录)
    options.wal_dir = "/path/to/wal";
    
    // WAL文件保留时间(秒)
    options.WAL_ttl_seconds = 0;  // 0表示不限制
    
    // WAL文件大小限制(MB)
    options.WAL_size_limit_MB = 0;  // 0表示不限制
    
    // WAL恢复模式
    options.wal_recovery_mode = rocksdb::WALRecoveryMode::kPointInTimeRecovery;
    
    return options;
}

3.2 WAL恢复模式 #

cpp
enum WALRecoveryMode {
    // 不恢复,忽略WAL
    kTolerateCorruptedTailRecords,
    
    // 点恢复,遇到错误停止
    kPointInTimeRecovery,
    
    // 完全恢复,跳过错误记录
    kAbsoluteConsistency,
    
    // 跳过所有错误
    kSkipAnyCorruptedRecords,
};

// 推荐配置
options.wal_recovery_mode = rocksdb::WALRecoveryMode::kPointInTimeRecovery;

3.3 写入选项 #

cpp
#include <rocksdb/options.h>

rocksdb::WriteOptions GetWriteOptions() {
    rocksdb::WriteOptions options;
    
    // 同步写入WAL
    options.sync = true;  // 保证持久性
    
    // 禁用WAL
    // options.disableWAL = true;  // 提高性能,但可能丢数据
    
    return options;
}

四、WAL写入流程 #

4.1 写入过程 #

text
WAL写入流程:

写入请求 (key, value)
        │
        ▼
┌─────────────────┐
│  序列化Record   │
│  (添加序列号)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  计算CRC校验    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  追加到WAL文件  │
│  (顺序写入)     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  sync到磁盘     │
│  (可选)         │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  写入MemTable   │
└────────┬────────┘
         │
         ▼
    返回成功

4.2 sync选项影响 #

cpp
// sync = true(同步写入)
rocksdb::WriteOptions sync_options;
sync_options.sync = true;
// 每次写入都调用fsync
// 保证数据持久化
// 性能较低

// sync = false(异步写入)
rocksdb::WriteOptions async_options;
async_options.sync = false;
// 依赖操作系统刷盘
// 可能丢失数据
// 性能较高

五、WAL恢复流程 #

5.1 恢复过程 #

text
WAL恢复流程:

数据库启动
    │
    ▼
┌─────────────────┐
│  读取MANIFEST   │
│  获取当前状态   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  打开WAL文件    │
│  按序号排序     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  读取WAL记录    │
│  验证CRC        │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  重放写操作     │
│  恢复MemTable   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  数据库就绪     │
└─────────────────┘

5.2 恢复示例 #

cpp
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>

int main() {
    rocksdb::DB* db;
    rocksdb::Options options;
    
    // 设置恢复模式
    options.wal_recovery_mode = 
        rocksdb::WALRecoveryMode::kPointInTimeRecovery;
    
    // 打开数据库(自动恢复)
    rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);
    
    if (!status.ok()) {
        std::cerr << "Recovery failed: " << status.ToString() << std::endl;
        return 1;
    }
    
    std::cout << "Database recovered successfully!" << std::endl;
    
    // 验证数据
    std::string value;
    status = db->Get(rocksdb::ReadOptions(), "test_key", &value);
    
    delete db;
    return 0;
}

六、WAL管理 #

6.1 WAL文件生命周期 #

text
WAL文件生命周期:

1. 创建
   - 数据库打开时
   - 切换WAL时

2. 使用
   - 接收写入操作
   - 记录所有变更

3. 归档
   - 所有数据已Flush到SST
   - 不再需要恢复

4. 删除
   - 超过保留时间
   - 超过大小限制

6.2 WAL清理 #

cpp
#include <rocksdb/db.h>

void ManageWALFiles(rocksdb::DB* db) {
    // 获取WAL文件信息
    std::vector<std::string> wal_files;
    db->GetSortedWalFiles(wal_files);
    
    for (const auto& file : wal_files) {
        std::cout << "WAL file: " << file << std::endl;
    }
    
    // 手动Flush触发WAL清理
    db->Flush(rocksdb::FlushOptions());
}

6.3 WAL目录分离 #

cpp
#include <rocksdb/options.h>

rocksdb::Options GetSeparateWALOptions() {
    rocksdb::Options options;
    
    // 将WAL放在单独的磁盘
    options.wal_dir = "/fast_ssd/wal";
    
    // 数据文件放在大容量磁盘
    // db_dir = "/large_hdd/data";
    
    return options;
}

七、WAL与列族 #

7.1 多列族WAL #

text
多列族WAL机制:

所有列族共享同一个WAL文件

写入请求:
- CF1: Put(key1, value1)
- CF2: Put(key2, value2)

WAL记录:
┌────────────────────────────────────────┐
│ Record 1: CF1, key1, value1            │
├────────────────────────────────────────┤
│ Record 2: CF2, key2, value2            │
└────────────────────────────────────────┘

恢复时:
根据列族ID恢复到对应MemTable

7.2 原子性保证 #

cpp
#include <rocksdb/db.h>
#include <rocksdb/write_batch.h>

void AtomicCrossCFWrite(rocksdb::DB* db,
                        rocksdb::ColumnFamilyHandle* cf1,
                        rocksdb::ColumnFamilyHandle* cf2) {
    
    rocksdb::WriteBatch batch;
    
    // 跨列族操作
    batch.Put(cf1, "key1", "value1");
    batch.Put(cf2, "key2", "value2");
    
    // 原子写入(WAL保证)
    db->Write(rocksdb::WriteOptions(), &batch);
}

八、WAL性能优化 #

8.1 禁用WAL #

cpp
#include <rocksdb/options.h>

// 场景1:批量导入数据
rocksdb::WriteOptions import_options;
import_options.disableWAL = true;
import_options.sync = false;

// 批量写入
for (int i = 0; i < 1000000; i++) {
    db->Put(import_options, "key" + std::to_string(i), "value");
}

// 导入完成后手动Flush
db->Flush(rocksdb::FlushOptions());

// 场景2:缓存数据
rocksdb::WriteOptions cache_options;
cache_options.disableWAL = true;
// 数据可以丢失,追求最高性能

8.2 批量写入 #

cpp
#include <rocksdb/write_batch.h>

void BatchWrite(rocksdb::DB* db) {
    rocksdb::WriteBatch batch;
    
    // 批量添加操作
    for (int i = 0; i < 1000; i++) {
        batch.Put("key" + std::to_string(i), "value");
    }
    
    // 一次WAL写入
    db->Write(rocksdb::WriteOptions(), &batch);
}

8.3 组提交 #

text
组提交机制:

多个写入请求合并为一次WAL写入

请求1 ─┐
请求2 ─┼─→ 组提交 → 一次WAL写入
请求3 ─┘

优点:
- 减少WAL写入次数
- 提高写入吞吐
- 减少fsync开销

九、WAL监控 #

9.1 监控指标 #

cpp
#include <rocksdb/db.h>
#include <iostream>

void PrintWALStats(rocksdb::DB* db) {
    uint64_t value;
    
    // WAL文件数量
    db->GetIntProperty("rocksdb.num-wal-files", &value);
    std::cout << "WAL files: " << value << std::endl;
    
    // WAL文件大小
    db->GetIntProperty("rocksdb.wal-file-size", &value);
    std::cout << "WAL size: " << value / 1024 / 1024 << " MB" << std::endl;
}

9.2 性能统计 #

cpp
#include <rocksdb/statistics.h>

void PrintWALStatistics(rocksdb::DB* db) {
    auto stats = db->GetOptions().statistics;
    
    // WAL写入字节数
    uint64_t wal_bytes = stats->getTickerCount(rocksdb::WAL_FILE_BYTES);
    std::cout << "WAL bytes written: " << wal_bytes << std::endl;
    
    // WAL sync次数
    uint64_t wal_syncs = stats->getTickerCount(rocksdb::WAL_FILE_SYNCED);
    std::cout << "WAL syncs: " << wal_syncs << std::endl;
}

十、最佳实践 #

10.1 WAL配置建议 #

场景 sync disableWAL 说明
关键数据 true false 保证持久性
高吞吐 false false 平衡性能和安全
批量导入 false true 最高性能
缓存数据 false true 可接受数据丢失

10.2 故障恢复建议 #

  1. 选择合适的恢复模式:kPointInTimeRecovery推荐
  2. 定期备份:WAL不是备份替代品
  3. 监控WAL大小:防止WAL过大
  4. 测试恢复流程:确保恢复有效

十一、总结 #

11.1 WAL关键参数 #

参数 说明
wal_dir WAL目录
WAL_ttl_seconds WAL保留时间
WAL_size_limit_MB WAL大小限制
wal_recovery_mode 恢复模式
sync 同步写入
disableWAL 禁用WAL

11.2 关键要点 #

  1. 持久性保证:WAL确保数据不丢失
  2. 崩溃恢复:通过重放WAL恢复数据
  3. 性能权衡:sync选项影响性能
  4. 合理配置:根据场景选择配置
  5. 监控管理:关注WAL大小和性能

下一步,让我们学习Compaction机制!

最后更新:2026-03-27