10215300402 朱维清 10222140408 谷杰
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
VirgilZhu 06fa3f20b3 init design md 1ヶ月前
.github/workflows Fix GitHub CI on Linux. 删除 1年前
benchmarks Support Zstd compression level in Leveldb 删除 1年前
cmake Align CMake configuration with related projects. 删除 5年前
db Allow compaction to finish in TEST_CompactRange 删除 4ヶ月前
doc The master branch was renamed to main. 删除 2年前
helpers/memenv Remove main() from most tests. 删除 3年前
image init design md 删除 1ヶ月前
include/leveldb 添加ttl测试用例 删除 2ヶ月前
issues Remove main() from most tests. 删除 3年前
port Support Zstd compression level in Leveldb 删除 1年前
table leveldb: Check slice length in Footer::DecodeFrom() 删除 1年前
test 修改ttl随机种子 删除 1ヶ月前
third_party Roll third_party/benchmark to f7547e29ccaed7b64ef4f7495ecfff1c9f6f3d03 删除 1年前
util Merge pull request #1104 from reillyeon:chromium_env 删除 1年前
.clang-format Consolidate benchmark code to benchmarks/. 5年前
.gitignore 添加ttl测试用例 2ヶ月前
.gitmodules Added google/benchmark submodule. 4年前
AUTHORS Release LevelDB 1.14 11年前
CMakeLists.txt 添加ttl测试用例 2ヶ月前
CONTRIBUTING.md Update contributing guidelines. 2年前
LICENSE reverting disastrous MOE commit, returning to r21 13年前
NEWS sync with upstream @ 21409451 13年前
README.md init design md 1ヶ月前
TODO Update to leveldb 1.6 12年前

README.md

LevelDB: Value Field & KV Separation 设计文档

项目概述

**项目背景:**LevelDB 项目的 KV 键值对存储信息单一;LSM-Tree 读写放大开销大,导致 LevelDB 顺序范围查询时的数据吞吐量随 Value 大小增加而急剧下降。

项目目标 & 实现目的

  1. 实现 LevelDB 的 value 字段功能
    • 实现类似 关系数据库-表格-列 和 文档数据库-文档-字段 的多字段功能设计;
      • 提供高效的接口来读写 value、通过 value 查询多个匹配的 Key
    • 使 LevelDB 同时具有 高性能读写大量键值对 和 多字段数据存储 的功能。
  2. 实现 KV 分离(分离存储、点查、范围查询、GC 机制)
    • 减小 LevelDB 的读写放大;
    • 减小 LSM-Tree 的层级;
    • Compaction 不需重写 value;
    • 一个 SSTable 的 Block 能存更多 Key,有利于减少读 LSM-Tree 的开销;
    • Cache 能储存更多 SSTable,减少磁盘 I/O 开销。
  3. 实现 Benchmark,测试并分析性能:
    • 验证功能扩展(value 多字段设计 & KV 分离)的有效性;
    • 评估性能提升(读写 Throughput 性能、读写放大、LSM-Tree 层级、随机点查/顺序范围查询效率(延迟)、GC 开销与性能影响、各读写阶段耗时占比 (Wait / Log / Memtable / Other))
    • 分析资源利用率(硬盘 I/O 、内存负载)
    • 比较大/小数据集、高并发场景的表现
    • 找到性能瓶颈并针对优化。

功能设计

2.1 字段设计

  • 设计目标:
    1. 将 value 设计为一个字段数组,每个元素对应一个字段 Fieldfield_name: field_value
    2. 对于 value 存储时的字符串类型值与真实含义的字段数组,采用字段数组的序列化与字符串的解析实现两者的转化;
    3. 允许在存储 KV 键值对前修改字段内容;
    4. 允许通过字段值查询所有对应的 key。
  • 实现思路:
    1. 设计类 Fields 来操作字段数组,与字段相关的字段截取、读写操作、序列化等函数均在 Fields 类中实现;
    2. 通过字段查询 Key :实现函数 FindKeysByField,传入若干字段名和字段值(即子字段数组),遍历查找 LSM-Tree 找到对应的若干 key。

2.2 KV 分离

  • 设计目标:
      • 对于大 value,分离 KV 存储,减小 LevelDB 的读写放大;
      • 对于小 value,以 KV 键值对的正常形式存入 LSM-Tree;
    1. 对于 vLog 有 GC 机制,可以定期和在 vLog 超出限制大小时触发,回收 vLog 中的失效 value 并更新 vLog 与 LSM-Tree;
    2. 确保操作的原子性。
  • 实现思路:
    1. vLog 有若干个,仅保存大于阈值的大 value 对应的 KV 数据对,且为 Append-Only,仅有一个 vLog 是活跃的(可插入新 value 与 key),具有一个垃圾计数器,当计数器超过阈值,触发 GC,并被重写
    2. 原 LSM-Tree 中的 KV 键值对,大 value(value 存入 vLog 的情况)改为 <key, <fileno, offset>> 的形式;小 value 则与原 LevelDB SSTable 中 KV 键值对存储格式相同
    3. 当前活跃的 vLog 超过限制大小后,新建一个文件作为新的活跃 vLog;
    4. 将大 value 写入当前活跃 vLog 后,获得该 value 在 vLog 的 offset 和 vLog 的编号 fileno ,将 <key, <fileno, offset>> 写入 WAL 和 Memtable 中;
    5. GC 的触发时机为某个 vLog 的垃圾计数器到达阈值时,GC 过程重写目标 vLog 时,若当前处理的 KV 键值对的 key 在 LSM-Tree 中不存在或已更新,则忽略这个 KV 键值对;
      • 新的 vLog 被重写完毕后,更新 vLog 中 KV 的新 <fileno, offset> 到 LSM-Tree 中;
      • 为了避免以下情况:用户在之前的时间戳删除了 LSM-Tree 中的某个 key,但当前时间戳 GC 导致重写后的 vLog 中的 KV 键值对被回写到 LSM-Tree 中更高层的 SSTable,相当于该 key 对应的 KV 键值对被额外重新插入了,新写入数据在旧写入数据下层的问题——每次读取数据需要遍历查找每一层的所有 SSTable。

