RocksDB快照 #

一、快照基础 #

1.1 什么是快照 #

快照(Snapshot)是RocksDB在某一时刻数据库状态的一致性视图。通过快照可以实现:

text
快照特性:
├── 一致性读 - 读取特定时间点的数据
├── 隔离性 - 不受并发写入影响
├── 轻量级 - 创建快照成本很低
└── MVCC支持 - 基于多版本并发控制

1.2 快照原理 #

text
数据版本链:

写入序列:
Write(key1, v1) → seq=100
Write(key2, v2) → seq=101
Write(key1, v3) → seq=102
Write(key3, v1) → seq=103

快照创建:
Snapshot A → seq=101
Snapshot B → seq=103

读取:
- 普通读取:看到seq<=103的所有数据
- Snapshot A读取:只看到seq<=101的数据
- Snapshot B读取:只看到seq<=103的数据

1.3 创建和使用快照 #

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

int main() {
    rocksdb::DB* db;
    rocksdb::Options options;
    options.create_if_missing = true;
    
    rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);
    if (!status.ok()) {
        std::cerr << "Open failed: " << status.ToString() << std::endl;
        return 1;
    }
    
    // 写入初始数据
    db->Put(rocksdb::WriteOptions(), "key1", "value1_v1");
    db->Put(rocksdb::WriteOptions(), "key2", "value2_v1");
    
    // 创建快照
    const rocksdb::Snapshot* snapshot = db->GetSnapshot();
    
    std::cout << "Snapshot created at sequence: " << snapshot->GetSequenceNumber() << std::endl;
    
    // 修改数据
    db->Put(rocksdb::WriteOptions(), "key1", "value1_v2");
    db->Put(rocksdb::WriteOptions(), "key2", "value2_v2");
    db->Put(rocksdb::WriteOptions(), "key3", "value3_v1");
    
    // 读取当前数据
    std::string value;
    std::cout << "\nCurrent data:" << std::endl;
    db->Get(rocksdb::ReadOptions(), "key1", &value);
    std::cout << "key1 = " << value << std::endl;
    db->Get(rocksdb::ReadOptions(), "key2", &value);
    std::cout << "key2 = " << value << std::endl;
    db->Get(rocksdb::ReadOptions(), "key3", &value);
    std::cout << "key3 = " << value << std::endl;
    
    // 通过快照读取历史数据
    rocksdb::ReadOptions snapshot_options;
    snapshot_options.snapshot = snapshot;
    
    std::cout << "\nSnapshot data:" << std::endl;
    db->Get(snapshot_options, "key1", &value);
    std::cout << "key1 = " << value << std::endl;
    db->Get(snapshot_options, "key2", &value);
    std::cout << "key2 = " << value << std::endl;
    
    status = db->Get(snapshot_options, "key3", &value);
    if (status.IsNotFound()) {
        std::cout << "key3 not found (not in snapshot)" << std::endl;
    }
    
    // 释放快照
    db->ReleaseSnapshot(snapshot);
    
    delete db;
    return 0;
}

二、快照应用场景 #

2.1 一致性读取 #

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

// 一致性读取多个键
std::vector<std::pair<std::string, std::string>>
ConsistentMultiGet(rocksdb::DB* db, const std::vector<std::string>& keys) {
    
    // 创建快照
    const rocksdb::Snapshot* snapshot = db->GetSnapshot();
    
    // 使用快照读取
    rocksdb::ReadOptions read_options;
    read_options.snapshot = snapshot;
    
    std::vector<std::pair<std::string, std::string>> result;
    
    for (const auto& key : keys) {
        std::string value;
        rocksdb::Status status = db->Get(read_options, key, &value);
        if (status.ok()) {
            result.push_back({key, value});
        }
    }
    
    // 释放快照
    db->ReleaseSnapshot(snapshot);
    
    return result;
}

