卧推及其架构简介
#database #neo4j #memgraph #benchmark

构建表演图数据库带来了许多不同的挑战,从建筑设计到实施细节,涉及的技术,产品维护 - 列表还在不断。所有这些十字路口的决定都会影响数据库性能。

开发和维护产品是一个永无止境的阶段。并且必须进行适当的性能测试,以维持整个生命周期的数据库性能特征。

这是台式发挥作用的地方。为了确保Memgraph的性能的一致性,通过Memgraph的内部基准测试工具进行测试。在每个提交中,TheCI/CD基础架构都会运行性能测试,以检查每个代码更改如何影响性能。让我们看看台式架构。

台式架构

目前,台面是一个项目under Memgraph repository (previously Mgbench)。它由Python脚本和C ++客户端组成。 Python脚本用于通过准备工作负载,配置等来管理基准执行,而C ++客户端实际上执行基准。

benchgraph architecture diagram

一些更重要的python脚本是:

  • benchmark.py-用于启动和管理基准执行的主要入口点。该脚本初始化了所有必要的文件,类和对象。它启动数据库和基准测试,并收集结果。
  • runners.py-配置,启动和停止数据库的脚本。
  • benchmark_context.py-它收集在基准执行期间可以配置的所有数据。
  • base.py-这是基本工作负载类。所有其他工作负载都是位于工作负载目录中的子类。例如,ldbc_interactive.py定义了LDBC交互式数据集和查询(但这不是官方的LDBC Interactive Workload)。每个工作负载类都可以生成数据集,使用数据集的自定义导入或为导入过程提供Cypherl文件。
  • compare_results.py-用于比较不同基准运行的结果。

C++ bolt benchmark client具有自己的一套功能。它通过螺栓协议运行,并在目标数据库上执行Cypher查询。它支持验证,与时间相关的执行以及在多个线程上运行查询。

让我们深入研究基准。 py,跑步者 .py和C ++客户端进一步。

基准 .py

benchmak .py脚本中充满了重要的细节,对于运行基准至关重要,但这只是对其中的一些窥视。

所有参数均在基准 .py脚本中传递和解释。例如,以下片段用于设置将导入数据并执行基准的工人数量:


benchmark_parser.add_argument(
        "--num-workers-for-import",
        type=int,
        default=multiprocessing.cpu_count() // 2,
        help="number of workers used to import the dataset",
    )
    benchmark_parser.add_argument(
        "--num-workers-for-benchmark",
        type=int,
        default=1,
        help="number of workers used to execute the benchmark",
    )

在生成工作负载查询的过程中,您可以为每个特定查询设置种子。由于查询中的某些参数是随机生成的,因此种子确保执行随机生成的查询的相同序列。

def get_queries(gen, count):
    # Make the generator deterministic.
    random.seed(gen.__name__)
    # Generate queries.
    ret = []
    for i in range(count):
        ret.append(gen())
    return ret

以下方法定义了如何执行热身,混合和现实的工作量以及查询计数的近似方式。

def warmup(...):
    

def mixed_workload(...):
          

def get_query_cache_count(...):
    

跑步者 .py

runners.py脚本管理数据库,C ++客户端执行基准。跑步者脚本可以
管理本地备忘录和neo4J,以及Docker Memgraph和Neo4J。

供应商与以下类一起处理;当然,实施细节缺少:

class Memgraph(BaseRunner):
    
class Neo4j(BaseRunner):
    
class MemgraphDocker(BaseRunner):
    
 class Neo4jDocker(BaseRunner)
    

请记住,与本机版本相比,Docker版本具有明显的性能开销。

C ++螺栓客户端执行基准也可以以本机和Docker形式进行管理。


class BoltClient(BaseClient):
    
class BoltClientDocker(BaseClient):
    

C ++螺栓客户端

另一个重要的代码是C ++螺栓客户端。客户端用于执行工作负载。工作量指定为Cypher查询列表。客户端可以模拟与数据库的多个并发连接。以下代码片段初始化了一定数量的工人并将其连接到数据库:

