diff --git a/README.md b/README.md index 054134c..3958762 100644 --- a/README.md +++ b/README.md @@ -588,165 +588,78 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { **性能测试:** **Benchmark测试运行结果及分析:** -![error](./Report/png/benchmark.png) +![error](./Report/png/new_benchmark.png) ### 2.结果分析 -1. **插入时间 (Insertion time for 100001 entries: 516356 microseconds)** +### 1. **总写入时间(Total time for 100001 writes)** -这个时间(516356 微秒,约 516 毫秒)看起来是合理的,特别是对于 100001 条记录的插入操作。如果数据插入过程没有特别复杂的计算或操作,这个时间应该是正常的,除非硬件性能或其他因素导致延迟。 +- **结果**: 1464.65 ms +- **分析**: 该值表示100001次写入操作所花费的总时间,接近1.46秒。这个时间段包括了所有写入操作和可能的其他处理,如日志记录和合并等。根据吞吐量(Throughput)可以看出每秒操作次数约为68277次,说明写入性能还是较为稳定。 -2. **没有索引的查询时间 (Time without index: 106719 microseconds)** +### 2. **吞吐量(Throughput)** -这个时间是查询在没有索引的情况下执行的时间。106719 微秒(大约 107 毫秒)对于没有索引的查询来说是可以接受的,尤其是在数据量较大时。如果数据库没有索引,查找所有相关条目会比较耗时。 +- **结果**: 68276.6 OPS (operations per second) +- **分析**: 吞吐量是每秒钟能够完成的写操作数量,68277次每秒的吞吐量表明系统在进行批量写入时性能良好,适合高负载下的写入操作。 -3. **创建索引的时间 (Time to create index: 596677 microseconds)** +### 3. **平均延迟(Average latency)** -这个时间(596677 微秒,约 597 毫秒)对于创建索引来说是正常的,尤其是在插入了大量数据后。如果数据量非常大,索引创建时间可能会显得稍长。通常情况下,创建索引的时间会随着数据量的增加而增大。 +- **结果**: 0.0133881 ms +- **分析**: 平均延迟为0.0134毫秒,表示每个写入操作的平均响应时间非常低。延迟较低说明系统能够快速响应每个请求,这对需要低延迟的应用非常重要。 -4. **有索引的查询时间 (Time with index: 68 microseconds)** +### 4. **P75 和 P99 延迟** -这个时间(68 微秒)非常短,几乎可以认为是一个非常好的优化结果。通常,索引查询比没有索引时要快得多,因为它避免了全表扫描。因此,这个时间是非常正常且预期的,说明索引大大加速了查询。 +- **结果**: P75 latency: 0.012 ms, P99 latency: 0.016 ms +- 分析: + - **P75延迟**为0.012毫秒,表示75%的请求在0.012毫秒内完成。 + - **P99延迟**为0.016毫秒,表示99%的请求在0.016毫秒内完成。 + - 这表明绝大多数请求的延迟都非常小,延迟稳定,且尾延迟(P99)也处于可接受范围。 -5. **查询结果 (Found 1 keys with index)** +### 5. **索引创建时间(Time to create index)** -这里显示索引查询找到了 1 个键。是正常的, `name=Customer#10000` 应该返回 1 条记录。 +- **结果**: 661884 microseconds (~661.88 ms) +- **分析**: 索引的创建时间接近660毫秒,可能由于索引数据结构的构建和填充过程。该时间相对较长,但不至于影响整体性能。 -6. **数据库统计信息 (Database stats)** +### 6. **索引查询时间(Time with index)** -``` -Compactions -Level Files Size(MB) Time(sec) Read(MB) Write(MB) --------------------------------------------------- - 0 2 6 0 0 7 - 1 5 8 0 16 7 -``` +- **结果**: 97 microseconds +- **分析**: 使用索引进行查询的时间非常低,仅为97微秒。这表明通过索引可以显著提高查询性能,特别是在查询大数据量时,查询响应时间大幅降低。 + +### 7. **写放大(Write Amplification)** + +- **结果**: Write Amplification Factor: 2.24915 +- **分析**: 写放大因子为2.25,表示系统写入的数据量是原始数据量的2.25倍。这个值较为理想,说明合并过程和磁盘上的写入是有效的,没有造成过多的额外写入。 + +### 8. **总数据写入量(Total data written)** + +- **结果**: 7058762 bytes (~7.06 MB) +- **分析**: 这是在进行100001次写入操作过程中,系统写入到磁盘的总字节数。与写放大因子2.25结合来看,系统的写入开销较为合理。 + +### 9. **索引删除时间(Time to delete index on field 'name')** + +- **结果**: 1235916 microseconds (~1235.92 ms) +- **分析**: 删除索引所需的时间较长,接近1.24秒。这可能是因为索引结构的更新和删除操作涉及较多的计算和磁盘操作,影响了响应速度。 + +### 10. **数据库状态(Compactions)** + +- 分析: + - 在Level 0上有1个文件,大小为3MB,写入了7MB的数据。 + - 在Level 1上有4个文件,大小为8MB,写入了15MB的数据。 + - 这些数据说明系统在进行数据合并时,Level 0和Level 1的文件大小和写入数据量都显示出一定的合并操作,表明系统在进行正常的磁盘整理。 + +------ + +### 总结 -这些信息表明数据库的压缩(Compaction)过程。`Level 0` 和 `Level 1` 显示了数据库的文件数和大小。此部分数据正常,意味着数据库在处理数据时有一些 I/O 操作和文件整理。 - -7. **删除索引的时间 (Time to delete index on field 'name': 605850 microseconds)** - -删除索引的时间(605850 微秒,约 606 毫秒)比创建索引的时间稍长。这个时间是合理的,删除索引通常会涉及到重新整理数据结构和清理索引文件,因此可能比创建索引稍慢。 - -> 根据这些结果,以下是吞吐、延迟、和写放大的分析: -> -> ------ -> -> ### **吞吐量** -> -> 吞吐量衡量的是系统在单位时间内能处理的操作量。根据插入时间和查询时间,可以推算吞吐量。 -> -> 1. **插入操作的吞吐量** -> -> - 插入时间为 516,356 微秒(约 516 毫秒)。 -> -> - 记录总数为 100,001。 -> -> $$ -> 吞吐量 = 总操作数总时间\frac{\text{总操作数}}{\text{总时间}}。 -> $$ -> -> -> -> $$ -> 吞吐量=100,0010.516≈193,798 条/秒吞吐量 = \frac{100,001}{0.516} \approx 193,798 \, \text{条/秒} -> $$ -> -> -> -> 2. **查询操作的吞吐量** -> -> - 有索引的查询时间为 68 微秒(非常快)。 -> -> $$ -> 查询操作吞吐量 = 10.000068≈14,705,882 次/秒\frac{1}{0.000068} \approx 14,705,882 \, \text{次/秒}。 -> $$ -> -> -> -> - 无索引的查询时间为 106,719 微秒。 -> -> -> $$ -> 无索引的查询吞吐量=10.106719≈9.37 次/秒无索引的查询吞吐量 = \frac{1}{0.106719} \approx 9.37 \, \text{次/秒} -> $$ -> -> -> ------ -> -> ### **延迟** -> -> 延迟是单个操作所需的时间,可以从实验结果中直接得出: -> -> 1. **插入延迟** -> -> -> $$ -> 平均插入延迟 = 516,356100,001≈5.16 微秒/条\frac{516,356}{100,001} \approx 5.16 \, \text{微秒/条}。 -> $$ -> -> -> 2. **查询延迟** -> -> - 有索引的查询延迟:68 微秒。 -> - 无索引的查询延迟:106,719 微秒。 -> -> ------ -> -> ### **写放大** -> -> 写放大是写入数据量与实际写入磁盘数据量的比值。根据数据库统计信息,可以估算写放大: -> -> 1. **统计数据** -> -> - 数据量:插入 100,001 条记录,假设每条记录大小为 64 字节,总大小约为 -> -> -> $$ -> 100,001×64=6.4 MB100,001 \times 64 = 6.4 \, \text{MB} -> $$ -> -> -> - 压缩日志显示: -> -> - 读取数据量:16 MB。 -> - 写入数据量:14 MB(Level 0 和 Level 1 总写入量)。 -> -> 2. **计算写放大** -> -> -> $$ -> 写放大 = 总写入量数据量=146.4≈2.19\frac{\text{总写入量}}{\text{数据量}} = \frac{14}{6.4} \approx 2.19。 -> $$ -> -> -> - 这个写放大值在 LSM 树中属于合理范围,尤其是数据量较大时。 -> -> ------ -> -> ### **总结** -> -> - **吞吐量**: -> - 插入操作约为 **193,798 条/秒**。 -> - 有索引的查询吞吐量为 **14,705,882 次/秒**,而无索引的查询吞吐量仅为 **9.37 次/秒**。 -> - **延迟**: -> - 插入操作的平均延迟为 **5.16 微秒/条**。 -> - 有索引的查询延迟远低于无索引的查询延迟(68 微秒 vs 106,719 微秒)。 -> - **写放大**: -> 写放大约为 **2.19**,表明索引的写入效率较高,但仍需注意在高频写入场景中的性能影响。 -> -> 如果进一步优化,建议从减少写放大(例如改进合并机制)和提升无索引查询性能入手,以平衡系统资源。 - - - -**benchmark运行结果总结:** - -整体来看,输出结果是正常的: - -- **插入和索引创建时间**:插入数据和创建索引所需的时间相对较长,但考虑到数据量和索引的生成,时间是合理的。 -- **有索引的查询时间**:索引加速了查询,这部分的时间(68 微秒)非常短,表现出色。 -- **删除索引的时间**:删除索引需要稍长时间,这也是常见的现象。 +- 性能优点: + - 吞吐量较高(68277 OPS),平均延迟和尾延迟较低,表明系统在高负载下能够稳定运行。 + - 索引查询时间非常低,能够有效提升查询性能。 + - 写放大因子在2.25左右,表现较为理想。 +- 需要关注的方面(代价): + - 索引创建和删除操作时间较长,尤其是删除索引的时间较为显著,可能需要优化索引管理策略。 + - 合并操作(Compaction)虽然正常,但可以根据实际需求进一步优化以减少对性能的影响。 +整体来看,该系统在性能上表现良好,适用于高吞吐量的写入操作,并能有效提升查询性能。
@@ -760,7 +673,7 @@ Level Files Size(MB) Time(sec) Read(MB) Write(MB) ## 六,问题与解决方案 -### 1. **问题:如何避免** `**indexDb_**` **的递归调用?** +### 1. **问题:如何避免** `indexDb_` **的递归调用?** 在实现 `Put` 和 `Delete` 方法时,由于 `indexDb_` 也调用了 `DBImpl` 的方法,可能导致递归调用的问题。具体表现为在 `indexDb_` 内部操作时仍会试图更新索引。 diff --git a/Report/png/new_benchmark.png b/Report/png/new_benchmark.png new file mode 100644 index 0000000..78541a7 Binary files /dev/null and b/Report/png/new_benchmark.png differ diff --git a/db/db_impl.cc b/db/db_impl.cc index 727a021..736fdae 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -1236,6 +1236,12 @@ void DBImpl::ReleaseSnapshot(const Snapshot* snapshot) { // Convenience methods Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { //ToDo + // 创建读写锁,放在函数内部 + static std::mutex rw_mutex_; // 全局静态锁,确保多个线程安全 + + // 加写锁,确保写操作的原子性 + std::unique_lock lock(rw_mutex_); + WriteBatch batch; WriteBatch indexBatch; Status s; @@ -1326,6 +1332,12 @@ Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { //ToDo + // 创建读写锁,放在函数内部 + static std::mutex rw_mutex_; // 全局静态锁,确保多个线程安全 + + // 加写锁,确保写操作的原子性 + std::unique_lock lock(rw_mutex_); + WriteBatch batch; WriteBatch indexBatch; Status s; @@ -1771,7 +1783,7 @@ Status DBImpl::DeleteIndex(const std::string& fieldName) { std::vector DBImpl::QueryByIndex(const std::string& fieldName) { std::vector results; - // 假设您有一个存储索引的数据库 indexDb_ + // 假设有一个存储索引的数据库 indexDb_ // leveldb::ReadOptions read_options; // std::string value; diff --git a/test/Benchmark_test.cc b/test/Benchmark_test.cc index 5a587ed..9bfd228 100644 --- a/test/Benchmark_test.cc +++ b/test/Benchmark_test.cc @@ -2,6 +2,11 @@ #include #include #include +#include +#include +#include +#include +#include #include using namespace std::chrono; @@ -57,6 +62,8 @@ std::vector FindKeysByField(leveldb::DB* db, const Field& field) { return keys; } +size_t total_size = 0; // 用于累加请求写入的数据量 + // 生成数据并插入数据库 void GenerateAndInsertData(leveldb::DB* db, int num_entries) { leveldb::WriteOptions write_options; @@ -70,9 +77,121 @@ void GenerateAndInsertData(leveldb::DB* db, int num_entries) { std::string value = "name:" + name + "|address:" + address + "|phone:" + phone; + // 计算每条记录的大小 + size_t key_size = key.size(); + size_t value_size = value.size(); + size_t record_size = key_size + value_size; + + total_size += record_size; // 累加到总请求写入的数据量 + status = db->Put(write_options, key, value); assert(status.ok() && "Failed to insert data"); } + std::cout << "Total data written (in bytes): " << total_size << " bytes" << std::endl; +} + +// 计算统计指标 +void CalculateLatencyStats(const std::vector& latencies) { + if (latencies.empty()) return; + + // 平均延迟 + double avg_latency = std::accumulate(latencies.begin(), latencies.end(), 0.0) / latencies.size(); + + // P75 延迟 + std::vector sorted_latencies = latencies; + std::sort(sorted_latencies.begin(), sorted_latencies.end()); + double p75_latency = sorted_latencies[latencies.size() * 75 / 100]; + + // P99 延迟 + double p99_latency = sorted_latencies[latencies.size() * 99 / 100]; + + // 输出结果 + std::cout << "Average latency: " << avg_latency << " ms" << std::endl; + std::cout << "P75 latency: " << p75_latency << " ms" << std::endl; + std::cout << "P99 latency: " << p99_latency << " ms" << std::endl; +} + +// 基准测试:计算吞吐量和延迟 +void BenchmarkWritePerformance(leveldb::DB* db, int num_entries) { + leveldb::WriteOptions write_options; + leveldb::Status status; + + std::vector latencies; // 存储每次操作的延迟 + + auto start = high_resolution_clock::now(); + + for (int i = 1; i <= num_entries; ++i) { + std::string key = "k_" + std::to_string(i); + std::string value = "name:Customer#" + std::to_string(i) + "|address:Address_" + std::to_string(i) + "|phone:25-989-741-" + std::to_string(1000 + i); + + // 计算每条记录的大小 + size_t key_size = key.size(); + size_t value_size = value.size(); + size_t record_size = key_size + value_size; + + total_size += record_size; // 累加到总请求写入的数据量 + + auto op_start = high_resolution_clock::now(); + status = db->Put(write_options, key, value); + auto op_end = high_resolution_clock::now(); + + assert(status.ok() && "Failed to insert data"); + + // 记录每次操作的延迟(ms) + double latency = duration_cast(op_end - op_start).count() / 1000.0; + latencies.push_back(latency); + } + + auto end = high_resolution_clock::now(); + auto duration = duration_cast(end - start).count(); + + // 计算吞吐量 + double throughput = num_entries / (duration / 1000000.0); + + std::cout << "Total time for " << num_entries << " writes: " << duration / 1000.0 << " ms" << std::endl; + std::cout << "Throughput: " << throughput << " OPS (operations per second)" << std::endl; + + // 计算延迟统计 + CalculateLatencyStats(latencies); +} + +// 获取写放大(Write Amplification) +void CalculateWriteAmplification(leveldb::DB* db) { + std::string property; + + bool success = db->GetProperty("leveldb.stats", &property); + if (!success) { + std::cerr << "Failed to get db stats" << std::endl; + return; + } + + + + + // 获取日志文件中的合并信息 + std::ifstream log_file("/home/kevin/leveldb_proj/build/testdb/LOG"); // 替换为实际日志路径 + std::string log_line; + size_t total_compacted = 0; + + std::regex compact_regex(R"(.*Compacted.*=>\s*([\d]+)\s*bytes)"); + + while (std::getline(log_file, log_line)) { + std::smatch match; + if (std::regex_search(log_line, match, compact_regex)) { + total_compacted += std::stoull(match[1]); + } + } + + log_file.close(); + + double write_amplification = static_cast(total_compacted) ; + std::cout << "Write Amplification: " << write_amplification << std::endl; + + std::cout << "Total data written (in bytes): " << total_size << " bytes" << std::endl; + + double write_amplification_factor = static_cast(total_compacted) / total_size; + std::cout << "Write Amplification Factor: " << write_amplification_factor << std::endl; + } // 基准测试:二级索引性能提升 @@ -91,7 +210,7 @@ void BenchmarkFieldQueryWithIndex(leveldb::DB* db) { leveldb::Status status = db->CreateIndexOnField("name"); end = high_resolution_clock::now(); duration = duration_cast(end - start); - std::cout << "Time to create index: " << duration.count() << std::endl; + std::cout << "Time to create index: " << duration.count() << " microseconds" << std::endl; // 测试后,查询有索引的字段性能 start = high_resolution_clock::now(); @@ -110,17 +229,17 @@ void BenchmarkFieldQueryWithIndex(leveldb::DB* db) { } } -// 基准测试:记录插入时的性能影响 -void BenchmarkWritePerformance(leveldb::DB* db, int num_entries) { - leveldb::WriteOptions write_options; - auto start = high_resolution_clock::now(); +// // 基准测试:记录插入时的性能影响 +// void BenchmarkWritePerformance(leveldb::DB* db, int num_entries) { +// leveldb::WriteOptions write_options; +// auto start = high_resolution_clock::now(); - GenerateAndInsertData(db, num_entries); // 执行批量插入 +// GenerateAndInsertData(db, num_entries); // 执行批量插入 - auto end = high_resolution_clock::now(); - auto duration = duration_cast(end - start); - std::cout << "Insertion time for " << num_entries << " entries: " << duration.count() << " microseconds" << std::endl; -} +// auto end = high_resolution_clock::now(); +// auto duration = duration_cast(end - start); +// std::cout << "Insertion time for " << num_entries << " entries: " << duration.count() << " microseconds" << std::endl; +// } // 基准测试:记录删除二级索引的开销 void BenchmarkDeleteIndex(leveldb::DB* db, const std::string& field_name) { @@ -168,6 +287,9 @@ int main() { // 获取数据库大小 GetDatabaseSize(db); + // 计算写放大 + CalculateWriteAmplification(db); + // 测试删除二级索引的开销 BenchmarkDeleteIndex(db, "name");