ソースを参照

增加了部分注释,推进了部分实验报告

cyq
cyq 8ヶ月前
コミット
d9ff6d8a3d
3個のファイルの変更190行の追加55行の削除
  1. +137
    -2
      README.md
  2. +34
    -34
      benchmarks/db_bench_FieldDB.cc
  3. +19
    -19
      fielddb/field_db.h

+ 137
- 2
README.md ファイルの表示

@ -642,12 +642,147 @@ TestNormalRecover:创索引、批量写、此时之前测试都检测过能被
TestParalRecover**该测试比较特别,需要运行两次**:创索引 -> 并发:线程0批量写,线程1write,线程2delete,线程3 在单条插入后,deletedb。线程3导致了其他线程错误,测试会终止(模拟数据库崩溃),这会导致各线程在各种奇怪的时间点崩溃。此时注释掉上半部分代码,运行下半部分:单条写入能被读到,并检测一致性。
这里我们运行了几十次,前半部分的崩溃报错有多种,但后半部分的运行都是成功的。同时也追踪了恢复的运行过程,确实有数据从metadb中被正确解析。
### 3.2 性能测试
测试、分析、优化
### 3.2 性能测试、分析、优化
#### 3.2.1 性能测量的实现
我们主要采用了外部测量和内部测量相互结合的方式,来评估数据库系统的性能表现和定位性能瓶颈。外部测量的方式主要借助于benchmark完成。内部测量的主要采用插桩来测量数据库的各个部分的性能损耗情况。
相较于原版的leveldb,FieldDB增加了Field和二级索引功能,因此我们针对新的常见使用场景,增加了benchmark的测试点。新增部分有:
```c++
"CreateIndex," //创建索引
"FindKeysByField," //得到包含所有Field的KV对(不使用索引)
"QueryByIndex," //通过索引得到对应的主键
"DeleteIndex," //删除索引
"WriteSeqWhileCreating," //创建索引的同时顺序写
"WriteSeqWhileDeleting," //删除索引的同时顺序写
"WriteRandomWhileCreating," //创建索引的同时随机写
"WriteRandomWhileDeleting," //删除索引的同时随机写
"ReadSeqWhileCreating," //创建索引的同时顺序读
"ReadSeqWhileDeleting," //删除索引的同时顺序读
"ReadRandomWhileCreating," //创建索引的同时随机读
"ReadRandomWhileDeleting," //删除索引的同时随机读
"WriteRandomWithIndex," //随机写带索引的键值
"WriteSeqWithIndex," //顺序写带索引的键值
"WriteSeqWhileIndependentCCD," //在不断创建删除索引的情况下,顺序写与创删索引无关的数据
"WriteSeqWhileCCD," //在不断创建删除索引的情况下,顺序写与创删索引有关的数据
```
通过上述新增加的benchmark,可以更加全面的了解增加了新功能后的,各个常见使用场景下的FieldDB的性能指标。各个benchmark的具体实现可以在`/becnmarks/db_becnh_FieldDB.cc`中找到。
为了能够进一步的定位性能瓶颈,我们对于操作的关键路径进行了层次化的插桩分析,实现更加精准的性能测量。根据外部测量得到的数据,相较于leveldb,对于读性能,FieldDB几乎没有影响,但是对于写性能,FieldDB性能有所下降,因此我们着重使用插桩分析了写入的关键路径。由于所收集的数据如下:
```c++
int count = 0;//总计完成请求数量
int count_Batch = 0;//总计完成的Batch数量
int count_Batch_Sub = 0;//总计完成的Batch_sub数量
uint64_t elapsed = 0;//总计时间消耗
uint64_t construct_elapsed = 0;//构建写入内容消耗
uint64_t construct_BatchReq_init_elapsed = 0;//请求初始化消耗
uint64_t construct_BatchReq_elapsed = 0;//构建batch的消耗
uint64_t construct_BatchReq_Sub_elapsed = 0;//构建batch_sub消耗
uint64_t construct_BatchReq_perSub_elapsed = 0;//每个Batch_sub消耗
uint64_t construct_FieldsReq_Read_elapsed = 0;//构建时读取的消耗
uint64_t write_elapsed = 0;//写入的总耗时
uint64_t write_meta_elapsed = 0;//写入meta的耗时
uint64_t write_index_elapsed = 0;//写入index的耗时
uint64_t write_kv_elapsed = 0;//写入kv的耗时
uint64_t write_clean_elapsed = 0;//清除meta的耗时
uint64_t write_bytes = 0;
uint64_t write_step = 500 * 1024 * 1024;
uint64_t write_bytes_lim = write_step;
uint64_t temp_elapsed = 0;
uint64_t waiting_elasped = 0; //等待耗时
inline void dumpStatistics() {
if(count && count % 500000 == 0 || write_bytes && write_bytes > write_bytes_lim) {
std::cout << "=====================================================\n";
std::cout << "Total Count : " << count;
std::cout << "\tTotal Write Bytes(MB) : " << write_bytes / 1048576.0 << std::endl;
std::cout << "Average Time(ms) : " << elapsed * 1.0 / count;
std::cout << "\tAverage Write rates(MB/s) : " << write_bytes / 1048576.0 / elapsed * 1000000 << std::endl;
std::cout << "Construct Time(ms) : " << construct_elapsed * 1.0 / count << std::endl;
std::cout << "\tConstruct BatchReq Init Time(ms) : " << construct_BatchReq_init_elapsed * 1.0 / count << std::endl;
std::cout << "\tConstruct BatchReq Time(ms) : " << construct_BatchReq_elapsed * 1.0 / count << std::endl;
std::cout << "\tConstruct BatchReq Sub Time(ms) : " << construct_BatchReq_Sub_elapsed * 1.0 / count << std::endl;
std::cout << "\tConstruct BatchReq perSub Time(ms) : " << construct_BatchReq_perSub_elapsed * 1.0 / count_Batch_Sub << std::endl;
std::cout << "\tConstruct FieldsReq Read Time(ms) : " << construct_FieldsReq_Read_elapsed * 1.0 / count << std::endl;
std::cout << "Write Time(ms) : " << write_elapsed * 1.0 / count << std::endl;
std::cout << "\tWrite Meta Time(ms) : " << write_meta_elapsed * 1.0 / count << std::endl;
std::cout << "\tWrite Index Time(ms) : " << write_index_elapsed * 1.0 / count << std::endl;
std::cout << "\tWrite KV Time(ms) : " << write_kv_elapsed * 1.0 / count << std::endl;
std::cout << "\tWrite Clean Time(ms) : " << write_clean_elapsed * 1.0 / count << std::endl;
std::cout << "TaskQueue Size : " << taskqueue_.size() << std::endl;
std::cout << "temp_elased : " << temp_elapsed * 1.0 / count << std::endl;
std::cout << "waiting elapsed : " << waiting_elasped * 1.0 / count << std::endl;
std::cout << "=====================================================\n";
write_bytes_lim = write_bytes + write_step;
std::fflush(stdout);
}
}
```
数据收集的方式如下所示(仅展示部分):
```c++
Status FieldDB::HandleRequest(Request &req, const WriteOptions &op) {
uint64_t start_ = env_->NowMicros();
MutexLock L(&mutex_);
taskqueue_.push_back(&req);
while(true){
uint64_t start_waiting = env_->NowMicros();
while(req.isPending() || !req.done && &req != taskqueue_.front()) {
req.cond_.Wait();
}
waiting_elasped += env_->NowMicros() - start_waiting;
if(req.done) {
elapsed += env_->NowMicros() - start_;
count ++;
dumpStatistics();
return req.s; //在返回时自动释放锁L
}
Request *tail = GetHandleInterval();
WriteBatch KVBatch,IndexBatch,MetaBatch;
SliceHashSet batchKeySet;
Status status;
/************************************************************************/
```
#### 3.2.2 性能分析与优化(需要加上相关的性能分析结果)
通过外部测量和内部测量,我们定位到了许多的值得优化的点,并进行迭代,下面将对于两个比较显著的点进行阐述:
1. 消除冗余的写入请求
通过插桩分析,我们发现对于一个普通的写入,理论上只有对于kvDB写入的流量,但是在实际写入的时候,对于kvDB、metaDB、indexDB都会产生时间消耗。通过审阅代码,我们发现原因在于产生了对于空WriteBatch的写入,因此在写入之前对于WriteBatch的大小进行判断,如果是一个空的WriteBatch,则直接跳过实际写入流程。
2. 使用Slice代替std::string
进一步分析了写入流程的各个部分后,我们认为实际写入数据库部分的写入消耗已经到了理论上限,在现有的结构上并没有优化的办法。同时,在写入流程中的读数据库也无法很好地优化。我们将目光聚焦于构建请求部分的实现上的优化。经过了代码分析,我们发现了在一开始实现的时候为了实现的便捷,我们大量的使用了stl库的`std::string`,但是有每次`string`的拷贝构造问题,会导致数据的多次在内存中的拷贝操作,在数据量较大的时候,我们认为对于性能会产生影响。
基于上述的考量,我们通过几轮的commit,将`request`内部的数据结构、相关辅助数据结构以及实现方式全部尽可能的使用`Slice`替换`std::string`。经过测试,我们发现性能确实有所提高。
#### 3.2.3 最终版本的性能分析(草稿)
1. 对于leveldb本身的一些分析(着重于多线程性能方面)
2. 对于FieldDB的分析
1) 所有涉及读取性能的:和原版leveldb相比,几乎没有任何的损耗,还是非常好的
2) 常规的写入性能:有所下降,但是由于是因为需要增加读操作,无法避免
3) 对于创删索引:总体态度是虽然没有比较对象,但是总体可以接受
4) 对于创删索引和写并发:如果是无关的,那么还是保持了高吞吐;如果是相关的,那么不得不受限于创删索引
## 4. 问题与解决
1. 包装一层性能减半?
2.
## 5. 潜在优化点
1. 使用一些高性能并发设计模式,如reactor来优化多线程写入时由于锁竞争导致的性能问题
2. 采用一些高性能的库,如dpdk等
3. 使用一些基于polling的请求处理手段等
4. 对于各个log进行合并,减少写放大
## 6. 分工

