diff --git a/README.md b/README.md index 7093ff7..7a09aaf 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,11 @@ - [6. **数据插入与删除的原子性实现**](#6-数据插入与删除的原子性实现) - [7.**持久化与恢复机制**](#7-持久化与恢复机制) - [五,性能测试](#五性能测试) - - [1. **测试流程**](#1测试流程) - - [2. **结果分析**](#2结果分析) + - [1.**单元测试**](#1单元测试) + - [2.**单元性能测试**](#2单元性能测试) + - [3.**不同数据量的情况下索引数据更新/删除对原始数据写入性能影响**](#3不同数据量的情况下索引数据更新/删除对原始数据写入性能影响) + - [4.**并发测试**](#4并发测试) + - [5.**不同数据量下二级索引查询和直接遍历的性能比较**](#5不同数据量下二级索引查询和直接遍历的性能比较) - [六,问题与解决方案](#六问题与解决方案) - [七,总结](#七总结) @@ -562,9 +565,7 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { ## 五,性能测试 -### 1.测试流程 - -**单元测试:** +### **1.单元测试:** 1. 插入原始数据: ``` @@ -586,12 +587,12 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { ![error](Report/png/test_result.png) -**性能测试:** +### **2.单元性能测试:** -**Benchmark测试运行结果及分析:** +**1.Benchmark测试运行结果及分析:** ![error](./Report/png/new_benchmark.png) -### 2.结果分析 +### 结果分析 #### 1. **总写入时间(Total time for 100001 writes)** @@ -648,8 +649,6 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { - 在Level 1上有4个文件,大小为8MB,写入了15MB的数据。 - 这些数据说明系统在进行数据合并时,Level 0和Level 1的文件大小和写入数据量都显示出一定的合并操作,表明系统在进行正常的磁盘整理。 ------- - #### 总结 - 性能优点: @@ -664,13 +663,211 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) {
+--- + +### **3.不同数据量的情况下索引数据更新/删除对原始数据写入性能影响:** + +| ![](.\Report\png\data_a.png) | ![](.\Report\png\data_b.png) | ![](.\Report\png\data_c.png) | +|------------------------------|------------------------------|------------------------------| + +| ![](.\Report\png\average_latency.png) | ![](.\Report\png\index_time.png) | +|---------------------------------------|----------------------------------| +| ![](.\Report\png\latency_percentiles.png) | ![](.\Report\png\write_throughput.png) | + + + +### 数据分析 + +#### **1. 索引创建和删除性能** + +- **索引创建时间**: + - 10,000 条:599.162 ms + - 50,000 条:649.744 ms + - 100,000 条:587.904 ms + - **观察**:创建索引的时间在不同数据量下变化不大,均在 600 ms 左右。表现较为稳定,说明索引创建的复杂度对数据规模不敏感。 +- **索引删除时间**: + - 10,000 条:1204.5 ms + - 50,000 条:1212.03 ms + - 100,000 条:1393.74 ms + - **观察**:索引删除耗时较创建时间长,且随着数据量的增加略有增长,可能与索引结构或删除机制(如清理存储资源或更新元数据)相关。 + +------ + +#### **2. 写入性能分析** + +##### **有索引写入性能** + +- **吞吐量(OPS)**: + - 10,000 条:57,646.9 OPS + - 50,000 条:56,038 OPS + - 100,000 条:69,547.3 OPS + - **观察**:随着数据量增加,吞吐量表现整体稳定且略有提升,表明写入时索引的更新策略较为高效。 +- **延迟**: + - **平均延迟**:从 0.0162 ms(10,000 条)下降到 0.0132 ms(100,000 条),显示写入性能随数据量提升有所优化。 + - **P75 和 P99 延迟**:分别维持在 0.014–0.013 ms 和 0.028–0.017 ms,变化范围小,表明系统在高负载下的稳定性良好。 + +##### **无索引写入性能** + +- **吞吐量(OPS)**: + - 10,000 条:113,652 OPS + - 50,000 条:78,503.9 OPS + - 100,000 条:75,930.8 OPS + - **观察**:无索引情况下吞吐量显著高于有索引,表明索引操作对写入有一定开销;但吞吐量随数据量增加显著下降,可能受数据库写入性能瓶颈影响。 +- **延迟**: + - **平均延迟**:从 0.0077 ms(10,000 条)增长到 0.0119 ms(100,000 条)。 + - **P75 和 P99 延迟**:分别维持在 0.008–0.011 ms 和 0.01–0.016 ms,变化范围较小,说明无索引写入性能对负载变化敏感性较低。 + +------ + +#### **3. 有索引与无索引的性能对比** + +| **指标** | **有索引** | **无索引** | **对比** | +| ------------------ | ----------------- | ---------------- | ------------------------------------------------------------ | +| **吞吐量(OPS)** | 57,646.9–69,547.3 | 75,930.8–113,652 | 无索引吞吐量更高,差距随着数据量增加缩小;有索引时写入性能受影响较大。 | +| **平均延迟(ms)** | 0.0132–0.0162 | 0.0077–0.0119 | 无索引平均延迟低,延迟开销主要来自索引更新操作。 | +| **P75 延迟(ms)** | 0.013–0.014 | 0.008–0.011 | 无索引 P75 延迟更优,表明其写入响应时间更快。 | +| **P99 延迟(ms)** | 0.017–0.028 | 0.01–0.016 | 无索引高百分位延迟更优,写入尾部响应性能优于有索引场景。 | + +------ + +#### **4. 趋势分析** + +1. **吞吐量与延迟**: + - **有索引吞吐量**稳定且延迟逐渐优化,说明索引更新的效率随数据量有所提升。 + - **无索引吞吐量**随数据量增加下降,可能与数据库写入的固有开销和资源竞争有关。 +2. **索引的开销**: + - 吞吐量与延迟对比表明索引带来的开销主要体现在写入延迟增加(大约 2 倍),但在高数据量时开销相对降低。 +3. **索引创建与删除**: + - 索引创建时间稳定,但删除耗时更长,可能影响系统的动态索引管理效率。 + +------ + +### 未来可能的优化方向: + +1. **优化索引更新逻辑**: + - 使用批量索引更新或异步索引机制,以减少写入延迟。 +2. **优化索引删除性能**: + - 分析索引删除过程中清理步骤的瓶颈(如内存释放或元数据清理),尝试使用增量删除策略。 +3. **动态调整索引策略**: + - 对高频写入但低频查询场景,提供选项禁用索引,或采用更轻量化的索引结构(如稀疏索引)。 +4. **提升无索引吞吐量稳定性**: + - 通过优化数据库写入路径(如 I/O 并发处理)减少吞吐量随数据量增长的下降。 --- +### **4.并发测试** + +利用多个线程并发写入10^6的数据: + +![](.\Report\png\tread_8.png) + +![](.\Report\png\concurrent_benchmark_results.png) + +### **单线程性能分析** +在没有并发的情况下,分别测量了多个写入操作的时间和性能: +1. **吞吐量(Throughput)**:随着每次测试写入的时间增加,吞吐量逐渐下降。例如,首次测试时吞吐量为 `17692.5 OPS`,而最后一次测试为 `10000 OPS`,这说明随着写入数量的增加,数据库的写入性能逐渐受到影响,可能是因为写入操作的负载增大,导致数据库处理每个操作的效率降低。 +2. **平均延迟(Average Latency)**:随着操作次数的增加,平均延迟呈现上升趋势。例如,第一次测试的延迟为 `0.0554 ms`,而最后一次测试的延迟为 `0.0988 ms`。这表明随着数据量的增加,数据库写入的延迟也在逐步增加。 +3. **P75 和 P99 延迟**:P75 和 P99 延迟也在一定范围内保持相对平稳,显示出大部分操作的延迟维持在一个较低的水平。但是随着写入数量的增加,这些延迟也略有上升。对于P99延迟,随着测试的进行,最慢的操作(99%的延迟)也表现出小幅增加。 +### **并发性能分析** +并发测试的结果如下: + +- **并发总吞吐量**:`79800.3 OPS`,这个吞吐量是多线程并发的总和,比单线程测试时的吞吐量要高很多。这是因为并发写入操作有效利用了多核处理器的能力,能够提高整体的吞吐量。 +- **并发性能**:尽管吞吐量有明显的提升,但每个线程的性能会受到一定的影响。每个线程的写入时间较单线程情况有所增加,因为并发操作会带来额外的上下文切换和锁竞争等开销。这会导致平均延迟上升并使吞吐量下降。 + +### **结论** + +1. **随着写入量增加,性能有所下降**:每次测试的吞吐量和延迟都在逐渐变化,吞吐量下降,延迟上升。可能的原因是数据库内部资源的争用、磁盘I/O负载、或者存储系统的瓶颈。 +2. **并发测试表现良好**:尽管每个线程的吞吐量有所下降,但并发测试整体吞吐量显著高于单线程,这表明并发操作提高了整体吞吐量。可以考虑增加更多的线程以进一步提高吞吐量,前提是系统资源能够支持。 +3. **性能瓶颈**:可以进一步分析数据库的性能瓶颈,可能是由于磁盘I/O、内存或CPU资源限制导致性能下降。考虑使用更强的硬件或优化数据库配置(如压缩设置、缓存优化等)可能有助于提升性能。 + + + +--- + +### **5.不同数据量下二级索引查询和直接遍历的性能比较** + +| ![](.\Report\png\data_a1.png) | ![](.\Report\png\data_b1.png) | ![](.\Report\png\data_c1.png) | +|------------------------------|------------------------------|------------------------------| + +![](D:\programing\database_sys_building\secondary_index\levelDB_secondary-index\Report\png\query_performance_comparison.png) + +从数据中可以观察到二级索引查询和直接遍历在不同数据量下的性能表现差异: + +### 1. **索引创建时间** + +- 索引创建时间随着数据量增加而略微增长: + - 10,000条数据:586.378毫秒 + - 50,000条数据:560.607毫秒 + - 100,000条数据:629.471毫秒 +- 索引创建的开销随着数据量的增长保持相对稳定,增幅较小。这表明索引创建的时间复杂度较低。 + +### 2. **查询性能** + +- 无索引查询时间(直接遍历): + + - 随数据量显著增加: + - 10,000条数据:89.138毫秒 + - 50,000条数据:97.579毫秒 + - 100,000条数据:122.947毫秒 + - 这种线性增长是因为直接遍历的时间复杂度为 O(n)O(n),每增加一条记录都需要额外的遍历开销。 + +- 有索引查询时间: + + - 始终保持在极低的水平: + - 10,000条数据:68微秒 + - 50,000条数据:69微秒 + - 100,000条数据:60微秒 + - 使用二级索引查询的时间复杂度为 O(1)O(1) 或近似 O(log⁡n)O(\log n),受数据量变化影响极小。 + +### 3. **写入性能** + +- 无索引写入: + + - 写入性能稍好于有索引写入: + - 10,000条数据:89.656毫秒,总吞吐量111,537 OPS + - 50,000条数据:666.428毫秒,总吞吐量75,026.9 OPS + - 100,000条数据:1339.33毫秒,总吞吐量74,664 OPS + - 这是因为直接写入没有额外的索引更新开销。 + +- 有索引写入: + + - 写入性能略低,因需要维护索引: + - 10,000条数据:145.412毫秒,总吞吐量68,770.1 OPS + - 50,000条数据:732.739毫秒,总吞吐量68,237.1 OPS + - 100,000条数据:1,417.67毫秒,总吞吐量70,538.3 OPS + - 写入时的索引维护增加了时间复杂度,导致写入性能稍有下降。 + +### 4. **索引删除时间** + +- 索引删除的时间也随着数据量增长而增加: + - 10,000条数据:1200.24毫秒 + - 50,000条数据:1305.27毫秒 + - 100,000条数据:1399.8毫秒 +- 这与索引删除需要更新索引结构相关。 + +------ + +### **综合分析** + +- **二级索引的优势**: + - 查询性能提升显著,特别是在大数据量场景下,直接遍历的查询时间呈线性增长,而有索引的查询时间基本保持不变。 + - 当查询操作频繁时,索引的使用能显著提高系统的整体效率。 +- **二级索引的劣势**: + - 写入性能略微下降,因为需要额外的索引维护。 + - 索引创建和删除引入了一定的开销,适用于需要长期使用的场景。 +- **适用场景**: + - 数据量较大且查询频繁的场景,二级索引能提供极大的性能提升。 + - 对实时性要求较低的批量写入任务中,可以考虑先完成写入,再批量创建索引,避免实时更新索引的开销。 + +总结:二级索引在查询密集型任务中非常高效,但需要权衡写入性能和索引维护的成本。 + + + +--- ## 六,问题与解决方案 diff --git a/Report/png/average_latency.png b/Report/png/average_latency.png new file mode 100644 index 0000000..87262d0 Binary files /dev/null and b/Report/png/average_latency.png differ diff --git a/Report/png/concurrent_benchmark_results.png b/Report/png/concurrent_benchmark_results.png new file mode 100644 index 0000000..e05054c Binary files /dev/null and b/Report/png/concurrent_benchmark_results.png differ diff --git a/Report/png/data_a.png b/Report/png/data_a.png new file mode 100644 index 0000000..966e656 Binary files /dev/null and b/Report/png/data_a.png differ diff --git a/Report/png/data_a1.png b/Report/png/data_a1.png new file mode 100644 index 0000000..02ce626 Binary files /dev/null and b/Report/png/data_a1.png differ diff --git a/Report/png/data_b.png b/Report/png/data_b.png new file mode 100644 index 0000000..98272b1 Binary files /dev/null and b/Report/png/data_b.png differ diff --git a/Report/png/data_b1.png b/Report/png/data_b1.png new file mode 100644 index 0000000..e8dbbfd Binary files /dev/null and b/Report/png/data_b1.png differ diff --git a/Report/png/data_c.png b/Report/png/data_c.png new file mode 100644 index 0000000..ff59415 Binary files /dev/null and b/Report/png/data_c.png differ diff --git a/Report/png/data_c1.png b/Report/png/data_c1.png new file mode 100644 index 0000000..e6d22f4 Binary files /dev/null and b/Report/png/data_c1.png differ diff --git a/Report/png/index_time.png b/Report/png/index_time.png new file mode 100644 index 0000000..6941108 Binary files /dev/null and b/Report/png/index_time.png differ diff --git a/Report/png/latency_percentiles.png b/Report/png/latency_percentiles.png new file mode 100644 index 0000000..3a06ee8 Binary files /dev/null and b/Report/png/latency_percentiles.png differ diff --git a/Report/png/query_performance_comparison.png b/Report/png/query_performance_comparison.png new file mode 100644 index 0000000..72ad3f5 Binary files /dev/null and b/Report/png/query_performance_comparison.png differ diff --git a/Report/png/tread_8.png b/Report/png/tread_8.png new file mode 100644 index 0000000..182e622 Binary files /dev/null and b/Report/png/tread_8.png differ diff --git a/Report/png/write_throughput.png b/Report/png/write_throughput.png new file mode 100644 index 0000000..3e0985b Binary files /dev/null and b/Report/png/write_throughput.png differ diff --git a/test/Benchmark_test.cc b/test/Benchmark_test.cc index 9bfd228..30655d7 100644 --- a/test/Benchmark_test.cc +++ b/test/Benchmark_test.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include using namespace std::chrono; @@ -241,6 +242,65 @@ void BenchmarkFieldQueryWithIndex(leveldb::DB* db) { // std::cout << "Insertion time for " << num_entries << " entries: " << duration.count() << " microseconds" << std::endl; // } +// 测试不同数据量下索引更新/删除对写入性能的影响 +void BenchmarkIndexImpactOnWrite(leveldb::DB* db, const std::vector& entry_sizes) { + for (int num_entries : entry_sizes) { + std::cout << "\nTesting with " << num_entries << " entries..." << std::endl; + + // 创建索引 + auto start = high_resolution_clock::now(); + leveldb::Status status = db->CreateIndexOnField("name"); + assert(status.ok() && "Failed to create index"); + auto end = high_resolution_clock::now(); + auto duration = duration_cast(end - start).count(); + std::cout << "Time to create index: " << duration / 1000.0 << " ms" << std::endl; + + // 测试插入性能(索引启用) + std::cout << "Benchmarking write performance with index..." << std::endl; + BenchmarkWritePerformance(db, num_entries); + + BenchmarkFieldQueryWithIndex(db); + + // 删除索引 + start = high_resolution_clock::now(); + status = db->DeleteIndex("name"); + assert(status.ok() && "Failed to delete index"); + end = high_resolution_clock::now(); + duration = duration_cast(end - start).count(); + std::cout << "Time to delete index: " << duration / 1000.0 << " ms" << std::endl; + + // 测试插入性能(索引禁用) + std::cout << "Benchmarking write performance without index..." << std::endl; + BenchmarkWritePerformance(db, num_entries); + } +} + +// 并发执行写入性能基准测试 +void ConcurrentBenchmarkWritePerformance(leveldb::DB* db, int num_entries, int num_threads) { + auto write_task = [db, num_entries]() { + BenchmarkWritePerformance(db, num_entries); + }; + + auto start_time = std::chrono::high_resolution_clock::now(); + + std::vector threads; + for (int i = 0; i < num_threads; ++i) { + threads.push_back(std::thread(write_task)); + } + + for (auto& t : threads) { + t.join(); + } + + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end_time - start_time; + + double total_throughput = num_threads * num_entries / elapsed.count(); + std::cout << "Total time for concurrent writes: " << elapsed.count() * 1000 << " ms\n"; + std::cout << "Total throughput: " << total_throughput << " OPS\n"; +} + + // 基准测试:记录删除二级索引的开销 void BenchmarkDeleteIndex(leveldb::DB* db, const std::string& field_name) { auto start = high_resolution_clock::now(); @@ -293,6 +353,17 @@ int main() { // 测试删除二级索引的开销 BenchmarkDeleteIndex(db, "name"); + // 定义不同的数据量 + std::vector entry_sizes = {10000, 50000, 100000}; + + // 测试索引对写入性能的影响 + BenchmarkIndexImpactOnWrite(db, entry_sizes); + + // 测试并发写入 + int num_entries = 100000; // 每个线程的写入条目数 + int num_threads = 8; // 并发线程数 + ConcurrentBenchmarkWritePerformance(db, num_entries, num_threads); + // 关闭数据库 delete db;