int main() {
    rocksdb::DB* db;
    rocksdb::Options options;
    options.create_if_missing = true;
    rocksdb::DB::Open(options, "/tmp/testdb", &db);
    
    // 写入相关数据
    db->Put(rocksdb::WriteOptions(), "user:1:name", "Alice");
    db->Put(rocksdb::WriteOptions(), "user:1:age", "25");
    db->Put(rocksdb::WriteOptions(), "user:1:email", "alice@example.com");
    
    // 一致性读取
    std::vector<std::string> keys = {
        "user:1:name",
        "user:1:age",
        "user:1:email"
    };
    
    auto data = ConsistentMultiGet(db, keys);
    
    std::cout << "Consistent read result:" << std::endl;
    for (const auto& [key, value] : data) {
        std::cout << "  " << key << " = " << value << std::endl;
    }
    
    delete db;
    return 0;
}

2.2 实现事务隔离 #

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

// 模拟事务隔离
class ReadOnlyTransaction {
public:
    ReadOnlyTransaction(rocksdb::DB* db) : db_(db) {
        snapshot_ = db_->GetSnapshot();
    }
    
    ~ReadOnlyTransaction() {
        if (snapshot_) {
            db_->ReleaseSnapshot(snapshot_);
        }
    }
    
    std::string Get(const std::string& key) {
        rocksdb::ReadOptions options;
        options.snapshot = snapshot_;
        
        std::string value;
        db_->Get(options, key, &value);
        return value;
    }
    
    rocksdb::Iterator* NewIterator() {
        rocksdb::ReadOptions options;
        options.snapshot = snapshot_;
        return db_->NewIterator(options);
    }

private:
    rocksdb::DB* db_;
    const rocksdb::Snapshot* snapshot_;
};

int main() {
    rocksdb::DB* db;
    rocksdb::Options options;
    options.create_if_missing = true;
    rocksdb::DB::Open(options, "/tmp/testdb", &db);
    
    // 写入初始数据
    db->Put(rocksdb::WriteOptions(), "counter", "0");
    
    // 创建只读事务
    ReadOnlyTransaction txn(db);
    
    // 另一个线程修改数据
    std::thread writer([db]() {
        for (int i = 1; i <= 10; i++) {
            db->Put(rocksdb::WriteOptions(), "counter", std::to_string(i));
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    });
    
    // 事务中多次读取,值应该保持不变
    for (int i = 0; i < 5; i++) {
        std::string value = txn.Get("counter");
        std::cout << "Transaction read: counter = " << value << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
    
    writer.join();
    
    // 事务结束后读取最新值
    std::string latest;
    db->Get(rocksdb::ReadOptions(), "counter", &latest);
    std::cout << "Latest value: counter = " << latest << std::endl;
    
    delete db;
    return 0;
}

2.3 数据备份 #

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

void CreateBackup(rocksdb::DB* db, const std::string& backup_dir) {
    // 创建快照确保一致性
    const rocksdb::Snapshot* snapshot = db->GetSnapshot();
    
    // 创建检查点(基于快照)
    rocksdb::Checkpoint* checkpoint;
    rocksdb::Status status = rocksdb::Checkpoint::Create(db, &checkpoint);
    
    if (status.ok()) {
        status = checkpoint->CreateCheckpoint(backup_dir);
        if (status.ok()) {
            std::cout << "Backup created successfully at " << backup_dir << std::endl;
        }
    }
    
    delete checkpoint;
    db->ReleaseSnapshot(snapshot);
}

三、快照迭代 #

3.1 快照迭代器 #

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

void IterateWithSnapshot(rocksdb::DB* db) {
    // 写入初始数据
    for (int i = 0; i < 5; i++) {
        db->Put(rocksdb::WriteOptions(), 
                "key" + std::to_string(i), 
                "v1_" + std::to_string(i));
    }
    
    // 创建快照
    const rocksdb::Snapshot* snapshot = db->GetSnapshot();
    
    // 修改数据
    for (int i = 0; i < 5; i++) {
        db->Put(rocksdb::WriteOptions(), 
                "key" + std::to_string(i), 
                "v2_" + std::to_string(i));
    }
    db->Put(rocksdb::WriteOptions(), "key5", "v2_5");
    
    // 使用快照迭代
    rocksdb::ReadOptions snapshot_options;
    snapshot_options.snapshot = snapshot;
    
    std::cout << "Snapshot iteration:" << std::endl;
    rocksdb::Iterator* it = db->NewIterator(snapshot_options);
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        std::cout << "  " << it->key().ToString() 
                  << " => " << it->value().ToString() << std::endl;
    }
    delete it;
    
    // 当前数据迭代
    std::cout << "\nCurrent iteration:" << std::endl;
    it = db->NewIterator(rocksdb::ReadOptions());
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        std::cout << "  " << it->key().ToString() 
                  << " => " << it->value().ToString() << std::endl;
    }
    delete it;
    
    db->ReleaseSnapshot(snapshot);
}

