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

本文档涵盖的设计内容只是最初的设想，实现过程中大概率会进行调整甚至重构。各部分也将在项目落实的过程中进行补充完善。

# 2. 功能设计
## 2.1 字段设计
设计目标：对value存储读取时进行序列化编码，使其支持字段。  

实现思路：设计之初有考虑增加一些元数据（例如过滤器、字段偏移支持二分）来加速查询。但考虑到在数据库中kv的数量是十分庞大的，新加数据结构会带来巨大的空间开销。因此我们决定在这里牺牲时间换取空间，而将时间的加速放在索引中。  
在这一基础上，我们对序列化进行了简单的优化：将字段名排序后，一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销，而好处在于遍历一个value的字段时，如果得到的字段名比目标大，就可以提前结束遍历。 
```
std::string SerializeValue(const FieldArray& fields){
    FieldArray sortFields = fields;
    std::sort(sortFields.begin(), sortFields.end(), compareByFirst);
    std::string result;
    for (const Field& pairs : sortFields) {
        PutLengthPrefixedSlice(&result, pairs.first);
        PutLengthPrefixedSlice(&result, pairs.second);
    }
    return result;
}
``` 
最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。  
`FindKeysByField`调用`NewIterator`遍历所有数据，field名和值符合则加入返回的key中。

## 2.2 二级索引
设计目标：对某个字段或属性建立索引，提高对该字段的查询效率。需考虑索引的创建、删除、维护。    

实现思路： 这一部分的难点主要在于，索引数据与原数据的存储需要进行隔离，不同操作之间存在同步与异步问题，还需要考虑两种数据间的一致性。为了使设计简洁化，避免不同模块耦合带来潜在的问题，我们的设计如下：  
1. 总体上，我们对两种数据分别创建一个db类的对象`kvDb, indexDb`。对外的接口类`FieldDb`包含了这两个对象，提供原先的leveldb各种接口，以及新功能，并在这一层完成两个对象的管理。  
2. `kvDb`与原先的存储一致，`indexDb`中key是原key与索引字段的联合编码，value是空（详见数据结构设计），并且lab1中新增的接口也不会被调用到。  
3. 在open新的fieldDb时，会创建这两个对象并open他们。fieldDb会维护一个字符串数组`fieldWithIndex`，记录了当前哪些字段名拥有索引。一个`pair<indexStatus, fieldName>`的队列`taskQueue`， 维护了创建或者删除索引的任务请求。创建或删除索引时，把任务加入队尾并休眠。一个任务完成后唤醒队首继续下一个任务（类似write机制）  
```
class FieldDb {
    Db *kvDb;
    Db *indexDb;
    string[] fieldWithIndex;
    queue<pair<bool indexStatus, std::string fieldName>> taskQueue;
            // 0建 or 1删，或把pair抽象成一个类

    //原db所有对外接口，下面没提就调用kvDb相应函数
    bool CreateIndexOnField(const std::string& fieldName){、
        //加入taskQueue并休眠/拿写锁
        //kvdb->FindKvsByField得到数据信息
        //编码
        //kvdb->writeIndexLog写索引日志
        //indexDb->put存储索引
        //fieldWithIndex.insert(fieldName)
        //唤醒taskQueue队首/释放写锁
    }
    bool DeleteIndex(std::string &fieldName);    //同上
    std::vector<std::string> QueryByIndex(Field &field){
        //判断fieldWithIndex是否有
        //indexDb->iterator得到编码后的数据信息
        //解码返回
    }
}
```
4. 一个创建/删除索引任务开始时，首先锁住写锁，再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值)，对返回的数组中每个元素一一与字段名编码成新key，**合并在一个writebatch中写入日志**，再调用`indexDb`中的put或delete。当`indexdb`把所有操作执行完后，在`fieldWithIndex`中添加/删除这个`fieldName`，使得用户可以针对这个字段进行索引读，最后唤醒下一个任务，没有则释放写锁。  
5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者，简单调用`kvDb`中的对应接口（不涉及索引）。  
对`putFields`，先判断是否有`fieldWithIndex`中有的字段，如果有，并对`kvDb`调用一个(多个)`put`，**但在写日志时一并加上索引日志写入**。  
`delete`逻辑一致。  
6. 针对索引的日志：为了保证两个数据库间的一致性，由`kvDb`的日志模块统一管理。这其中包含了两种chunk（kv写入和索引写入），在恢复时需要分别解析，决定往哪一个数据库中写入。索引写入的时机在4、5中的**加粗**部分，如何编码还有待设计。也就是说，`indexDb`本身的日志模块不再起到作用，项目后期可以修改关闭这一部分。
7. 对两个数据库的其他部分，理论上每个数据库内部的其他模块不会互相影响。  


# 3. 数据结构设计
具体设计模块化，实现时再具体考虑。  
`indexDb`的kv编码：**暂时考虑助教文档那种**  
区分日志中kv部分和index部分：思路是在writebatch中某个地方加个标识区分，每一类的编码与各自的key编码类似**细节有待完成**

# 4. 接口/函数设计
`FieldDb`的对外接口函数之前已展示，这里补充一些子数据库需提供给`FieldDb`的抽象功能（暂时想到的）：
```
class Db{
    //原有部分和lab1部分
    //kvdb需要：
    pair<slice key, string fieldVal> FindKvsByField(string fieldName); //搜集索引需要的数据信息
    status writeIndexLog(string fieldName, pair<slice key, string fieldVal>); //向indexDb put前先写日志
}
```
类内部实现的功能函数，具体实现过程中再抽象。 

# 5. 功能测试
1. 基本的每个接口函数调用。    
2. 创建/删除索引时的并发读写、并发创/删。
3. 数据库的恢复检查。  
4. 性能测试。  

# 6. 可能的挑战与解决方案
已想到的部分在之前已阐述，其余待发现。  
mark一些原代码(db中)的修改点：recover时日志解析修改，write时的日志写入可能要合并索引的写入。

# 7. 分工与进度安排
 功能 | 完成日期 | 分工 
:------|:---------|:------
value序列化|11.19 | 李度
fieldDb接口|11.25|陈胤遒
lab1整体+测试|11.30|高宇菲
fieldDb功能实现|12.10|李度
kvdb功能实现与原代码修改|12.10|陈胤遒
整体系统整合+测试|12.20|李度、陈胤遒、高宇菲
性能测试|12.30|高宇菲