数据结构设计

3.1 字段功能

  • Field & FieldArrayusing Field = std::pair<std::string, std::string>; using FieldArray = std::vector<std::pair<std::string, std::string>>;
  • class Fields,用于操作字段数组。

3.2 KV 分离

接口/函数设计

4.1 字段功能

  • class Fields

    class Fields {
    public:
        // 从 FieldArray 构造
        explicit Fields(FieldArray field_array);
        // 从字符串解码构造
        explicit Fields(const std::string& value_str);
      
        ~Fields();
      
        // 更新字段值
        void update_field(const std::string& name, const std::string& value);
        // 获取字段
        Field get_field(const std::string& name) const;
        // 检查字段是否存在
        bool has_field(const std::string& name) const;
        // 序列化字段数组为字符串
        std::string Serialize() const;
        // 重载运算符 [] 用于访问字段值
        std::string operator[](const std::string& name) const;
        // 重载运算符 [] 用于修改字段值
        std::string& operator[](const std::string& name);
        // 重载运算符 == 用于比较两个 Fields 是否相等
        bool operator==(const Fields& other) const;
    };
    

4.2 KV 分离

  • 大 value 的 key 对应的 value 存储位置:VPtr

    struct VPtr {
        int fileno;      // VLog 文件号
        uint64_t offset; // 偏移量
    };
    
  • class VLog

    class VLog {
    private:
        // 该 VLog 是否活跃,即可插值
        bool activate_;
          
        // 最大 VLog 大小
        std::size_t maxSize_;
          
        // GC 计数器
        std::size_t deadkeyCount;
          
    public:
        // 构造函数,默认赋值 GC 计数器为 GC 触发的最大阈值
        VLog(bool activate, std::size_t maxSize, std::size_t gcThreshold)
            : activate_(activate), maxSize_(maxSize), deadkeyCount(gcThreshold) {}
          
        // 向 VLog 中添加一个新的键值对
        virtual void append(const std::string& key, const std::string& value) = 0;
          
        // 查找给定键对应的值
        virtual VPtr lookup(const std::string& key) const = 0;
          
        // 执行垃圾回收操作
        virtual void GarbageCollection() = 0;
          
        virtual ~VLog() {}
    };
    

功能测试

单元测试(测试用例)

  • 字段功能:
    1. (测试是否能序列化 FieldArray、反序列化解析 value ;)
    2. 测试是否能成功写入、点查与范围查询;
    3. 测试是否能通过 value 查询所有匹配的 Key。
  • KV 分离:
    1. 测试大小 value 与对应 key 是否按规则存入 LSM-Tree 或 VLog;
    2. 测试是否能通过 key 与 VPtr 找到 VLog 中正确的 value;
    3. 测试 GC 流程是否正确,包括是否成功触发、重写 VLog、回写 LSM-Tree 的 key 对应的 VPtr

性能测试(Benchmark)

  1. 测试不同 value 大小的 Throughput;
  2. 测试读写放大的减小程度;
  3. 测试随着插入数据量,LSM-Tree 层级的减小程度;
  4. 测试随机点查 / 顺序范围查询效率;
  5. 测试 GC 开销;
  6. 测试各读写阶段耗时占比(Wait / Log / Memtable / Other);
  7. 比较大/小数据集、高并发场景的表现;
  8. (比较资源利用率(硬盘 I/O 、内存负载);)

可能遇到的挑战与解决方案

  1. KV 分离中 VLog 的 GC 开销可能较大:GC 过程中需要扫描和重写 vLog,以及回写 LSM-Tree,需要遍历所有 SSTable,开销较大;
  2. 数据一致性问题:若程序突然停止运行,可能导致数据不一致的情况,目前暂时没想到恢复机制;
  3. 读、写、GC 等流程的持锁处理;
  4. 由于每次读取需要遍历整个 LSM-Tree 且由于 vLog 是 Append-Only 的无序存储,导致顺序读取的效率可能不比随机读取高多少,开销大,需要实现 value 预读机制,提前预读取下一个 value。

分工和进度安排

功能 预计完成日期 分工
Fields 类和相关接口实现 12月3日 朱维清、谷杰
测试实现字段功能 12月5日 谷杰
VLog 类和相关接口实现 12月15日 朱维清、谷杰
测试实现 KV 分离 12月19日 朱维清
Benchmark 测试(吞吐量、写放大、点查范围查) 12月26日 谷杰
Benchmark 测试(GC 开销、大小数据集、并发) 12月26日 朱维清