diff --git a/PLAN.md b/PLAN.md index 789a71c..d19008b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,5 +1,17 @@ ## 1. 项目概述 +### 项目背景 + +本项目基于LevelDB源代码进行修改和优化。`LevelDB`使用`LSM Tree`的数据结构,是`key-value`数据库的典型。LSM树后台为了加速查询,将键值对从磁盘里读取、排序再写入,会带来50倍以上的写放大。这种对HDD有利的优化方式不适用于当下使用较多的SSD,因此我们需要对其功能进行修改和优化。 + +### 实现功能 + +本项目要实现的内容及目的是: + ++ **字段设计**:模仿关系型数据库,扩展`value`的结构,在`value`中多个字段,并可以通过这些字段进行查询对应的`key`,实现类似关系数据库中按列查询的功能 + ++ **KV分离**:分离存储LevelDB的`key`和`value`,LSM树中的value为一个指向`Value Log`文件的指针,用户的真实`value`存储在`Value Log`中,减轻LSM树的存储负载,大幅度减小了读写放大的性能影响 + ## 2. 功能设计 #### 2.1. 字段设计 @@ -154,11 +166,148 @@ ## 5. 功能测试 +### 单元测试 + +对于实现的代码,首先设计测试用例验证其功能的正确性。 + +#### 1. 字段 + +在这一部分中,测试用例需要考虑到能否正确存入含有多字段的`value`,并正确读取,以及是否能根据目标字段找到对应的所有`key`。 + +```c++ +// 测试能否正确存入和读取 +TEST(TestField, PutGet) { + std::string key = "k_1"; + + FieldArray fields = { + {"name", "Arcueid"}, + {"address", "tYpeMuuN"}, + {"phone", "122-233-4455"} + }; + + std::string value = SerializeValue(fields); + db->Put(WriteOptions(), key, value); + + std::string value_ret; + db->Get(ReadOptions(), key, &value_ret); + auto fields_ret = ParseValue(value_ret); + + assert(fields == fields_ret); +} +``` + +```c++ +// 测试能否根据字段查找key +TEST(TestField, SearchKey){ + std::string key = "k_1"; + std::vector keys = ["k_1", "k_2", "k_3"]; + Field field_test = {"test_name", "Harry"}; + FieldArray fields = { + {"name", "Arcueid"}, + {"address", "tYpeMuuN"}, + {"phone", "122-233-4455"}, + field_test + }; + + std::string value = SerializeValue(fields); + for(auto key : keys){ + db->Put(WriteOptions(), key, value); + } + + std::vector key_ret = FindKeysByField(db, &field_test); + + assert(keys == key_ret); +} +``` + +#### 2. KV分离 + +```c++ +// 测试KV分离的写入与读取的正确性 +TEST(TestKVSeparate, PutGet){ + std::string key = "k_1"; + std::string value = "ar"; + std::value_addr; + + db->Put(WriteOptions(), key, value, &value_addr); + + // 测试能否通过存储的value_addr读取对应vlog中存储信息 + std::value value_ret; + GetValue(&value_addr, &value_ret); + assert(value_ret == value); + + // 测试能否直接通过key读取对应value + db->Get(ReadOptions(), key, &value_ret); + assert(value_ret == value); +} +``` + +### 性能测试 Benchmark + +设计代码测试读、写、扫描、字段查询等操作的**吞吐量、延迟和写放大**情况,反映LevelDB的性能。 + +```c++ +// 测试吞吐量 +void TestThroughput(leveldb::DB* db, int num_operations) { + auto start_time = std::chrono::steady_clock::now(); + + for (int i = 0; i < num_operations; ++i) { + // Operations + } + + auto end_time = std::chrono::steady_clock::now(); + auto duration = chrono::duration_cast( + end_time - start_time).count(); + cout << "Throughput: " << num_operations * 1000 / duration << " OPS" << endl; +} +``` + +```c++ +// 测试延迟 +void TestLatency(leveldb::DB* db, int num_operations, + std::vector &lat_res) { + int64_t latency = 0; + + auto end_time = std::chrono::steady_clock::now(); + auto last_time = end_time; + + for (int i = 0; i < num_operations; ++i) { + // Operations + + end_time = std::chrono::steady_clock::now(); + latency = std::chrono::duration_cast( + end_time - last_time).count(); + last_time = end_time; + + lat_res.emplace_back(latency); + } +} +``` + +对于写放大,通过LevelDB的log信息计算系统的写放大。 + ## 6. 可能遇到的挑战与解决方案 -## 7. 分工和进度安排 +### 字段设计可能的问题 ++ 字段数量过多可能导致存储结构复杂,更改字段时性能下降 ++ 字段可能包含多种类型,增加解析复杂性 ++ 不同字段大小差异大,导致读写性能下降 +### KV分离可能的问题 ++ 数据一致性:Key和Value存储位置不同,写入或删除时需要保证一致性 ++ 系统崩溃、磁盘损坏等可能导致分离存储的Value丢失 ++ 分离存储的Value文件的大小优先,如何合适地存储超大Value ++ 分离存储导致读写时需要经过索引,读写放大产生性能影响 +#### 解决方案待定 + +## 7. 分工和进度安排 +| 功能 | 完成日期 | 分工 +| :------: | :-----: | :-----:| +| Value字段设计及实现 | 12月6日 | 李畅 | +| KV分离基本功能实现 | 12月24日 | 韩晨旭 | +| 改进测试样例并运行测试 | 12月29日 | 韩晨旭、李畅 | +| 对照性能测试优化功能 | 1月4日 | 韩晨旭、李畅 | \ No newline at end of file