for (int worker = 0; worker < FLAGS_num_workers; ++worker) {
    threads.push_back(std::thread([&, worker]() {
      memgraph::io::network::Endpoint endpoint(FLAGS_address, FLAGS_port);
      memgraph::communication::ClientContext context(FLAGS_use_ssl);
      memgraph::communication::bolt::Client client(context);
      client.Connect(endpoint, FLAGS_username, FLAGS_password);

通过使用多个并发客户端,基准测试了连接到数据库并执行查询的不同用户。这很重要,因为您可以模拟数据库如何处理更高的数据负载。

在执行过程中收集了每个工人的延迟值,此后,计算了一些基本的尾部延迟值。


 for (int i = 0; i < FLAGS_num_workers; i++) {
    for (auto &e : worker_query_latency[i]) {
      query_latency.push_back(e);
    }
  }
  auto iterations = query_latency.size();
  const int lower_bound = 10;
  if (iterations > lower_bound) {
    std::sort(query_latency.begin(), query_latency.end());
    statistics["iterations"] = iterations;
    statistics["min"] = query_latency.front();
    statistics["max"] = query_latency.back();
    statistics["mean"] = std::accumulate(query_latency.begin(), query_latency.end(), 0.0) / iterations;
    statistics["p99"] = query_latency[floor(iterations * 0.99)];
    statistics["p95"] = query_latency[floor(iterations * 0.95)];
    statistics["p90"] = query_latency[floor(iterations * 0.90)];
    statistics["p75"] = query_latency[floor(iterations * 0.75)];
    statistics["p50"] = query_latency[floor(iterations * 0.50)];

  } else {
    spdlog::info("To few iterations to calculate latency values!");
    statistics["iterations"] = iterations;
  }

有关上述所有组件并在memgraph s ci/cd中使用的所有详细信息,请随时参考the code base

台式基准过程

每个基准测试都有一组规则或步骤,以获得基准结果,例如基准的持续时间,查询品种,重新启动数据库等。台面有其自己的一套运行细节。基准过程也在台式methodology中解释,但用于运行benchgraph。重要的是要很好地了解每个步骤,因为每个步骤都会影响基准结果。
下图显示了使用基准图运行基准测试的关键步骤:

benchgraph process

第一步是使用预定义的配置选项启动数据库。使用不同的配置选项运行MEMGRAPH使我们可以看到各自的性能含义。

下一步是导入过程。根据上述工作负载类的配置,可以定制导入,也可以通过通过C ++螺栓客户端执行Cypher查询来导入数据。能够执行Cypher查询列表使我们能够快速指定不同类型的客户工作负载。导入后,将新导入的数据导出为快照,并重复执行以下所有工作负载。但是,如果执行第一个工作负载的写查询,则数据集将被更改,因此无法使用,因为它将影响基准的结果。

导入完成后,数据库将停止。导入数据集可以压力数据库并影响测量值,因此必须重新启动数据库。重新启动后,数据库将从快照中导入数据,并且可以开始性能测试。

在测试开始时,根据基准配置和工作负载类型生成查询。

台面支持三种类型的工作负载:

  • 隔离 - 单一查询的并发执行。
  • 混合 - 同时执行单一类型的查询,并混合了指定查询组的一定比例的查询。
  • 现实 - 同时执行查询,从写,阅读,更新和分析组。

每个工作负载可用于模拟不同的生产场景。

执行查询后,在执行过程中收集并通过我们的CI/CD基础架构收集并报告诸如基本延迟测量,每秒查询和峰值RAM使用情况。

最后,数据库被停止,根据工作量的不同,它是从快照中重新开始的,然后再开始一个查询或工作负载的执行。

仅在执行基准期间重新启动数据库的旁注:在平均用例中,数据库可以长时间运行。可以说,在某些边缘情况下,当您进行一些升级或有问题时,它们并不经常重新启动。话虽这么说,为什么数据库在基准测试期间重新启动?在未重电数据库上进行测试后执行测试可以导致测试受到先前运行的测试的影响。例如,如果您想测量查询X和Y的性能,则应在相同的条件下运行,这意味着具有新的数据集的数据库,而没有任何caches。

基准配置选项

这里只是some of the flags,可用于在性能运行期间配置卧推。

--num-workers-for-benchmark-此标志定义将用于查询数据库的工人数量。每个工人都是连接到Memgraph并执行查询的新线程。所有线程共享相同的查询池,但是每个查询仅执行一次。

--single-threaded-runtime-sec-此标志定义了将在基准测试中执行的查询池数量。手头的问题是,您希望作为数据库基准的示例执行每个特定查询中有多少个。每个查询都可能需要不同的时间来执行,因此固定一个数字,请说100个查询,可以在1秒内完成,而另一种类型的100个查询可以运行一个小时。为了避免这样的问题,此标志定义了单线程运行时的持续时间,这些持续时间将在几秒钟内用于近似您希望执行的查询数量。查询将根据单线程运行时的所需时间范围进行预先生成。

--warm-up-热身标志可以采用三个不同的参数,coldhotvulcanic。冷是默认值。没有进行热身执行,hot将在基准之前执行一些预定义的查询,而vulcanic将在进行测量之前先运行整个工作负载。生产环境中的数据库通常是预热和预先播放的,因为它们会长时间运行。但这并非总是如此。加热数据库需要花费时间,并且在数据库内的数据的波动率可能会破坏缓存。冷性能是每个数据库最糟糕的情况。

--workload-realistic--workload-mixed-这些标志用于指定工作负载类型。默认情况下,台面在孤立的工作负载上运行。

--time-depended-execution-定义查询将在几秒钟内执行多长时间的标志。同一查询将再次重新运行直至超时。这对于测试数据库的缓存功能很有用。

Memgraph为什么要构建内部基准测试工具?

构建基准基础架构耗时,并且有一些市场上的负载测试工具。最重要的是,构建基准基础架构容易出错。在Memgraph的调试版本上的未配置索引和运行基准的记忆很有趣ð

但是,由于需要自定义基准过程,配置选项和特定协议,Memgraph正在使用房屋基准测试工具台面。这不是普遍的建议:如果某些工具可以满足您的基准需求,我们不建议踏上这一旅程。但是从长远来看,内部基准测试工具提供了很大的灵活性,并且外部工具是对所有执行所有性能测试的验证和支持的很好的补充。

Read more about Memgraph internals on memgraph.com