3.2 比较不同快照 #

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

void CompareSnapshots(rocksdb::DB* db) {
    // 初始数据
    db->Put(rocksdb::WriteOptions(), "key1", "v1");
    db->Put(rocksdb::WriteOptions(), "key2", "v1");
    db->Put(rocksdb::WriteOptions(), "key3", "v1");
    
    // 快照1
    const rocksdb::Snapshot* snap1 = db->GetSnapshot();
    
    // 修改
    db->Put(rocksdb::WriteOptions(), "key1", "v2");
    db->Delete(rocksdb::WriteOptions(), "key2");
    db->Put(rocksdb::WriteOptions(), "key4", "v1");
    
    // 快照2
    const rocksdb::Snapshot* snap2 = db->GetSnapshot();
    
    // 再次修改
    db->Put(rocksdb::WriteOptions(), "key1", "v3");
    db->Put(rocksdb::WriteOptions(), "key3", "v2");
    
    // 比较三个时间点的数据
    std::vector<std::string> keys = {"key1", "key2", "key3", "key4"};
    
    std::cout << "Key\t\tSnap1\tSnap2\tCurrent" << std::endl;
    std::cout << "----\t\t-----\t-----\t-------" << std::endl;
    
    for (const auto& key : keys) {
        std::string v1, v2, v3;
        
        rocksdb::ReadOptions opt1;
        opt1.snapshot = snap1;
        db->Get(opt1, key, &v1);
        
        rocksdb::ReadOptions opt2;
        opt2.snapshot = snap2;
        db->Get(opt2, key, &v2);
        
        db->Get(rocksdb::ReadOptions(), key, &v3);
        
        std::cout << key << "\t\t" 
                  << (v1.empty() ? "-" : v1) << "\t"
                  << (v2.empty() ? "-" : v2) << "\t"
                  << (v3.empty() ? "-" : v3) << std::endl;
    }
    
    db->ReleaseSnapshot(snap1);
    db->ReleaseSnapshot(snap2);
}

四、快照管理 #

4.1 快照生命周期 #

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

class ScopedSnapshot {
public:
    explicit ScopedSnapshot(rocksdb::DB* db) 
        : db_(db), snapshot_(db->GetSnapshot()) {}
    
    ~ScopedSnapshot() {
        if (snapshot_) {
            db_->ReleaseSnapshot(snapshot_);
        }
    }
    
    const rocksdb::Snapshot* Get() const { return snapshot_; }
    
    rocksdb::ReadOptions GetReadOptions() const {
        rocksdb::ReadOptions options;
        options.snapshot = snapshot_;
        return options;
    }
    
private:
    rocksdb::DB* db_;
    const rocksdb::Snapshot* snapshot_;
    
    ScopedSnapshot(const ScopedSnapshot&) = delete;
    ScopedSnapshot& operator=(const ScopedSnapshot&) = delete;
};

