10225501448 李度 10225101546 陈胤遒 10215501422 高宇菲
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
augurier e5bd459f58 部分实验报告,完善测试 8 miesięcy temu
.github/workflows Fix GitHub CI on Linux. 删除 2 lat temu
benchmarks Merge branch 'cyq' into ld 删除 8 miesięcy temu
cmake Align CMake configuration with related projects. 删除 5 lat temu
db 使用SliceHashTable代替unordered_set<string>,并增加了一些benchmark 删除 8 miesięcy temu
doc The master branch was renamed to main. 删除 3 lat temu
fielddb 上条没注释完全 删除 8 miesięcy temu
helpers/memenv Remove main() from most tests. 删除 3 lat temu
include/leveldb fielddb的benchmark、性能插桩以及一个对比的testdb 删除 8 miesięcy temu
issues Remove main() from most tests. 删除 3 lat temu
port Support Zstd compression level in Leveldb 删除 2 lat temu
table leveldb: Check slice length in Footer::DecodeFrom() 删除 2 lat temu
test 部分实验报告,完善测试 删除 8 miesięcy temu
testdb 查出了性能测试destroy错误的问题 删除 8 miesięcy temu
third_party Roll third_party/benchmark to f7547e29ccaed7b64ef4f7495ecfff1c9f6f3d03 删除 2 lat temu
util Merge branch 'cyq' into ld 删除 8 miesięcy temu
.clang-format Consolidate benchmark code to benchmarks/. 6 lat temu
.gitignore fielddb的benchmark、性能插桩以及一个对比的testdb 8 miesięcy temu
.gitmodules Added google/benchmark submodule. 4 lat temu
3DB设计.md 并发控制的基本框架和大部分实现 8 miesięcy temu
AUTHORS Release LevelDB 1.14 12 lat temu
CMakeLists.txt fielddb的benchmark、性能插桩以及一个对比的testdb 8 miesięcy temu
CONTRIBUTING.md Update contributing guidelines. 3 lat temu
LICENSE reverting disastrous MOE commit, returning to r21 14 lat temu
NEWS sync with upstream @ 21409451 14 lat temu
README.md 部分实验报告,完善测试 8 miesięcy temu
TODO Update to leveldb 1.6 13 lat temu
设计文档.md 分工与补充 9 miesięcy temu

README.md

实验报告

仓库地址 https://gitea.shuishan.net.cn/10225501448/leveldb_proj2

1. 项目概述

leveldb中的存储原本只支持简单的字节序列,在这个项目中我们对其功能进行拓展,使其可以包含多个字段,并通过这些字段实现类似数据库列查询的功能。但如果仅通过字段查找数据,需要对整个数据库的遍历,不够高效,因此还要新增二级索引,提高对特定字段的查询效率。

2. 功能实现

2.1 字段

设计目标:对value存储读取时进行序列化编码,使其支持字段。

实现思路:设计之初有考虑增加一些元数据(例如过滤器、字段偏移支持二分)来加速查询。但考虑到在数据库中kv的数量是十分庞大的,新加数据结构会带来巨大的空间开销。因此我们决定在这里牺牲时间换取空间,而将时间的加速放在索引中。
在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法PutLengthPrefixedSlice存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。

std::string SerializeValue(const FieldArray& fields){
    std::sort(sortFields.begin(), sortFields.end(), compareByFirst);
    for (const Field& pairs : sortFields) {
        PutLengthPrefixedSlice(&result, pairs.first);
        PutLengthPrefixedSlice(&result, pairs.second);
    }
    return result;
}

最终db类提供了新接口putFields, getFields,分别对传入的字段序列化后调用原来的put, get接口。
FindKeysByField调用NewIterator遍历所有数据,field名和值符合则加入返回的key中。
这一部分的具体代码在util/serialize_value.cc中

2.2 二级索引

设计目标:对某个字段(属性)建立索引,提高对该字段的查询效率。

2.2.1 总体架构

fielddb

2.2.2 如何并发创删索引与读写

request

2.2.3 如何保证两个kv与index的一致性

metadb

3. 测试

3.1 正确性测试

相关代码在test/

3.1.1 封装函数功能

相关代码在helper.cc
在所有测试中,绝大部分key的值数字>0,同时也支持单独写入key<=0,方便测试观察。
value一共使用到了三个字段:name,address,age。name是"customer#+"key,address每次在给定的城市数组中抽取一个,age限定在0~100, 因此后两者会有大量重复,测试中的索引都是建立在这两个字段上。
由于测试中需要检查写入时与查询时,拥有相应的字段的kv是否对应,因此需要维护一个set<key>。又因为测试涉及到并发,因此封装了一个线程安全的ThreadSafeSet类,对于set的操作有锁保护。具体测试中使用两个该类统计了两类key:address为shanghai的key和age为20的key。

ThreadSafeSet shanghaiKeys; 
ThreadSafeSet age20Keys;

下面大致介绍一下封装函数的功能:

InsertOneField:只插一条特定数据的测试(让key<=0, 和批量写入区别开)。使用它是为了保证有些地方的写入必须正确,并通过追踪对应的key发现系统中潜在的问题。

DeleteOneField:只删一条特定数据的测试,功能与单条插入相似,主要用来测试delete。
GetOneField:只读一条特定数据的测试,与上面两条对应。