+ 34
- 34
benchmarks/db_bench_FieldDB.cc ファイルの表示

@ -50,41 +50,41 @@ using namespace fielddb;
// sstables -- Print sstable info
// heapprofile -- Dump a heap profile (if supported by this port)
static const char* FLAGS_benchmarks =
// "fillseq,"
// "fillsync,"
// "fillrandom,"
// "overwrite,"
// "readrandom,"
// "readrandom," // Extra run to allow previous compactions to quiesce
// "readseq,"
// "readreverse,"
// "compact,"
// "readrandom,"
// "readseq,"
// "readreverse,"
// "fill100K,"
// "crc32c,"
// "CreateIndex,"
// "FindKeysByField,"
// "QueryByIndex,"
// "DeleteIndex,"
// "compact,"
// "WriteSeqWhileCreating,"
// "WriteSeqWhileDeleting,"
// "compact,"
// "WriteRandomWhileCreating,"
// "WriteRandomWhileDeleting,"
// "compact,"
// "ReadSeqWhileCreating,"
// "ReadSeqWhileDeleting,"
// "ReadRandomWhileCreating,"
// "ReadRandomWhileDeleting,"
// "WriteRandomWithIndex,"
// "WriteSeqWithIndex,"
"fillseq,"
"WriteSeqWhileIndependentCCD,"
"fillseq,"
"WriteSeqWhileCCD,"
"fillsync,"
"fillrandom,"
"overwrite,"
"readrandom,"
"readrandom," // Extra run to allow previous compactions to quiesce
"readseq,"
"readreverse,"
"compact,"
"readrandom,"
"readseq,"
"readreverse,"
"fill100K,"
"crc32c,"
"CreateIndex," //创建索引
"FindKeysByField," //得到包含所有Field的KV对(不使用索引)
"QueryByIndex," //通过索引得到对应的主键
"DeleteIndex," //删除索引
"compact,"
"WriteSeqWhileCreating," //创建索引的同时顺序写
"WriteSeqWhileDeleting," //删除索引的同时顺序写
"compact,"
"WriteRandomWhileCreating," //创建索引的同时随机写
"WriteRandomWhileDeleting," //删除索引的同时随机写
"compact,"
"ReadSeqWhileCreating," //创建索引的同时顺序读
"ReadSeqWhileDeleting," //删除索引的同时顺序读
"ReadRandomWhileCreating," //创建索引的同时随机读
"ReadRandomWhileDeleting," //删除索引的同时随机读
"WriteRandomWithIndex," //随机写带索引的键值
"WriteSeqWithIndex," //顺序写带索引的键值
"fillseq,"
"WriteSeqWhileIndependentCCD," //在不断创建删除索引的情况下,顺序写与创删索引无关的数据
"fillseq,"
"WriteSeqWhileCCD," //在不断创建删除索引的情况下,顺序写与创删索引有关的数据
;
// Number of key/values to place in database