// 使用示例
void UseScopedSnapshot(rocksdb::DB* db) {
    ScopedSnapshot snap(db);
    
    std::string value;
    db->Get(snap.GetReadOptions(), "key", &value);
    
    // 函数结束时自动释放快照
}

4.2 快照列表管理 #

cpp
#include <rocksdb/db.h>
#include <vector>
#include <memory>

class SnapshotManager {
public:
    explicit SnapshotManager(rocksdb::DB* db) : db_(db) {}
    
    const rocksdb::Snapshot* CreateSnapshot() {
        const rocksdb::Snapshot* snapshot = db_->GetSnapshot();
        snapshots_.push_back(snapshot);
        return snapshot;
    }
    
    void ReleaseSnapshot(const rocksdb::Snapshot* snapshot) {
        auto it = std::find(snapshots_.begin(), snapshots_.end(), snapshot);
        if (it != snapshots_.end()) {
            db_->ReleaseSnapshot(snapshot);
            snapshots_.erase(it);
        }
    }
    
    void ReleaseAll() {
        for (const rocksdb::Snapshot* snapshot : snapshots_) {
            db_->ReleaseSnapshot(snapshot);
        }
        snapshots_.clear();
    }
    
    size_t Count() const { return snapshots_.size(); }

private:
    rocksdb::DB* db_;
    std::vector<const rocksdb::Snapshot*> snapshots_;
};

五、快照与Compaction #

5.1 快照对Compaction的影响 #

text
Compaction与快照的关系:

1. 快照会阻止数据被清理
   - 有快照指向的旧版本数据不会被删除
   - 可能导致空间放大

2. 快照释放后
   - 旧版本数据可以被清理
   - Compaction可以正常进行

3. 建议
   - 及时释放不再使用的快照
   - 避免长时间持有快照

5.2 监控快照影响 #

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

void MonitorSnapshotImpact(rocksdb::DB* db) {
    // 获取快照数量
    uint64_t num_snapshots = 0;
    db->GetIntProperty("rocksdb.num-snapshots", &num_snapshots);
    std::cout << "Number of snapshots: " << num_snapshots << std::endl;
    
    // 获取最旧快照的序列号
    uint64_t oldest_snapshot_seq = 0;
    db->GetIntProperty("rocksdb.oldest-snapshot-sequence", &oldest_snapshot_seq);
    std::cout << "Oldest snapshot sequence: " << oldest_snapshot_seq << std::endl;
    
    // 获取当前序列号
    uint64_t current_seq = db->GetLatestSequenceNumber();
    std::cout << "Current sequence: " << current_seq << std::endl;
    
    // 计算快照滞后
    uint64_t lag = current_seq - oldest_snapshot_seq;
    std::cout << "Snapshot lag: " << lag << " versions" << std::endl;
}

六、高级应用 #

6.1 实现时间旅行查询 #

cpp
#include <rocksdb/db.h>
#include <map>
#include <string>
#include <chrono>

class TimeSeriesDB {
public:
    TimeSeriesDB(rocksdb::DB* db) : db_(db) {}
    
    // 记录带时间戳的快照
    void RecordSnapshot(const std::string& name) {
        const rocksdb::Snapshot* snapshot = db_->GetSnapshot();
        uint64_t timestamp = std::chrono::system_clock::now().time_since_epoch().count();
        snapshots_[name] = {snapshot, timestamp};
    }
    
    // 查询特定时间点的数据
    std::string GetAtTime(const std::string& key, const std::string& snapshot_name) {
        auto it = snapshots_.find(snapshot_name);
        if (it == snapshots_.end()) {
            return "";
        }
        
        rocksdb::ReadOptions options;
        options.snapshot = it->second.snapshot;
        
        std::string value;
        db_->Get(options, key, &value);
        return value;
    }
    