对于所有涉及随机性的函数,传参一个随机种子。只要随机种子一致,随机生成的内容就一致,相应的读需要和相应的写、删保持一致。此外测试的数据量也是可以修改的。

InsertFieldData:批量写入,按照上面提到的测试规则生成kv,并调用putfields。检查返回状态是否ok。同时对于生成的字段,如果是address为shanghai或age为20,统计入相应的set。

DeleteFieldData:批量删除,按照规则(>0)生成key,并调用delete。检查返回状态是否ok。

WriteFieldData:与InsertFieldData相似,但生成kv后不直接调用putfields,而是写入writebatch,全部生成完在调用write把batch写入数据库。用来测试write功能,检查返回状态是否ok。

GetFieldData:测试读,按照规则(>0)生成key,并调用getfield。由于并发时不一定能读到,提供一个allownotfound参数。如果true,返回状态可以是notfound。如果false,就只能是ok。对于读到的field,检测是否被正确解析,以及每个字段是否满足规则。

GetDeleteData:检查对应种子的所有生成数据有没有删除干净(getfield的所有返回状态都是notfound)。

findKeysByCity:调用FindKeysByField({"address", "Shanghai"}),检查返回的所有key是否都在shanghaiKeys中(insert时统计的)。

findKeysByCityIndex:提供一个参数haveIndex,表明数据库有没有该索引(address)。调用querybyindex({"address", "Shanghai"}),如果haveindex检测返回状态ok,并且所有的key都要在shanghaiKeys中。

findKeysByAgeIndex:与上条相同,检测age字段。

checkDataInKVAndIndex: 在并发写删与恢复测试中,不能保证每个种子序列的所有key都在数据库中。但需要保证的是,kv和index中的数据是一致的。这里先后调用QueryByIndex和FindKeysByField,比较得到的key,确保两者一致。

3.1.2 基础测试

相关代码在basic_function_test.cc
这一部分主要测试每个功能在正常使用中是否正确,按照逻辑简单调用封装的功能函数。

TestLab1流程: 批量写 -> 必须读到 -> findkeysbycity -> 批量删 -> 必须全读不到。

TestLab2流程:批量写 -> 创索引address,age -> 索引查询address,age -> 删索引address -> 索引查询address(haveindex=false) -> 索引查询age -> 批量删 -> 索引查询age(索引还在能查,但返回的key数量为0) -> write -> 必须读到 -> 索引查询age。

至此,上面的流程基本覆盖了我们数据库的每个基础功能。

3.1.2 并发测试

相关代码在parallel_test.cc
这一部分主要测试读、写、创删索引之间的并发。每个测试中并发线程的数量也是可以修改的。

TestReadPut:创索引 -> 并发:两线程写(不同随机种子)三线程读(不保证能读到)-> 读两次写相应的随机种子(必须读到)-> 索引查(返回的key在两次写入中) -> 检查两数据库一致性(checkDataInKVAndIndex)。

TestPutCreatei:先批量写入一次数据 -> 并发:一线程创索引,一线程忙等,至数据库开始创建索引后单条写入 -> 检测索引创建是否成功(创建索引前批量写的数据,能通过索引查得到)-> 读单条写入 -> 检测数据库一致性。

TestCreateiCreatei:先批量写入一次数据 -> 并发:三线程创索引address -> 索引查 -> 一致性 -> 并发:两线程删索引address,一线程创索引age -> 索引查(address无,age有)-> 一致性。

TestPutDeleteOne(有索引时,大量并发put与delete相同key,确保kvdb和indexdb的一致性): 创索引 -> 并发:对于100条数据,10线程插入,10线程删除 -> 一致性。

TestPutDelete:创索引 -> 并发:两线程写0、1种子数据,两线程删0、1种子数据 -> 一致性。

TestWrite(在之前基础上并发所有,并加入write):创索引address、批量写种子2 -> 并发:线程0创索引age,其他线程忙等至开始创建,线程1批量写种子0,线程2write种子1, 线程3删索引age -> 检测:种子012所有数据都应被读到,一致性, age索引被删除。
这里的测试也可以加入delete,或不删索引age检测age的一致性,具体见注释。

至此,上面的流程基本覆盖了我们数据库的每个基础功能之间的并发。

3.1.2 恢复测试

相关代码在recover_test.cc
这一部分主要测试正常与异常的恢复。

TestNormalRecover:创索引、批量写、此时之前测试都检测过能被读到 -> delete db -> 重新open -> 读数据、索引查(之前写入的数据仍能被读到)。

TestParalRecover该测试比较特别,需要运行两次:创索引 -> 并发:线程0批量写,线程1write,线程2delete,线程3 在单条插入后,deletedb。线程3导致了其他线程错误,测试会终止(模拟数据库崩溃),这会导致各线程在各种奇怪的时间点崩溃。此时注释掉上半部分代码,运行下半部分:单条写入能被读到,并检测一致性。
这里我们运行了几十次,前半部分的崩溃报错有多种,但后半部分的运行都是成功的。同时也追踪了恢复的运行过程,确实有数据从metadb中被正确解析。

3.2 性能测试

测试、分析、优化

4. 问题与解决

5. 潜在优化点

6. 分工