+ 19
- 19
fielddb/field_db.h ファイルの表示

@ -92,23 +92,23 @@ private:
Request *GetHandleInterval(); //
// private:
// int count = 0;
// int count_Batch = 0;
// int count_Batch_Sub = 0;
// uint64_t elapsed = 0;
// uint64_t construct_elapsed = 0;
// uint64_t construct_BatchReq_init_elapsed = 0;
// uint64_t construct_BatchReq_elapsed = 0;
// uint64_t construct_BatchReq_Sub_elapsed = 0;
// uint64_t construct_BatchReq_perSub_elapsed = 0;
// uint64_t construct_FieldsReq_Read_elapsed = 0;
// uint64_t write_elapsed = 0;
// uint64_t write_meta_elapsed = 0;
// uint64_t write_index_elapsed = 0;
// uint64_t write_kv_elapsed = 0;
// uint64_t write_clean_elapsed = 0;
// int count = 0;//
// int count_Batch = 0;//Batch数量
// int count_Batch_Sub = 0;//Batch_sub数量
// uint64_t elapsed = 0;//
// uint64_t construct_elapsed = 0;//
// uint64_t construct_BatchReq_init_elapsed = 0;//
// uint64_t construct_BatchReq_elapsed = 0;//batch的消耗
// uint64_t construct_BatchReq_Sub_elapsed = 0;//batch_sub消耗
// uint64_t construct_BatchReq_perSub_elapsed = 0;//Batch_sub消耗
// uint64_t construct_FieldsReq_Read_elapsed = 0;//
// uint64_t write_elapsed = 0;//
// uint64_t write_meta_elapsed = 0;//meta的耗时
// uint64_t write_index_elapsed = 0;//index的耗时
// uint64_t write_kv_elapsed = 0;//kv的耗时
// uint64_t write_clean_elapsed = 0;//meta的耗时
// uint64_t write_bytes = 0;
// uint64_t write_step = 500 * 1024 * 1024;
@ -116,7 +116,7 @@ private:
// uint64_t temp_elapsed = 0;
// uint64_t waiting_elasped = 0;
// uint64_t waiting_elasped = 0;//
// inline void dumpStatistics() {
// if(count && count % 500000 == 0 || write_bytes && write_bytes > write_bytes_lim) {
@ -145,7 +145,7 @@ private:
// std::fflush(stdout);
// }
// }
};
// };
Status DestroyDB(const std::string& name,
const Options& options);

読み込み中…
キャンセル
保存