    // 清理旧快照
    void CleanupOldSnapshots(uint64_t max_age_ns) {
        uint64_t now = std::chrono::system_clock::now().time_since_epoch().count();
        
        for (auto it = snapshots_.begin(); it != snapshots_.end(); ) {
            if (now - it->second.timestamp > max_age_ns) {
                db_->ReleaseSnapshot(it->second.snapshot);
                it = snapshots_.erase(it);
            } else {
                ++it;
            }
        }
    }

private:
    rocksdb::DB* db_;
    
    struct SnapshotInfo {
        const rocksdb::Snapshot* snapshot;
        uint64_t timestamp;
    };
    
    std::map<std::string, SnapshotInfo> snapshots_;
};

6.2 实现乐观锁 #

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

class OptimisticLock {
public:
    OptimisticLock(rocksdb::DB* db, const std::string& key)
        : db_(db), key_(key), committed_(false) {
        // 创建快照作为版本基准
        snapshot_ = db_->GetSnapshot();
        
        // 读取当前值
        rocksdb::ReadOptions options;
        options.snapshot = snapshot_;
        db_->Get(options, key_, &original_value_);
    }
    
    ~OptimisticLock() {
        if (!committed_) {
            Rollback();
        }
        db_->ReleaseSnapshot(snapshot_);
    }
    
    std::string GetValue() const { return original_value_; }
    
    bool Commit(const std::string& new_value) {
        // 检查是否被修改
        std::string current_value;
        rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), key_, &current_value);
        
        bool modified = status.ok() ? (current_value != original_value_) : !original_value_.empty();
        
        if (modified) {
            return false;  // 冲突,提交失败
        }
        
        // 无冲突,提交
        status = db_->Put(rocksdb::WriteOptions(), key_, new_value);
        if (status.ok()) {
            committed_ = true;
            return true;
        }
        return false;
    }
    
    void Rollback() {
        // 乐观锁不需要显式回滚
        committed_ = false;
    }

private:
    rocksdb::DB* db_;
    std::string key_;
    const rocksdb::Snapshot* snapshot_;
    std::string original_value_;
    bool committed_;
};

// 使用示例
void DemoOptimisticLock(rocksdb::DB* db) {
    db->Put(rocksdb::WriteOptions(), "counter", "100");
    
    OptimisticLock lock(db, "counter");
    int value = std::stoi(lock.GetValue());
    value++;
    
    if (lock.Commit(std::to_string(value))) {
        std::cout << "Commit successful: counter = " << value << std::endl;
    } else {
        std::cout << "Commit failed due to conflict" << std::endl;
    }
}

七、最佳实践 #

7.1 快照使用建议 #

建议 说明
及时释放 不再使用时立即释放快照
使用RAII 用类封装快照生命周期
监控数量 关注快照数量和滞后
避免长时间持有 长时间快照会影响Compaction
批量操作 在快照内完成批量读取

7.2 性能影响 #

text
快照的性能影响:

1. 内存影响
   - 快照本身内存占用很小
   - 但会阻止旧版本数据清理

2. Compaction影响
   - 旧版本数据不能被删除
   - 可能导致空间放大

3. 读取性能
   - 快照读取需要检查版本
   - 性能略低于普通读取

4. 建议
   - 快照用完立即释放
   - 避免创建过多快照
   - 定期检查快照状态

八、总结 #

8.1 快照API速查 #

操作 方法 说明
创建快照 db->GetSnapshot() 创建当前状态快照
释放快照 db->ReleaseSnapshot(snapshot) 释放快照
使用快照读取 ReadOptions.snapshot = snapshot 设置读取选项
获取序列号 snapshot->GetSequenceNumber() 获取快照序列号

8.2 关键要点 #

  1. 一致性保证:快照提供一致性读取视图
  2. 轻量级:创建快照成本很低
  3. 及时释放:避免影响Compaction
  4. RAII封装:使用类管理快照生命周期
  5. 监控状态:关注快照数量和滞后

下一步,让我们学习高级特性!

最后更新:2026-03-27