<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ante Javor</title>
    <description>The latest articles on Forem by Ante Javor (@antejavor).</description>
    <link>https://forem.com/antejavor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F744109%2Fad6f894b-15f0-4611-b3f8-83dfbb0fd944.png</url>
      <title>Forem: Ante Javor</title>
      <link>https://forem.com/antejavor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/antejavor"/>
    <language>en</language>
    <item>
      <title>Benchgraph Backstory: The Untapped Potential</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Thu, 27 Apr 2023 13:28:26 +0000</pubDate>
      <link>https://forem.com/memgraph/benchgraph-backstory-the-untapped-potential-2h1n</link>
      <guid>https://forem.com/memgraph/benchgraph-backstory-the-untapped-potential-2h1n</guid>
      <description>&lt;p&gt;A few months ago, we launched &lt;a href="https://memgraph.com/benchgraph"&gt;Benchgraph&lt;/a&gt;, the platform for comparing the performance of graph databases. We tweaked our &lt;a href="https://memgraph.com/blog/introduction-to-benchgraph-and-its-architecture"&gt;in-house benchmarking tool&lt;/a&gt; a bit and started benchmarking Neo4j since Memgraph is highly compatible with Neo4j. There were definitely things we did right and some others that we did wrong, but after a few months, we decided to update a few things based on community feedback making the overall benchmark much efficient and valuable to the community.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the things that have been improved in the benchmark and what has changed. &lt;/p&gt;

&lt;h2&gt;
  
  
  New bigger datasets
&lt;/h2&gt;

&lt;p&gt;During the run of a typical benchmark, you can run specific queries for a fixed period of time or take a chunk of queries, execute them, and measure elapsed time. Either way, you need a decent amount of queries (same query with different arguments), and for that, you need a bigger dataset. On top of that, testing performance on the more extensive scaled datasets is necessary. To put things into perspective for query requirements, on some of the queries that are being run,  the database executes up to 300k queries or less, depending on the query complexity. For a detailed look into statistics, take a look at the &lt;a href="https://github.com/memgraph/benchgraph/tree/main/results"&gt;benchmark.json&lt;/a&gt; file that holds all the results from the benchmarks. There you can see how many specific queries were executed per particular test. &lt;/p&gt;

&lt;p&gt;To get back to the point of the bigger dataset, Pokec was an initial dataset that was used for running benchmarks. It is still there, but it is also a bit limited in size; the tests were executed on 100k nodes and 1.7M edges (medium size). So it is not overly large and has quite a simple structure.&lt;/p&gt;

&lt;p&gt;Because of the size, complexity, and feedback from the community, we decided to add a larger dataset. So the next dataset should be large, more complex, and recognizable. The choice was easy here; the industry-leading benchmark group Linked Data Benchmark Council (LDBC), which Memgraph is a part of, has open-sourced the datasets for benchmarking. The exact dataset is the social network dataset. It is a synthetically &lt;a href="https://github.com/ldbc/ldbc_snb_datagen_spark"&gt;generated dataset&lt;/a&gt; representing a social network. It is being used in LDBC audited benchmarks, SNB interactive, and SNB Buissines intelligence benchmarks. Keep in mind that this is NOT an official implementation of an LDBC benchmark, the open-source dataset is being used as a basis for benchmarks, and it will be used for our in-house testing process and improving Memgraph.&lt;/p&gt;

&lt;p&gt;The good thing about the LDBC SNB dataset is that it has a specific set of prefix sizes. For example, the SF1 tier of the LDBC SNB dataset has approximately 3M nodes and 17M edges. SF3 is 3 times the size of SF1, with approximately 9M nodes, and 52M edges. The scale sizes go up to SF1000, which is quite a big graph.&lt;br&gt;
The test was run on SF1, which is fairly small for the datasets available in the wild, but tests will be executed  on the bigger sizes soon (such as SF3 and SF10).&lt;/p&gt;

&lt;p&gt;Both LDBC SNB interactive and LDBC SNB business intelligence operate on the schematically same graph, the social network, but it is being generated a bit differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  More complex queries
&lt;/h2&gt;

&lt;p&gt;Queries used for benchmarking on the Pokec dataset in the first iteration of the Benchgraph were quite general. They did cover most of the basic operations such as aggregations, writes, reads, updates, and graph analytical queries. But overall, the queries were not too complex and provided insight into how good transactional workloads can be in Memgraph and Neo4j. Despite being generic, those queries were picked by us, so again, based on the feedback from the community, more complex queries were added to the Benchgraph. &lt;/p&gt;

&lt;p&gt;At first, the plan was to use only the LDBC dataset and write different queries for the dataset, but LDBC has a set of well-designed queries that were specifically prepared to stress the database. Each query targets a special scenario, also called “chock point.”  Not to be mistaken, they do not have deep graph traversal doing around 100 hops, but they are definitely more complex than the ones written for the Pokec dataset. There are two sets of queries for the LDBC SNB: &lt;a href="https://github.com/ldbc/ldbc_snb_interactive_impls"&gt;interactive&lt;/a&gt; and &lt;a href="https://github.com/ldbc/ldbc_snb_bi"&gt;business intelligence&lt;/a&gt;. LDBC provides a reference Cypher implementation for both of these queries for Neo4j. We took those queries, tweaked the data types, and made the queries work on Memgraph. Again, to be perfectly clear, this is NOT an official implementation of an LDBC Benchmark; this goes for both interactive and business intelligence queries. The queries were used as the basis for running the benchmark. &lt;/p&gt;

&lt;h2&gt;
  
  
  Update to the benchmark procedure
&lt;/h2&gt;

&lt;p&gt;The dataset and queries were not the only things that received an update. The process of running benchmarks has been updated, and the full technical details can be found in the &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench#fire-mgbench-benchmark-for-graph-databases"&gt;methodology&lt;/a&gt;. However apart from that, here is a short overview of what has changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests on multiple concurrent clients
&lt;/h3&gt;

&lt;p&gt;Running a database in production environments does not mean you will execute queries in isolation with a single connection to the database. The database can be used by multiple users and applications, which means it will have multiple connections concurrently. During that connection period, they would query the database system, and the database would need to respond to all the queries hitting them as fast as possible. Obviously, there will be noticeable differences in running this test on multiple worker threads. To test this, the tests were executed by using different numbers of worker threads that simulate that scenario; going to the Benchgraph, you will see the option &lt;strong&gt;Number of workers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As you can see, the test is currently executed on 12, 24, and 48 worker threads. In the last iteration, the tests were run by using only 12 threads. But this will change in the future, and the number of workers will probably increase even further to show different levels of performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  New volcanic warm-up
&lt;/h3&gt;

&lt;p&gt;One of the things we did notice in the first run of the Benchgraph is that Neo4j benefits a lot when identical queries are being executed multiple times. We were not aware of the extent to which Neo4j can benefit from it. Although being an excellent database feature, it comes with a few downsides, but more on that later. There were previously &lt;code&gt;cold&lt;/code&gt; and  &lt;code&gt;hot&lt;/code&gt; database conditions, as described in the &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md#database-conditions"&gt;methodology&lt;/a&gt;, but now, vulcanic is introduced.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Vulcanic&lt;/code&gt; database warm-up, the bulk of workload queries are executed first as a warm-up, and after that, all the queries are re-running it again. The second run is where the results are collected. This adds a bit of clarity on potential Neo4j performance. Keep in mind that volcanic warm-up tests database caching performance. Vulcanic warm-up is not applicable to the highly volatile dataset, which has a tendency to change drastically on a daily basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware
&lt;/h3&gt;

&lt;p&gt;As in the previous iteration, the tests were executed on identical hardware for both Memgraph and Neo4j. Due to the fact that our Intel HP server is a bit dusty and has a few years on its transistors the tests were executed on a newer AMD platform. There is a plan to expand this a bit further, potentially to AWS instances, since these things are standardized and people use them these days to deploy most of the applications. &lt;/p&gt;

&lt;h3&gt;
  
  
  Running configuration changes
&lt;/h3&gt;

&lt;p&gt;There have been a few changes in the running setup. The first and obvious one is that database versions are bumped up: the database version, community editions Neo4j 5.6, and Memgraph 2.7.&lt;/p&gt;

&lt;p&gt;Due to Neo4j's different architecture (on disk, while Memgraph being in-memory), Neo4j needs a lot more time to warm up and to record better slower performance on the smaller number of queries executed. Hence, the duration has increased for each test. Since a predefined set of queries for running a benchmark is generated before the test, the actual number of predefined queries has increased from 3 times to 10 times. On top of that, the previously mentioned vulcanic run was introduced to show the performance of repeated execution. &lt;/p&gt;

&lt;p&gt;For the isolated workload, the flag that defines the duration of execution &lt;code&gt;--single-threaded-runtime&lt;/code&gt; was set to 30 seconds (previously, it was 10 seconds). The lower bound for query execution was raised from 30 to 300 queries. This means that for slowest queries that took a few seconds to execute, at least 300 of those are being executed per test. Also, now each of the vendors executes an identical number of queries for each test. &lt;/p&gt;

&lt;p&gt;For the mixed and realistic workloads, the number of executed queries has been bumped up from 100 to 500. Also, Memgraph got a label index in the Pokec dataset since those are not set automatically by Memgraph. &lt;/p&gt;

&lt;p&gt;The exact running configuration and the rest of the details can be found in the methodology mentioned earlier. &lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at the Pokec dataset. For each of the results below, you can visit &lt;a href="https://memgraph.com/benchgraph/base?condition=cold&amp;amp;numberOfWorkers=12&amp;amp;datasetName=pokec&amp;amp;datasetSize=medium&amp;amp;platform=INTEL&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Detailed%20Results&amp;amp;anchor=expansion_1"&gt;here&lt;/a&gt;. Or if you select: INTEL, cold run, 12 workers, pokec, medium dataset, isolated workload, and go to detailed results for Q5, you will see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VgSR8_3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_cold_12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VgSR8_3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_cold_12.png" alt="Benchgraph expansion 1 cold 12" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This expansion 1 query has been executed 170996 times (&lt;a href="https://github.com/memgraph/benchgraph/blob/main/results/benchmarks.json"&gt;this is visible in raw JSON results&lt;/a&gt;) on both vendors with identical arguments. During the test, three main metrics were measured: peak memory, throughput, and latency. In the previous iteration of the benchmark, it was dynamically decided to execute a predefined number of queries based on the duration, which resulted in different query counts for Memgraph and Neo4j, but this is not the case anymore. After 170996 queries, Memgraph proves to be faster 3.16 times faster and has a 5.42 times lower p99 latency.  This is a simple single-hop query used quite often in graph analytical workloads.&lt;/p&gt;

&lt;p&gt;Trying out these 170996 queries on &lt;a href="https://memgraph.com/benchgraph/base?condition=cold&amp;amp;numberOfWorkers=48&amp;amp;datasetName=pokec&amp;amp;datasetSize=medium&amp;amp;platform=INTEL&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Detailed%20Results&amp;amp;anchor=expansion_1"&gt;48 threads&lt;/a&gt;, the following results were measured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5wZcPAMl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_cold_48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5wZcPAMl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_cold_48.png" alt="Benchgraph expansion 1 cold 48" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memgraph’s performance easily doubled with 48 threads, while Neo4j’s performance got worse. It looks like the Neo4j community edition has some multithreaded limits. P99 latency value has increased for both, which is a bit expected, but Memgraph is 8.73 times faster in this concurrent workload.&lt;/p&gt;

&lt;p&gt;Moving back now to the new &lt;code&gt;Vulcanic run&lt;/code&gt;, and back to &lt;a href="https://memgraph.com/benchgraph/base?condition=vulcanic&amp;amp;numberOfWorkers=12&amp;amp;datasetName=pokec&amp;amp;datasetSize=medium&amp;amp;platform=INTEL&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Detailed%20Results&amp;amp;anchor=expansion_1"&gt;12 threads&lt;/a&gt;, the 170966 queries were executed, and after that, the same set of 170966 queries was executed to get the measurement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zD0-1xWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_vulcanic_12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zD0-1xWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_vulcanic_12.png" alt="Benchgraph expansion 1 vulcanic 12" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, Neo4j improves on the stronger warm-up setting. Though it can be seen that p99 latency has dropped from 6.45 to 1.91 in Neo4j’s &lt;span&gt;case&lt;/span&gt;.So, Memgraph is the stable one here.&lt;/p&gt;

&lt;p&gt;The image below is again a vulcanic run, but this time under &lt;a href="https://memgraph.com/benchgraph/base?condition=vulcanic&amp;amp;numberOfWorkers=48&amp;amp;datasetName=pokec&amp;amp;datasetSize=medium&amp;amp;platform=INTEL&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Detailed%20Results&amp;amp;anchor=expansion_1"&gt;48 threads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sRTJBTja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_vulcanic_48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sRTJBTja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_expansion_1_vulcanic_48.png" alt="Benchgraph expansion 1 vulcanic 48" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even in the vulcanic run, where identical queries have been executed twice, Memgraph proved to be 3 times faster than Neo4j on 48 concurrent threads. Takeaways: the Memgraph community edition can handle the concurrent workloads better than the Neo4j community edition. &lt;/p&gt;

&lt;p&gt;What about LDBC interactive queries? Things are a bit &lt;a href="https://memgraph.com/benchgraph/base?condition=cold&amp;amp;numberOfWorkers=48&amp;amp;datasetName=ldbc_interactive&amp;amp;datasetSize=sf1&amp;amp;platform=AMD&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Global%20Results"&gt;more complicated&lt;/a&gt; here: On the AMD platform, cold run, 48 workers, ldbc_interactive dataset, and sf1 dataset size, Memgraph is faster than Neo4j on all the queries, except the &lt;code&gt;interactive query 9&lt;/code&gt;. It looks like Memgraph has an issue with that query. Take a look into the peak memory that jumped to a big 16GB. This will be investigated further. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IDO0JcRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_cold_48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IDO0JcRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_cold_48.png" alt="Benchgraph interactive cold 48" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving back to the &lt;a href="https://memgraph.com/benchgraph/base?condition=vulcanic&amp;amp;numberOfWorkers=48&amp;amp;datasetName=ldbc_interactive&amp;amp;datasetSize=sf1&amp;amp;platform=AMD&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Global%20Results"&gt;&lt;code&gt;Vulcanic&lt;/code&gt; warm-up&lt;/a&gt;, Neo4j performs much better here than on the cold run. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4_IS8yYs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_vulcanic_48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4_IS8yYs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_vulcanic_48.png" alt="Benchgraph interactive vulcanic 48" width="799" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But looking at the p99 latency on the cold run also tells an interesting story: Memgraph has 5 queries where p99 latency is over a second, while Neo4j has 10 of those.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mIby0cPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_latency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mIby0cPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_interactive_latency.png" alt="Benchgraph interactive latency" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is somewhat expected, as mentioned before, due to the different database architectures. The situation in the &lt;a href="https://memgraph.com/benchgraph/base?condition=cold&amp;amp;numberOfWorkers=48&amp;amp;datasetName=ldbc_bi&amp;amp;datasetSize=sf1&amp;amp;platform=AMD&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:0,%22queries%22:%5B0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38%5D%7D,%7B%22index%22:1,%22queries%22:%5B0,1,2,3%5D%7D,%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D,%7B%22index%22:3,%22queries%22:%5B0%5D%7D,%7B%22index%22:4,%22queries%22:%5B0%5D%7D%5D&amp;amp;tab=Global%20Results"&gt;BI run&lt;/a&gt; appears to be similar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8jesLz7B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_bi_cold.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8jesLz7B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/Benchgraph_bi_cold.png" alt="Benchgraph bi cold" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memgraph is a step ahead on the cold runs. On the vulcanic run, Neo4j improves quite a bit. Looks like Memgraph has a fundamental issue with handling BI_query_12. Again, we’ll investigate it further. &lt;/p&gt;

&lt;p&gt;All of the above, brings us to the potential issues of write performance; caches often get invalidated during the write performance of the database. And on that front, the situation is quite clear. Memgraph in-memory writes will be &lt;a href="https://memgraph.com/benchgraph/base?condition=cold&amp;amp;numberOfWorkers=48&amp;amp;datasetName=pokec&amp;amp;datasetSize=medium&amp;amp;workloadType=isolated&amp;amp;querySelection=%5B%7B%22index%22:2,%22queries%22:%5B0,1%5D%7D%5D&amp;amp;tab=Detailed%20Results&amp;amp;platform=AMD"&gt;faster&lt;/a&gt; than Neo4j on disk writes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BHKizbpJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/benchgraph_write.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BHKizbpJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-announcement/benchgraph_write.png" alt="benchgraph_write" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot more results out there on Benchgraph; change different configurations and take a look at the results. If you want to check the full results yourself, go to the &lt;a href="https://github.com/memgraph/benchgraph/blob/main/results/benchmarks.json"&gt;raw JSON files with the results&lt;/a&gt; in this iteration. Benchgraph provides just a subset of data, but more relevant data will be added, such as p95 latency, query counts, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can run benchmarks on your workload!
&lt;/h2&gt;

&lt;p&gt;Benchgraph workloads and queries are here to give you a glimpse of the potential performance you can expect for each database. That being said, it does not mean you will have the same situation on your workload. Running benchmarks on your specific dataset and on your specific queries will show performance that matters to you. Database tends to evolve; something that was 10x slower and faster changes with newer versions of databases, so make sure to run your own tests. &lt;/p&gt;

&lt;p&gt;But developing and running benchmarks is hard and time-consuming. That is the reason why we made Benchgraph simple to run. You can do it yourself &lt;a href="https://memgraph.com/blog/benchmark-memgraph-or-neo4j-with-benchgraph"&gt;by following the tutorial&lt;/a&gt; on running your own benchmarks via Benchgraph. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;How did you like the updates? Anything in particular that caught your attention? &lt;/p&gt;

&lt;p&gt;Benchmarks can be configured in various ways. Tiny details can have an impact on the results, but either way Memgraph offers much more perspective for speed improvement compared to Neo4j.&lt;/p&gt;

&lt;p&gt;From now on, Benchgraph will be at your disposal to unlock the untapped potential of your database, get performance insights, and make more informed decisions moving ahead. Looks like it’s time to &lt;a href="https://www.youtube.com/watch?v=vzc1iVtHgeE"&gt;add a new database to Benchgraph&lt;/a&gt;! As always, don’t hesitate to give us a shout if we can be of further assistance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/blog?topics=Under+the+Hood&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner#list"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ggEl866K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/external/memgraph-read-more-gradient-1200.png" alt="Read more about Memgraph internals on memgraph.com" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>memgraph</category>
      <category>neo4j</category>
      <category>database</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>How to Benchmark Memgraph [or Neo4j] with Benchgraph?</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Thu, 27 Apr 2023 13:28:21 +0000</pubDate>
      <link>https://forem.com/memgraph/how-to-benchmark-memgraph-or-neo4j-with-benchgraph-6p8</link>
      <guid>https://forem.com/memgraph/how-to-benchmark-memgraph-or-neo4j-with-benchgraph-6p8</guid>
      <description>&lt;p&gt;Preparing, running, and evaluating benchmarks is a tedious and time-consuming process. The hardest step in the whole process of benchmarking is setting up a “production-like” scenario. There are a lot of databases in production environments, a bunch of them serve different purposes, run different workloads, and are operated in different ways. This means it is impossible to simulate your specific workload and how your database will operate, so doing your own benchmarking is an important factor. On top of that, every use-case has some different metric that is used to evaluate the performance of the system.&lt;/p&gt;

&lt;p&gt;Since we are running benchgraph in our CI/CD environment, we decided to do small tweaks to benchgraph to ease the process of running benchmarks on Memgraph (and Neo4j 🙂) on your hardware, on your workload, and under conditions that matter to you. At the moment, the focus was on executing pure Cypher queries. &lt;/p&gt;

&lt;p&gt;To be able to run these benchmarks you need to have at least Python 3.7 and Docker installed and running, and a very basic knowledge of Python.&lt;/p&gt;

&lt;p&gt;Before you start working on your workload, you need to &lt;a href="https://download.memgraph.com/asset/benchgraph/benchgraph.zip"&gt;download benchgraph zip&lt;/a&gt; or pull &lt;a href="https://github.com/memgraph"&gt;Memgraph Github repository&lt;/a&gt;. If you are using the Memgraph repository just position yourself into &lt;code&gt;/tests/mgbench&lt;/code&gt;, if you are using zip just unzip it and open the folder in your favorite IDE or code editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your workload
&lt;/h2&gt;

&lt;p&gt;In the project structure you will see various different scripts, take a look at the &lt;a href="https://memgraph.com/blog/introduction-to-benchgraph-and-its-architecture"&gt;Benchgraph architecture&lt;/a&gt; for a more detailed overview of what each of these scripts does. But the important folder here is the workloads folder, where you will add your workload. You can start by creating a simple empty Python script, and giving it a name, in our case it is &lt;code&gt;demo.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In order to specify the workload that you will be able to run on Memgraph and Neo4j, you need to implement some details into your script. Here are 5 steps you need to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inherit the workload class&lt;/li&gt;
&lt;li&gt;Define a workload name&lt;/li&gt;
&lt;li&gt;Implement the dataset generator method&lt;/li&gt;
&lt;li&gt;Implement the index generator method&lt;/li&gt;
&lt;li&gt;Define the queries you want to benchmark&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These five steps will result in something similar to this simplified version of &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/workloads/demo.py"&gt;demo.py&lt;/a&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;workloads.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Workload&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Workload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dataset_generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeA {id: $id});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
            &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeB {id: $id});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"MATCH(a:NodeA {id: $A_id}),(b:NodeB{id: $B_id}) CREATE (a)-[:EDGE]-&amp;gt;(b)"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"A_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"B_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;indexes_generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeA(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeB(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark__test__get_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MATCH (n) RETURN n;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark__test__get_node_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MATCH (n:NodeA{id: $id}) RETURN n;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this &lt;code&gt;demo.py&lt;/code&gt; script into smaller pieces and explain the steps you need to do so it is easier to understand what is happening. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inherit the &lt;code&gt;workload&lt;/code&gt; class
&lt;/h3&gt;

&lt;p&gt;The Demo class has a parent class &lt;strong&gt;Workload&lt;/strong&gt;. Each custom workload should be inherited from the base Workload class. This means you need to add an import statement on top of your script and specify inheritance in Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;workloads.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Workload&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Workload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Define the workload name
&lt;/h3&gt;

&lt;p&gt;The class should specify the &lt;code&gt;NAME&lt;/code&gt; property. This is used to describe what workload class you want to execute. When calling &lt;code&gt;benchmark.py&lt;/code&gt;, the script that runs the benchmark process, this property will be used to differentiate different workloads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"demo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Implement the dataset generator method
&lt;/h3&gt;

&lt;p&gt;The class should implement the &lt;code&gt;dataset_generator()&lt;/code&gt; method. The method generates a dataset that returns the list of tuples. Each tuple contains a string of the Cypher query and dictionary that contains optional arguments, so the structure is as follows &lt;code&gt;[(str, dict), (str, dict)...]&lt;/code&gt;. Let's take a look at how the example list would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeA {id: 23});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeB {id: $id, foo: $property});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"property"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, you can pass just a Cypher query as a pure string without any values in the dictionary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeA {id: 23});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can specify parameters inside a dictionary. The variables next to &lt;code&gt;$&lt;/code&gt; sign in the query string will be replaced by the appropriate values behind the key from the dictionary. In this case, &lt;code&gt;$id&lt;/code&gt; is replaced by 123, and &lt;code&gt;$property&lt;/code&gt; is replaced by foo. The dictionary key names and variable names need to match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeB {id: $id, foo: $property});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"property"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to our &lt;code&gt;demo.py&lt;/code&gt; example, in the &lt;code&gt;dataset_generator()&lt;/code&gt; method, here you specify queries for generating a dataset. That means all queries for dataset generation are based on this specified list of queries. Keep in mind that you can't import dataset files (such as CSV, JSON, etc.) directly since the database is running in Docker, you need to convert dataset files to pure Cypher queries. But that should not be too hard to do in Python. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;demo.py&lt;/code&gt;, the first for loop is preparing the queries for creating 10000 nodes with the label NodeA and 10000 nodes with the label NodeB. We are using &lt;code&gt;random&lt;/code&gt; class to generate a random sequence of integer IDs. Each node has an id between 0 and 9999. In the second for loop, queries for connecting nodes randomly are generated. There is a total of 50000 edges, each connected to random NodeA and NodeB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dataset_generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeA {id: $id});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"CREATE (:NodeB {id: $id});"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="s"&gt;"MATCH(a:NodeA {id: $A_id}),(b:NodeB{id: $B_id}) CREATE (a)-[:EDGE]-&amp;gt;(b)"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"A_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"B_id"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Implement the index generator method
&lt;/h3&gt;

&lt;p&gt;The class should also implement the &lt;code&gt;indexes_generator()&lt;/code&gt; method. This is implemented the same way as the &lt;code&gt;dataset_generator()&lt;/code&gt; method, instead of queries for the dataset, &lt;code&gt;indexes_generator()&lt;/code&gt; should return the list of indexes that will be used. Of course, you can include constraints and other queries you have in your workload. The list of queries from &lt;code&gt;indexes_generator()&lt;/code&gt; will be executed before the queries for the dataset generator. The returning structure again is the list of tuples that contains query string and dictionary of parameters. Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;indexes_generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeA(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeB(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Define the queries you want to benchmark
&lt;/h3&gt;

&lt;p&gt;Now that your database has indexes and the dataset imported, you can specify what queries you wish to benchmark on the given dataset. Here are two queries that &lt;code&gt;demo.py&lt;/code&gt; workload defines. They are written as Python methods that return a single tuple with a query and dictionary, like in the data generator method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark__test__get_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MATCH (n) RETURN n;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark__test__get_node_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MATCH (n:NodeA{id: $id}) RETURN n;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The necessary details here are that each of the methods you wish to use in the benchmark test needs to start with &lt;code&gt;benchmark__&lt;/code&gt; in the name, otherwise, it will be ignored. The complete method name has the following structure &lt;code&gt;benchmark__group__name&lt;/code&gt;. The group can be used to execute specific tests, but more on that later.&lt;/p&gt;

&lt;p&gt;From the workload setup, this is all you need to do. The next step is running your workload. &lt;/p&gt;

&lt;h2&gt;
  
  
  Run benchmarks on your workload
&lt;/h2&gt;

&lt;p&gt;Let's start with the most straightforward way to run the &lt;code&gt;demo.py&lt;/code&gt; workload from the example above. The main script that manages benchmark execution is &lt;code&gt;benchmark.py&lt;/code&gt;, it accepts a variety of different arguments, but we will get there. Open the terminal of your choice, and position yourself in the downloaded benchgraph folder. &lt;/p&gt;

&lt;p&gt;To start the benchmark, you need to run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python3 benchmark.py vendor-docker --vendor-name ( memgraph-docker || neo4j-docker ) benchmarks demo/*/*/* --export-results result.json  --no-authorization&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;For example, to run this on &lt;strong&gt;Memgraph&lt;/strong&gt;, the command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python3 benchmark.py vendor-docker --vendor-name memgraph-docker benchmarks “demo/*/*/*” --export-results results.json --no-authorization&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;After a few seconds or minutes, depending on your workload, the benchmark should be done with execution.&lt;br&gt;
In your terminal, you should see something similar to this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c_MrysOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/how-to-run-benchgraph-on-your-data/benchmark-terminal-output.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c_MrysOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/how-to-run-benchgraph-on-your-data/benchmark-terminal-output.png" alt="benchgraph terminal output" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the image in the end, you can see the summary of results, feel free to explore and write different queries for Memgraph to see what type of performance you can expect.&lt;/p&gt;

&lt;p&gt;To run this same workload on Neo4j, just change &lt;code&gt;–vendor-name&lt;/code&gt; argument to &lt;code&gt;neo4j-docker&lt;/code&gt;. If you stumble upon the issues with setting specific indexes or queries, take a look at how to run the same workload on different vendors.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to compare results
&lt;/h2&gt;

&lt;p&gt;Once the benchmark has been run on both vendors, or with different configurations, the results are saved in a file specified by &lt;code&gt;--export-results&lt;/code&gt; argument. You can use the results files and compare them against other vendor results via the &lt;code&gt;compare_results.py&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python3 compare_results.py --compare path_to/run_1.json path_to/run_2.json --output run_1_vs_run_2.html --different-vendors&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;The output is an HTML file with a visual representation of the performance differences between two compared vendors. The first passed summary JSON file is the reference point. Feel free to open an HTML file in any browser at hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FPv_WwFL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/how-to-run-benchgraph-on-your-data/benchmark-output-html-file.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FPv_WwFL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/how-to-run-benchgraph-on-your-data/benchmark-output-html-file.png" alt="benchmark output html file" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to configure the benchmark run
&lt;/h2&gt;

&lt;p&gt;Configuring your benchmark run will enable you to see how things change under different conditions. Some arguments used in the run above are self-explanatory. For a full list take a look at the benchmark.py script. For now, let's break down the most important ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NAME/VARIANT/GROUP/QUERY&lt;/code&gt; - The argument &lt;code&gt;demo/*/*/*&lt;/code&gt; says to execute the workload named &lt;code&gt;demo&lt;/code&gt;, and all of its variants, groups, and queries. This flag is used for direct control of what workload you wish to execute. The &lt;code&gt;NAME&lt;/code&gt; here is the name of the workload defined in the Workload class. &lt;code&gt;VARIANT&lt;/code&gt; is an additional workload configuration, which will be explained a bit later. &lt;code&gt;GROUP&lt;/code&gt; is defined in the query method name, and &lt;code&gt;QUERY&lt;/code&gt; is the query name you wish to execute. If you want to execute a specific query from &lt;code&gt;demo.py&lt;/code&gt;, it would look like this: &lt;code&gt;demo/*/test/get_nodes&lt;/code&gt;. This will run a &lt;code&gt;demo&lt;/code&gt; workload on all &lt;code&gt;variants&lt;/code&gt;, in the &lt;code&gt;test&lt;/code&gt; query group and query &lt;code&gt;get_nodes&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--single-threaded-runtime-sec&lt;/code&gt; - The question at hand is how many of each specific queries you wish to execute as a sample for a database benchmark. Each query can take a different time to execute, so fixating a number could yield some queries finishing in 1 second and others running for a minute. This flag defines the duration in seconds that will be used to approximate how many queries you wish to execute. The default value is 10 seconds, this means the &lt;code&gt;benchmark.py&lt;/code&gt; will generate a predetermined number of queries to approximate single treaded runtime of 10 seconds. Increasing this will yield a longer running test. Each specific query will get a different count that specifies how many queries will be generated. This can be inspected after the test. For example, for 10 seconds of single-threaded runtime, the queries from demo workload &lt;code&gt;get_node_by_id&lt;/code&gt; got 64230 different queries, while &lt;code&gt;get_nodes&lt;/code&gt; got 5061 because of different time complexity of queries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--num-workers-for-benchmark&lt;/code&gt; - The flag defines how many concurrent clients will open and query the database. With this flag, you can simulate different database users connecting to the database and executing queries. Each of the clients is independent and executes queries as fast as possible. They share a total pool of queries that were generated by the &lt;code&gt;--single-threaded-runtime-sec&lt;/code&gt;. This means the total number of queries that need to be executed is shared between a specified number of workers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--warm-up&lt;/code&gt; - The warm-up flag can take three different arguments, &lt;code&gt;cold&lt;/code&gt;, &lt;code&gt;hot&lt;/code&gt;, and &lt;code&gt;vulcanic&lt;/code&gt;. Cold is the default. There is no warm-up being executed, &lt;code&gt;hot&lt;/code&gt; will execute some predefined queries before the benchmark, while &lt;code&gt;vulcanic&lt;/code&gt; will run the whole workload first before taking measurements. Here is the implementation of &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/benchmark.py#L186"&gt;warm-up&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to run the same workload on the different vendors
&lt;/h2&gt;

&lt;p&gt;The base &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/workloads/base.py"&gt;workload class&lt;/a&gt; has &lt;code&gt;benchmarking context&lt;/code&gt; information that contains all benchmark arguments used in the benchmark run. Some are mentioned above. The key argument here is the &lt;code&gt;--vendor-name&lt;/code&gt;, which defines what database is being used in this benchmark.&lt;/p&gt;

&lt;p&gt;During the creation of your workload, you can access the parent class property by using &lt;code&gt;self.benchmark_context.vendor_name&lt;/code&gt;. For example, if you want to specify special index creation for each vendor, the &lt;code&gt;indexes_generator()&lt;/code&gt; could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;indexes_generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;"neo4j"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;benchmark_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vendor_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX FOR (n:NodeA) ON (n.id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX FOR (n:NodeB) ON (n.id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;indexes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeA(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CREATE INDEX ON :NodeB(id);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies to the &lt;code&gt;dataset_generator()&lt;/code&gt;. During the generation of the dataset, you can use special types of queries for different vendors, to simulate identical scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy benchmarking
&lt;/h2&gt;

&lt;p&gt;Have fun benchmarking Memgraph and Neo4j! We would love to hear more about your results on our &lt;a href="https://discord.com/invite/memgraph"&gt;Discord server&lt;/a&gt;. If you have issues understanding what is happening, take a look at Benchgraph architecture or just reach out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/blog?topics=Under+the+Hood&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner#list"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ggEl866K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/external/memgraph-read-more-gradient-1200.png" alt="Read more about Memgraph internals on memgraph.com" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>memgraph</category>
      <category>neo4j</category>
      <category>benchmark</category>
      <category>database</category>
    </item>
    <item>
      <title>Introduction to Benchgraph and its Architecture</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Thu, 27 Apr 2023 13:28:07 +0000</pubDate>
      <link>https://forem.com/memgraph/introduction-to-benchgraph-and-its-architecture-2ede</link>
      <guid>https://forem.com/memgraph/introduction-to-benchgraph-and-its-architecture-2ede</guid>
      <description>&lt;p&gt;Building a performant graph database comes with many different challenges, from architecture design, to implementation details, technologies involved, product maintenance — the list goes on and on. And the decisions made at all of these crossroads influence the database performance.&lt;/p&gt;

&lt;p&gt;Developing and maintaining a product is a never-ending phase. And proper performance testing is necessary to maintain database performance characteristics during its whole lifecycle.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;benchgraph&lt;/strong&gt; comes into play. To ensure the consistency of Memgraph’s performance, tests are run via benchgraph, Memgraph's in-house benchmarking tool. On each commit, theCI/CD infrastructure runs performance tests to check how each code change influenced the performance. Let’s take a look at benchgraph architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchgraph architecture
&lt;/h2&gt;

&lt;p&gt;At the moment, benchgraph is a project &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench"&gt;under Memgraph repository (previously Mgbench)&lt;/a&gt;. It consists of Python scripts and a C++ client. Python scripts are used to manage the benchmark execution by preparing the workload, configurations, and so on, while the C++ client actually executes the benchmark. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VkciFC75--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-architecture/memgraph-benchgraph-architecture-diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VkciFC75--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-architecture/memgraph-benchgraph-architecture-diagram.png" alt="benchgraph architecture diagram" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of the more important Python scripts are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/benchmark.py"&gt;benchmark.py&lt;/a&gt; - The main entry point used for starting and managing the execution of the benchmark. This script initializes all the necessary files, classes, and objects. It starts the database and the benchmark and gathers the results. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/runners.py"&gt;runners.py&lt;/a&gt; - The script that configures, starts, and stops the database. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/benchmark_context.py"&gt;benchmark_context.py&lt;/a&gt; - It gathers all the data that can be configured during the benchmark execution. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/workloads/base.py"&gt;base.py&lt;/a&gt; - This is the base workload class. All other workloads are subclasses located in the workloads directory. For example, &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/workloads/ldbc_interactive.py"&gt;ldbc_interactive.py&lt;/a&gt; defines ldbc interactive dataset and queries (but this is NOT an official LDBC interactive workload). Each workload class can generate the dataset, use custom import ofthe dataset or provide a CYPHERL file for the import process. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/compare_results.py"&gt;compare_results.py&lt;/a&gt; - Used for comparing results from different benchmark runs. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/client.cpp"&gt;C++ bolt benchmark client&lt;/a&gt; has its own set of features. It runs over the Bolt protocol and executes Cypher queries on the targeted database. It supports validation, time-dependent execution, and running queries on multiple threads.&lt;/p&gt;

&lt;p&gt;Let’s dive into  &lt;span&gt;benchmark.&lt;/span&gt;py, &lt;span&gt;runners&lt;/span&gt;.py, and the C++ client a bit further.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;span&gt;benchmark&lt;/span&gt;.py
&lt;/h2&gt;

&lt;p&gt;The &lt;span&gt;benchmak&lt;/span&gt;.py script is filled with important details crucial for running the benchmark, but here is just a peek at a few of them. &lt;/p&gt;

&lt;p&gt;All arguments are passed and interpreted in the &lt;span&gt;benchmark&lt;/span&gt;.py script. For example, the following snippet is used to set the number of workers that will import the data and execute the benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="n"&gt;benchmark_parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"--num-workers-for-import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;multiprocessing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number of workers used to import the dataset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;benchmark_parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"--num-workers-for-benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number of workers used to execute the benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the procedure that generates the queries for the workload, you can set up the seed for each specific query. As some arguments in the queries are generated randomly, the seed ensures that the identical sequence of randomly generated queries is executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Make the generator deterministic.
&lt;/span&gt;    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Generate queries.
&lt;/span&gt;    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following methods define how warmup, the mixed, and realistic workload are executed and how the query count is approximated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;warmup&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mixed_workload&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;
          &lt;span class="err"&gt;…&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_query_cache_count&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;span&gt;runners&lt;/span&gt;.py
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/runners.py"&gt;runners.py&lt;/a&gt; script manages the database, and the C++ client executes the benchmark. Runners script can &lt;br&gt;
manage both native Memgraph and Neo4j, as well as Docker Memgraph and Neo4j. &lt;/p&gt;

&lt;p&gt;Vendors are handled with the following classes; of course, implementation details are missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Memgraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseRunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Neo4j&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseRunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MemgraphDocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseRunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
 &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Neo4jDocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseRunner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that the Docker versions have a noticeable performance overhead compared to the native versions. &lt;/p&gt;

&lt;p&gt;The C++ Bolt client executing benchmarks can also be managed in the native and Docker form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BoltClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseClient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BoltClientDocker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseClient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  C++ Bolt client
&lt;/h2&gt;

&lt;p&gt;The other important piece of code is the C++ bolt client. The client is used for the execution of the workload. The workload is specified as a list of Cypher queries. The client can simulate multiple concurrent connections to the database. The following code snippet initializes a specific number of workers and connects them to the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;FLAGS_num_workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kr"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;memgraph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FLAGS_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAGS_port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;memgraph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;communication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ClientContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FLAGS_use_ssl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;memgraph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;communication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bolt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAGS_username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAGS_password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using multiple concurrent clients, the benchmark simulates different users connecting to the database and executing queries. This is important because you can simulate how your database handles higher data loads. &lt;/p&gt;

&lt;p&gt;Each worker's latency values are collected during execution, and after that, some basic tail-latency values are calculated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="err"&gt;…&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;FLAGS_num_workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;worker_query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lower_bound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lower_bound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"iterations"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;accumulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"p99"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"p95"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"p90"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"p75"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"p50"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_latency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;spdlog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"To few iterations to calculate latency values!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"iterations"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the rest of the details about all the components mentioned above and used in Memgraph’s CI/CD, feel free to refer to &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/client.cpp"&gt;the code base&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchgraph benchmark process
&lt;/h2&gt;

&lt;p&gt;Each benchmark has a set of rules or steps it follows to get benchmark results, such as duration of the benchmark, query variety, restarting a database, and similar. Benchgraph has its own set of running specifics. The benchmark process is also explained in the benchgraph &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/README.md"&gt;methodology&lt;/a&gt; but for running a &lt;a href="https://memgraph.com/benchgraph/base"&gt;benchgraph&lt;/a&gt;. It’s important to understand each step well because each step influences benchmark results. &lt;br&gt;
The image below shows the key steps in running a benchmark with Benchgraph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k3G8ZUJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-architecture/memgraph-benchgraph-process.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k3G8ZUJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/benchgraph-architecture/memgraph-benchgraph-process.png" alt="benchgraph process" width="800" height="1043"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step is to start the database with predefined configuration options. Running Memgraph with different configuration options allows us to see respective performance implications. &lt;/p&gt;

&lt;p&gt;The next step is the import procedure. Depending on the configuration of workload classes mentioned above, import may be custom, or data can be imported by executing Cypher queries via the C++ Bolt client. Being able to execute a list of Cypher queries allows us to specify different types of customer workloads quickly. After the import, the newly imported data is exported as a snapshot and reused to execute all the workloads that follow. But, if the first workload executed write queries, the dataset will be changed and therefore unusable because it will influence the results of the benchmark. &lt;/p&gt;

&lt;p&gt;Once the import is finished, the database is stopped. Importing a dataset can stress the database and influence measurements, so the database must be restarted. After the restart, the database imports data from a snapshot, and the performance test can begin. &lt;/p&gt;

&lt;p&gt;At the beginning of the test, queries are generated based on the benchmark configuration and the type of workload. &lt;/p&gt;

&lt;p&gt;Benchgraph support three types of workloads: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolated&lt;/strong&gt; - Concurrent execution of a single type of query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed&lt;/strong&gt; - Concurrent execution of a single type of query mixed with a certain percentage of queries from a designated query group. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realistic&lt;/strong&gt; - Concurrent execution of queries from write, read, update, and analyze groups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of the workloads can be used to simulate a different production scenario. &lt;/p&gt;

&lt;p&gt;Once the queries are executed, metrics such as basic latency measurements, queries per second, and peak RAM usage during the execution are collected and reported daily via our CI/CD infrastructure to graphana. &lt;/p&gt;

&lt;p&gt;In the end, the database is stopped, and depending on the workload, it is started again with a fresh dataset from the snapshot, and the execution of the next query or workload begins again.&lt;/p&gt;

&lt;p&gt;Just a side note on restarting a database during the execution of a benchmark: In an average use case, databases can run for a prolonged period of time. They are not being restarted very often, let’s say, in some edge cases when you are doing some upgrades or are having issues. That being said, why are databases restarted during benchmarks? Executing test after test on the non-restarted database can lead to tests being influenced by previously run tests. For example, if you want to measure the performance of queries X and Y, they should be run under the same conditions, which means a database with a fresh dataset and without any caches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark configuration options
&lt;/h2&gt;

&lt;p&gt;Here are just &lt;a href="https://github.com/memgraph/memgraph/blob/master/tests/mgbench/benchmark.py"&gt;some of the flags&lt;/a&gt; that can be used to configure benchgraph during the performance runs. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;--num-workers-for-benchmark&lt;/code&gt; - This flag defines the number of workers that will be used to query the database. Each worker is a new thread that connects to Memgraph and executes queries. All threads share the same pool of queries, but each query is executed just once. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;--single-threaded-runtime-sec&lt;/code&gt; - This flag defines the pool number of queries that will be executed in the benchmark. The question at hand is how many of each specific query you wish to execute as a sample for a database benchmark. Each query can take a different time to execute, so fixating a number, let’s say 100 queries, could be finished in 1 second, and 100 queries of a different type could run for an hour. To avoid such an issue, this flag defines the duration of single-threaded runtime in seconds that will be used to approximate the number of queries you wish to execute. The queries will be pre-generated based on the required time range for a single-threaded runtime. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;--warm-up&lt;/code&gt; -  The warm-up flag can take three different arguments, &lt;code&gt;cold&lt;/code&gt;, &lt;code&gt;hot&lt;/code&gt;, and &lt;code&gt;vulcanic&lt;/code&gt;. Cold is the default. There is no warm-up being executed, &lt;code&gt;hot&lt;/code&gt; will execute some predefined queries before the benchmark, while &lt;code&gt;vulcanic&lt;/code&gt; will run the whole workload first before taking measurements. Databases in production environments are usually run pre-warmed and pre-cached since they are running for extended periods of time. But this is not always the case; warming up the database takes time, and caching can be ruined by the volatility of the data inside the database. Cold performance is the worst-case scenario for every database and should be considered.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--workload-realistic&lt;/code&gt;, &lt;code&gt;--workload-mixed&lt;/code&gt; - These flags are used to specify the workload type. By default, Benchgraph runs on an isolated workload.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--time-depended-execution&lt;/code&gt; - A flag for defining how long the queries will be executed in seconds. The same pool of queries will be re-run all over again until time-out. This is useful for testing caching capabilities of the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does Memgraph build an in-house benchmark tool?
&lt;/h2&gt;

&lt;p&gt;Building benchmark infrastructure is time-consuming, and there are a few options for load-testing tools on the market. On top of that, building benchmarking infrastructure is error-prone; memories of unconfigured indexes and running benchmarks on debug versions of Memgraph are fun 😂&lt;/p&gt;

&lt;p&gt;But due to the need for a custom benchmark process, configuration options, and specific protocol, Memgraph is working on a house benchmarking tool Benchgraph. This is not universal advice: We would not advise embarking on this journey if some of the tools out there can satisfy your benchmarking needs. But in the long run, an internal benchmarking tool provides a lot of flexibility, and an external tool is a great addition as validation and support to all performance tests being executed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/blog?topics=Under+the+Hood&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner#list"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ggEl866K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://public-assets.memgraph.com/external/memgraph-read-more-gradient-1200.png" alt="Read more about Memgraph internals on memgraph.com" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>memgraph</category>
      <category>neo4j</category>
      <category>database</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>How Much Money Will You Spend on Hosting a Database</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Tue, 10 Jan 2023 13:45:58 +0000</pubDate>
      <link>https://forem.com/memgraph/how-much-money-will-you-spend-on-hosting-a-database-2i7l</link>
      <guid>https://forem.com/memgraph/how-much-money-will-you-spend-on-hosting-a-database-2i7l</guid>
      <description>&lt;p&gt;Each modern application needs an appropriate database that has basic datastore capabilities or strong analytical capabilities. There is a variety of options to choose from, and choosing a database can be a complex adventure. Just start with the proprietary or open-source choice, and things get very messy. But, after screening several database options and their features, you will probably narrow it down to a few possible candidates for your databases. No matter what databases are on your shortlist, open-source or proprietary, there are always some costs attached to running that database system. At some point in decision-making, the cost will be one of the most important factors when deciding between your options. The issue is that approximating the true future cost of owning a database can be hard. &lt;/p&gt;

&lt;p&gt;The cost can come from various sources, such as licensing, training, features included, support, and hosting of the database etc. Each mentioned cost is important and should be considered independently because it can differ from vendor to vendor. But, it’s the accumulation of those costs that form the cost of ownership. One cost is often overlooked - hosting. And it can differ tremendously from database to database. &lt;/p&gt;

&lt;p&gt;The cost of hosting a database is always present and constant, if you are renting a full VMs and the data volume is not growing rapidly. Since hosting costs are usually paid in shorter time frames, for example on a monthly basis, they can add up to noticeable sums over time. Understanding the cost of ownership and hosting costs, in particular, can give you a full view of the database cost. &lt;/p&gt;

&lt;p&gt;If you have highly interconnected and recursive data and you want to do analytics on it, you will have to find an appropriate graph database. In this blog post, we will compare the hosting costs of running Memgraph and Neo4j graph databases. &lt;/p&gt;

&lt;p&gt;Memgraph and Neo4j are compatible databases. Both use Bolt protocol and Cypher for queries, which means you can easily switch your infrastructure between them. Both vendors have free community and paid enterprise editions of the database. &lt;a href="https://memgraph.com/blog/get-a-feature-rich-open-source-community-edition-graph-database-ready-for-production?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;There are some differences between editions&lt;/a&gt;, for example Memgraph community edition has a replication for high availability, while Neo4j community does not. To understand difrences in hostings costs, let’s see how  community editions compare head-to-head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approximate the cost of hosting a database
&lt;/h2&gt;

&lt;p&gt;When engineers start to think about the cost of ownership of database systems and compare vendors, they usually focus on server resource usage. Engineers like to keep track of each CPU cycle, megabyte spent here and there, and overall database performance. Hence, the important metrics are &lt;strong&gt;CPU time, RAM, Network&lt;/strong&gt; and &lt;strong&gt;disk storage&lt;/strong&gt;. Network and disk storage can also contribute cost, but in this blog post we will be talking about small to medium datasets in which case these two parameters play a minor role.&lt;/p&gt;

&lt;p&gt;CPU time is the cost of running computations on the database. Since you will probably rent a full cloud virtual machine just for your database, CPU time will not matter too much. But overall database performance will be important since &lt;a href="https://memgraph.com/blog/how-to-choose-a-graph-database-for-your-real-time-application?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;performance can limit your use case&lt;/a&gt;. To combat database inefficiency and higher load, you will have to increase CPU count or CPU power. Performance of each database was already discussed in the &lt;a href="https://memgraph.com/blog/memgraph-vs-neo4j-performance-benchmark-comparison?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;Memgraph vs. Neo4j: A Performance Comparison.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RAM usage is what can considerably shape the overall price of an instance. In general, you want to use as less CPU and RAM as possible since their high usage can generate a lot of costs. Let’s assume that you have an unspecified-sized dataset at the moment, and you need to host Memgraph and Neo4j on the most popular cloud VM vendor, AWS.&lt;/p&gt;

&lt;p&gt;Amazon AWS EC2 T3 instances have a balance of computing, memory, and network resources for a broad spectrum of general purpose workloads, including large-scale micro-services, small and medium databases and business-critical applications. There are also other types of instances available on AWS that have different purposes, such as CPU-optimized, memory-optimized, and storage-optimized, that can fit your use case even better, but T3 instances have been chosen for demonstration purposes since they are quite general. &lt;/p&gt;

&lt;p&gt;Let's assume you predict running your database for three years on this infrastructure, after which you will probably need to scale up. This would be the on-demand cost of running the instances for a three full-year rent, at the time of writing: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v_OuAsQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memgraph-database-cost-aproximation.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v_OuAsQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memgraph-database-cost-aproximation.png" alt="image alt" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind, that AWS offer reservations, which can half the stated renting prices, but this is just an aproximation of the costs. When choosing between Memgraph and Neo4j, their memory usage is what will define how large the instance needs to be and how much money you will need to spend in the end. &lt;/p&gt;

&lt;h2&gt;
  
  
  Differences between Memgraph and Neo4j memory architecture
&lt;/h2&gt;

&lt;p&gt;Before going into differences in memory usage, it is important to understand how both systems operate. Since Neo4j and Memgraph are architecturally different systems, they use memory quite differently. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memgraph&lt;/strong&gt; is a native C++ database and stores all data in RAM memory. No data is stored on the disk, and all queries are executed on the data in RAM. Memgraph supports persistency, which means that if the server loses power, data present in Memgraph at the moment before the crash will not be lost. Persistency is achieved with periodic snapshots saved as a backup on disk. By design, Memgraph is better equipped to execute real-time computations that need to be executed in the shortest possible timeframe.But for this very reason, Memgraph is limited by the available RAM of your machine. If your dataset doesn’t fit in RAM, you can’t use Memgraph. That’s why it’s important to &lt;a href="https://memgraph.com/docs/memgraph/under-the-hood/storage?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;calculate the approximate size of your dataset&lt;/a&gt; beforehand and find out &lt;a href="https://memgraph.com/docs/memgraph/reference-guide/memory-control?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;how to control memory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neo4j&lt;/strong&gt; story is a bit more complex since it’s JVM based. JVM performs all RAM allocation, and JVM overhead comes with Neo4j as standard. Since Neo4j is contained in JVM, understanding complete memory usage is not as straightforward as with Memgraph. On top of that, Neo4j stores all the data on disk, but it is also loading data in RAM as a cache. This means queries can use both data from RAM and disk. Querying the data from RAM will lead to faster performance while querying the data from disk will lead to performance degradation. &lt;/p&gt;

&lt;p&gt;The memory storage available for caching is defined by the &lt;a href="https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/#config_server.memory.pagecache.size"&gt;page cache&lt;/a&gt; property in the Neo4j configuration. It gives you the ability to define how much graph data can be stored in RAM. Neo4j is not limited by total RAM, but rather by total disk storage, but a drop in the Neo4j’s performance without the RAM would be noticeable. &lt;/p&gt;

&lt;p&gt;To improve performance, Neo4j will try to load as much data as possible to the RAM cache, performing the so-called warm-up process.The &lt;a href="https://neo4j.com/docs/operations-manual/current/performance/memory-configuration/"&gt;documentation recommends&lt;/a&gt; giving 90% of the instance available memory to the page cache. That is an optimization from the Neo4j side that will help improve the overall performance of Neo4j. &lt;/p&gt;

&lt;p&gt;Even though there are many differences between on-disk and in-memory storage, the choice primarily depends on your use case and your requirements. &lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark shines light on hosting costs
&lt;/h2&gt;

&lt;p&gt;Now that you know that memory usage is an important factor for hosting costs and how both Memgraph and Neo4j work with memory,  how do you actually decide which database to chose? The best option would be the one that uses less memory, thus enables you to use smaller instance. To find out memory measurements, we have executed workloads on the database containing the Slovenian social network, Pokec available in three different sizes, small, medium, and large:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_small_import.cypher"&gt;small&lt;/a&gt; - 10,000 vertices, 121,716 edges &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_medium_import.cypher"&gt;medium&lt;/a&gt; - 100,000 vertices, 1,768,515 edges &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_large.setup.cypher.gz"&gt;large&lt;/a&gt; - 1,632,803 vertices, 30,622,564 edges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the sizes of the datasets in production enviroments vary from few thousands to trillions of nodes and edges, these datasets are all on the smaller, as they are used for demonstration purposes. In the Memgraph storage engine, the small dataset takes approximately 40 MB of RAM, the medium 400MB of RAM, and the large dataset takes approximately 4GB of RAM. &lt;/p&gt;

&lt;p&gt;Memory is tracked during importing of the dataset from the CYPHERL file and the execution of two queries. While the workloads are executing on a Linux machine, the script samples the total Memgraph and Neo4j RSS usage every 50 milliseconds and tracks the memory usage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep in mind that the workloads were run with the out-of-the-box community version of Neo4j and Memgraph, databases weren’t additionally configured.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The two executed queries are K-hop query and an aggregation query. The expansion or K-hop queries are among the most interesting queries in any graph analytical workload. Expansion queries start from the target node and return all the connected nodes that are a defined number of hops away. It is an analytical query that is fairly cheap to execute and used a lot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;n.id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aggregation query is very memory intensive. It needs to match all the nodes in the database and get the minimum, maximum and average values of a certain node property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n.age&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n.age&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;avg&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n.age&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how did Memgraph and Neo4j do on performing these tasks? If you are presuming that, because Memgraph is an in-memory graph database, it must use more RAM than Neo4j, an on-disk graph database, you will be surprised. &lt;/p&gt;

&lt;p&gt;The graph below shows memory usage during the execution of an expansion 1 query on a small dataset. The identical query was run 10.000 times on 12 concurrent clients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ln7-A2vQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ln7-A2vQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-1.png" alt="memory-graph-usage" width="880" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the line chart, Memgraph executed these 10.000 queries much faster, and with less memory. As Memgraph executed this workload in just a fraction of the time, compared to Neo4j, the linechart is not the best way to visualize the data. &lt;/p&gt;

&lt;p&gt;It is interesting to see how Neo4j memory usage is rising and falling due to the JVM and workload execution, because JVM is trying to allocate optimal amount of memory during the execution. It is important to note that Neo4j is not using all of the memory to execute this workload, meaning that JVM is over-allocating memory for future use, which is usual behavior for JVM-based applications. Due to it, it’s harder to interpret actual memory usage, and Neo4j didn’t ease the process as well. &lt;/p&gt;

&lt;p&gt;The graph below shows memory usage during the execution of 10.000 aggregation queries on a small dataset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5SzgCwN8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5SzgCwN8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-2.png" alt="memory-graph-usage" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how Memgraph’s memory usage is fairly constant at 300 MB during the entire workload execution, while Neo4j’s memory usage is again using various amounts of memory during the run. Again, JVM allocates extra memory while executing the queries, and in the end, it settles around 5.3 GB.&lt;/p&gt;

&lt;p&gt;Neo4j memory usage can be decreased to use RAM for JVM and some extra RAM for other components like transactions, query caching, etc. But as it turns out, the costs of this modification is performance and stability degradation. During testing, we experimented a bit with Neo4j memory usage configurations and limited Neo4j memory usage a bit more aggressively, which resulted in several crashes. The ability to fine-tune a database is  great for configuring software into perfect condition, but it can increase engineering costs, since you need to invest engineering time to properly understand the system and fine tune the database configuration.  Memgraph is fully self-manageable, which is shown by the constant memory usage. &lt;/p&gt;

&lt;p&gt;Overall, Neo4j consumes more memory than Memgraph while executing workloads on the small dataset, which is expected for JVM based system, but this can change with the scale of the dataset. To understand the worst-case scenario of operating the database on AWS instances, &lt;strong&gt;peak memory usage&lt;/strong&gt; should be considered. Peak memory usage will show the amount of RAM the instance should use in the worst case scenario. &lt;/p&gt;

&lt;p&gt;Here are peak memory usages for the small dataset:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A5RkP1F6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A5RkP1F6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-3.png" alt="memory-graph-usage-small-dataset" width="880" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memgraph peak memory usage is 235 MB during data import, while Neo4j is 5 345 MB during aggregation queries. &lt;/p&gt;

&lt;p&gt;In this particular case, Memgraph could potentially fit on the t3.nano instance, but that would be a very thigh squeeze, and the instance could be used only for the database, no other software could be run on the instance. This means Memgraph fits on a t3.micro which costs $299 for a three-year rent. Neo4j, on the other hand, could fit on a t3.large which costs $2.369 for a three-year rent. That is a difference of $2.070 dollars for a three-year rent &lt;/p&gt;

&lt;p&gt;Let’s see what happens on a medium-sized dataset:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---PwbjKOq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---PwbjKOq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-4.png" alt="memory-graph-usage-medium-dataset" width="880" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, Memgraph's peak memory usage is 1.2 GB during data import, while Neo4j is 6.3 GB during aggregation queries. Relative to these peak values, Memgraph could fit on a t3.small which costs 599$ for a three-year rent. Neo4j, on the other hand, could fit on a t3.large which costs 2.369$ for a three-year rent. That is a difference of 1.770$ dollars for a three-year rent. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f_x9w3aR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f_x9w3aR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-much-will-you-spend-hosting-a-database/memory-graph-usage-5.png" alt="memory-graph-usage-large-dataset" width="880" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the large dataset, Memgraph's peak memory usage is 6.07 GB during data import, while Neo4j is 17.9 GB, also on data import. The import process consists of many serial transactions, which is memory-wise stressful for both databases. In this particular case, Memgraph fits on a t3.large which costs 2.369$ for a three-year rent. Neo4j, on the other hand, could fit on a t3.2xlarge which costs 9,586$ for a three-year rent. That is a difference of 7.217$ for a three-year rent. &lt;/p&gt;

&lt;p&gt;As you can see, in some situations Memgraph can be more memory efficent then Neo4j. By correlating peak memory usages to AWS instance pricing savings with Memgraph could be substantial. This depends of few factors, such as, dataset size, RAM restrictctios, performance goals, instance types etc. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Database hosting is an interesting cost in the total cost of ownership and it should be taken into consideration before deciding which database to choose. As you can see, Memgraph is a pretty stable database regarding memory usage, which could lead to potentially significant cost savings. On top of that, Memgraph is also faster, which can lead to a more responsive system overall. &lt;/p&gt;

&lt;p&gt;We would love to see what type of application you want to develop with Memgraph. Take Memgraph for a &lt;a href="https://memgraph.com/download?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;test drive&lt;/a&gt;, check out our &lt;a href="https://memgraph.com/docs?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;documentation&lt;/a&gt; to understand the nuts and bolts of how it works, or read more &lt;a href="https://memgraph.com/blog?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;blogs&lt;/a&gt; to see a plethora of use cases using Memgraph. If you have any questions for our engineers or want to interact with other Memgraph users, join the Memgraph Community on &lt;a href="https://discord.com/invite/memgraph"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/memgraph-for-neo4j-developers/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NWKqzkwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/external/memgraph-read-more-gradient-1200.png" alt="Read more about Neo4j and Memgraph on memgraph.com" width="880" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>memgraph</category>
      <category>opensource</category>
      <category>aws</category>
    </item>
    <item>
      <title>Get a Feature-Rich Open-Source Community Edition Graph Database Ready for Production</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Mon, 09 Jan 2023 10:45:02 +0000</pubDate>
      <link>https://forem.com/memgraph/get-a-feature-rich-open-source-community-edition-graph-database-ready-for-production-5fej</link>
      <guid>https://forem.com/memgraph/get-a-feature-rich-open-source-community-edition-graph-database-ready-for-production-5fej</guid>
      <description>&lt;p&gt;In the last couple of years, the world has embraced the open-source community, bringing many community-driven projects to success. These days if you need a software solution for some business challenge, you can probably find the solution in a wide range of successful open-source projects. Since graph database space is fairly young and most projects started in the last few years, most graph databases are developed under open-source licenses from the get-go. This is great for the whole evolving ecosystem of graph databases, and it gives users free access to the most cutting-edge graph database technology. &lt;/p&gt;

&lt;p&gt;Developing a scalable and robust graph database takes immense amounts of effort and capital. This is why most database companies require a lot of capital upfront. The fact that you are giving your product for free to the community doesn’t help the fact that you need capital to maintain, scale, and improve the database. Most vendors then differentiate between the free community version and the enterprise version that comes with a bill. It varies from vendor to vendor, but community and enterprise versions are usually identical products based on the same codebase. The difference is that the enterprise editions include more features, especially regarding security, and they allow for a bigger scale of data. &lt;/p&gt;

&lt;p&gt;So to decide what version of the graph database you need, you need to decide what features are important for your use case and do they come with a cost or not. In this blog post, the focus will be on Memgraph and Neo4j as potential vendors for your graph solution. Memgraph and Neo4j are compatible databases. They both use Bolt protocol and Cypher for queries, which means you can easily switch your infrastructure between them. Both vendors have community and enterprise editions of the database. Let’s see how they compare head to head. &lt;/p&gt;

&lt;h2&gt;
  
  
  Production-ready database
&lt;/h2&gt;

&lt;p&gt;To call a database production ready, it needs to have several core features. The first and most important feature is &lt;a href="https://en.wikipedia.org/wiki/ACID" rel="noopener noreferrer"&gt;ACID&lt;/a&gt; transactions, where ACID stands for atomicity, consistency, isolation, and durability. ACID transactions ensure that your database is executing queries properly. This means that your graph data won’t be compromised by incomplete transactions and left in a corrupted state. To avoid getting into extreme engineering details about each ACID property, this means your database has basic engineering prerequisites to be called a database. &lt;/p&gt;

&lt;p&gt;The other basic feature or prerequisite is &lt;strong&gt;persistence&lt;/strong&gt;. It ensures that your database is saving data and its current state to permanent memory storage, which means that losing power on your database server won’t lose any data. These two properties are not the only important ones, but each production-ready database needs to have them. &lt;/p&gt;

&lt;p&gt;Obviously, both Memgraph and Neo4j support ACID transactions. Neo4j being an on-disk database, is, by design, a persistent database. Memgraph is an in-memory graph database, so it does periodic snapshots that are stored in permanent memory, which enables persistence. &lt;/p&gt;

&lt;p&gt;After the database is capable of doing ACID transactions and being persistent, other features are highly dependent on the specific use case that you need, and those features will define if you need an enterprise or community version of the database for production. For a production environment, you should probably deploy an enterprise version of the database, right? Well, not really. It depends on what database features are included in the community versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memgraph and Noe4j community editions head to head
&lt;/h2&gt;

&lt;p&gt;Both Memgraph and Neo4j are open-source vendors that provide a lot of value to graph communities. Building great products and making them available to the community comes at the cost of operating expenses. That is why both vendors provide enterprise editions of their graph database. Enterprise editions usually contain most of the features that are needed for more scalable and demanding use cases. Both Memgraph and Neo4j come with their own set of enterprise features, which mainly focus on security, such as LDAP integration, activity auditing, role-based authorization etc.&lt;/p&gt;

&lt;p&gt;Obviously, using a community version of the database is cheaper for you. This can be crucial for small companies and startups that need a performant graph database but don’t have bucks for the enterprise edition. Below are several features that differentiate Memgraph and Neo4j community editions. Some of them might be very important for your use case, and some of them less important, but they are all very universal to any graph solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  High availability
&lt;/h3&gt;

&lt;p&gt;Running any software these days needs to provide some agreed-upon and universal availability claims, such as 99.99% uptime per year. These constraints and values can differ from project to project, but deploying any database in production without the supported features for &lt;strong&gt;high availability&lt;/strong&gt; is not recommended for critical infrastructure projects.&lt;/p&gt;

&lt;p&gt;There are several different ways to achieve high availability, one of them being &lt;strong&gt;replication&lt;/strong&gt;. Implementations of replication can be done in several different ways, but concepts and benefits are similar. Some of the benefits that come with replication are the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;high availability&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;server load balancing,&lt;/li&gt;
&lt;li&gt;data reliability,&lt;/li&gt;
&lt;li&gt;disaster recovery,&lt;/li&gt;
&lt;li&gt;lower query latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Database replication is the process in which the main database instance sends changes or updates to the database replicas. In usual configurations, each database instance is located on a different server. If, for any reason, the main database instance fails, another secondary backup server with a replica of the database will take care of the upcoming requests. There can be multiple replicas of the main instance, depending on the implementation and redundancy requirements. This also means that if multiple servers have a hardware failure, you will not lose your data. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3g2sx468cht75ipmd7ge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3g2sx468cht75ipmd7ge.png" alt="get-a-feature-rich-open-source-community-edition-graph-database-ready-for-production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Replication can be a bit of a complex topic that is out of the scope of this blog post. But the important thing is that running replication and syncing data between different servers leads to many benefits, one of them being high availability, that will ensure that your services are always up and running. This is especially important for critical infrastructure applications. &lt;/p&gt;

&lt;p&gt;This brings the discussion back to Memgraph and Neo4j community editions. Neo4j community edition does not support replication of the database. This feature and other features regarding multiple database instances are part of clustering features under the enterprise edition of Neo4j. If your data in a community edition is stored on a single instance of Neo4j, it is vulnerable to any type of server failure. &lt;a href="https://memgraph.com/blog/implementing-data-replication" rel="noopener noreferrer"&gt;Memgraph does support replication&lt;/a&gt;, and in the community edition, you can run replication on a cluster of Memgraph instances. &lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;One of the most important aspects of running any database system is &lt;strong&gt;performance&lt;/strong&gt;. It affects every graph project and application built on top of the database. You want your graph database to be performant as possible so you can build fast and reliable applications on top of it. Graph databases thrive on complex analytical workloads, so if you are building real-time streaming applications, performance is a must from the get-go. &lt;/p&gt;

&lt;p&gt;Memgraph community edition is quite performant as &lt;a href="https://memgraph.com/blog/memgraph-vs-neo4j-performance-benchmark-comparison" rel="noopener noreferrer"&gt;it can run up to 120 times than the Neo4j community edition&lt;/a&gt;. The key here is that Memgraph’s performance doesn’t differ between community and enterprise editions. If you run a small business and want a performant graph database out of the box, Memgraph’s community edition offers it for free. &lt;/p&gt;

&lt;p&gt;On the other hand, the situation with the Neo4j community edition is a bit more complex. As mentioned on their official website, the enterprise edition benefits from the faster Cypher, which should run queries from 50% to 100% faster on the enterprise edition. This means faster queries are not supported on the community edition of the database. &lt;/p&gt;

&lt;p&gt;Even though it’s an on-disk graph database, Neo4j is able to cache a lot of graph data to RAM. It does so to improve performance and avoid costly disk access. In order to benefit from that performance, you need to per-warm the database cache by executing various different queries and just giving it time. Without this pre-warm procedure, Neo4j will perform quite poorly. &lt;/p&gt;

&lt;p&gt;Once Neo4j is warmed, it should not be turned off or restarted since that will empty the cache. There is an automatic warm-up feature and automatic re-heat after a restart, but they are limited to the enterprise edition. In the community edition, you will need to crank it up manually to get some performance boost. &lt;/p&gt;

&lt;p&gt;Also, some Neo4j community versions have 4 CPU core limits, but it is unclear from the documentation which components are affected by these limitations. All in all, the Neo4j Community Edition restricts performance, while Memgraph offers full performance for both editions of the database. &lt;/p&gt;

&lt;h3&gt;
  
  
  Data science on graphs
&lt;/h3&gt;

&lt;p&gt;Since community editions are oriented towards small and medium-sized companies with humble R&amp;amp;D budgets, it’s useful to know that both Memgraph and Neo4j have a library of supported algorithms. Neo4j’s Graph Data Science library (GDS) and Memgraph Advanced Graph Extensions (MAGE). &lt;/p&gt;

&lt;p&gt;Both libraries are open-source and contain various graph algorithms such as PageRank, community detection algorithms, etc. These out-of-the-box solutions save users time in developing common algorithms. They are usually hard to understand and write.&lt;/p&gt;

&lt;p&gt;But, there are some differences regarding supported algorithms and how they are implemented. In the Neo4j community edition of GDS, you can execute algorithms on just 4 CPU cores. Enterprise edition lifts the CPU limit. On top of that, Neo4j GDS with enterprise license benefits from optimized graph implementations, which are not present in the community edition. With Memgraph MAGE, there are no CPU restrictions, and all algorithms perform in their optimized, native form without restrictions. &lt;/p&gt;

&lt;h3&gt;
  
  
  Property constraints
&lt;/h3&gt;

&lt;p&gt;One of the features you will probably need is &lt;strong&gt;property constraints&lt;/strong&gt; when creating nodes and relationships. This feature ensures the presence of a property. Having a node representing a person but without a name or last name properties doesn’t make much sense. In the community edition of Neo4j, you cannot create a property constraint on a node or relationship, it’s an enterprise feature. Memgraph’s community edition does not have that or similar Cypher restrictions. &lt;/p&gt;

&lt;p&gt;Memgraph and Neo4j have some cool and useful features in the community version. Not all features are present in both cases, so you should check your own set of requirements for the project. Mentioned features above are just some of the general features that could be universally important to any graph project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Cost of ownership
&lt;/h2&gt;

&lt;p&gt;Both databases are open-source community editions and are available to the public for free, so there is no direct cost to using them. But, what you need to pay for is hosting the databases in production. If you host a database on public VMs, the hosting cost will correlate with CPU time, memory, network, and storage usage. &lt;/p&gt;

&lt;p&gt;The bar chart below shows &lt;a href="https://memgraph.github.io/benchgraph/base?condition=cold&amp;amp;datasetSize=small&amp;amp;workloadType=realistic&amp;amp;querySelection=%5B%5D&amp;amp;tab=Global%20Results" rel="noopener noreferrer"&gt;Memgraph and Neo4j memory usage&lt;/a&gt; during the execution of 4 different types of workloads: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figrz1ngihfy9uxq62omu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figrz1ngihfy9uxq62omu.png" alt="get-a-feature-rich-open-source-community-edition-graph-database-ready-for-production" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though Memgraph is a high-performant in-memory database, in some scenarios, Neo4j will actually consume more memory. At these four mixed workloads, Neo4j is paying the price for being based on JVM, which can have a lot of memory overhead. As seen on the chart, Neo4j uses up to 2.2GB of memory, while Memgraph uses around 400MB for the identical task. Neo4j will allocate quite large amounts of memory and use it only partially for caching, which leads to buying more expensive cloud virtual machines and making hosting more expensive.&lt;/p&gt;

&lt;p&gt;Memory usage costs are highly correlated with replication. The downside of replication is the hosting costs of running multiple database replica instances. The hosting cost of every replica instance is identical to the costs of hosting the main instance, as they all require the same resources.  As you can see, in some situations, a single instance of Neo4j can have a higher hosting cost than Memgraph, therefor running multiple instances in replication will lead to much higher costs overall. This, of course, depends on several factors, how big the dataset is, how restrictive you are about RAM usage, what AWS instance is being used, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Having multiple choices for an open-source graph database is great for developers. When choosing which graph database to use, it comes down to supported features. As you can see, Memgraph community edition has &lt;a href="https://memgraph.com/blog/how-to-choose-a-graph-database-for-your-real-time-application?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost" rel="noopener noreferrer"&gt;stellar performance&lt;/a&gt;, supports replication to achieve high availability, and allows property constraints. Neo4j, on the other hand, is a bit more restrictive with the community edition and is less performant, but it is a company that paved the way for graph databases with a rich legacy which can sometimes be a two-sided coin.&lt;/p&gt;

&lt;p&gt;When the time comes to make a choice, keep in mind that available features are not the only important thing. You should also consider the cost of ownership, supported visualization tools, stellar documentation, customer support, supported languages, easy-to-use APIs, etc. Memgraph as a company was started because we couldn’t find a database that would address all these issues in a satisfactory way, starting from performance to other important features, which is why these are the issues we address with extra attention. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/memgraph-for-neo4j-developers/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0azgpsgm3wp9w5sd5wu.png" alt="Read more about Neo4j and Memgraph on memgraph.com" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gratitude</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How to Choose a Graph Database for Your Real-Time Application</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Tue, 20 Dec 2022 14:47:20 +0000</pubDate>
      <link>https://forem.com/memgraph/how-to-choose-a-graph-database-for-your-real-time-application-374</link>
      <guid>https://forem.com/memgraph/how-to-choose-a-graph-database-for-your-real-time-application-374</guid>
      <description>&lt;p&gt;Graph databases are a powerful tool to analyze high volumes of highly connected data. Alongside batch processing, graph databases can execute real-time analytics on streaming data by using graph algorithms and queries. But what does real-time even mean, and how it fits into the context of graph databases? &lt;/p&gt;

&lt;p&gt;Real-time software can differ widely from use case to use case. From Formula 1 real-time implementations with extremely high velocity to simple food delivery real-time alert systems. In general, real-time software is built with time requirements in mind and should be as responsive as possible. The exact definition of time requirement is defined by the use case. In F1, data is changing in milliseconds, which means tens of milliseconds are considered real-time, while in food delivery, events happen in seconds, which means seconds are considered real-time. It may not seem much of a difference from milliseconds to seconds, but from an engineering perspective, the difference is huge. Lowering time requirements under a second requires a lot of engineering effort. In general, a use case is considered to require real-time analytics if the changes need to be observed frequently, in most cases, in intervals under 1 second. &lt;/p&gt;

&lt;p&gt;Another important aspect of real-time software is the value of information which is at its highest the moment a certain event occurs and a change happens. The F1 team finds it important to notice an opportunity or issue as soon as possible, and in the food delivery case, you can plan your evening depending on the estimated food delivery time based on delivery updates. If users face latency, the information loses value and the real.time software becomes useless. Delivering important information within the right time limits, aka in real-time, on a huge scale of data can be a challenging engineering effort. &lt;/p&gt;

&lt;p&gt;Graph analytics that yields data-driven actions and recommendations can be a part of real-time systems and serve analytics to provide users with relevant information. As a bunch of different components will be built on top of that database, its impact on the whole chain should be as minimal as possible. &lt;/p&gt;

&lt;p&gt;There are several graph databases to choose from, and this blog post will consider Memgraph and Neo4j as possible vendors for a real-time solution. These graph databases were chosen since they both have great interoperability. First, let’s see what metrics define what graph database could be a good fit for real-time use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time solutions require low latency and high throughput
&lt;/h2&gt;

&lt;p&gt;Real-time software infrastructure needs to be able to serve a bunch of analytical dashboard applications and front-facing user applications under time-defined time restrictions. This means application infrastructure must have the &lt;strong&gt;lowest possible latency&lt;/strong&gt; because unwanted latencies can creep in as data volume, number of clients or workload increases. This means infrastructure should be optimized for the worst-case scenario from the start of the project development. Latency value represents the ability of the software infrastructure to respond to certain types of events or requests within the defined time limit or lower. &lt;/p&gt;

&lt;p&gt;Latency can be introduced in many different components of infrastructure. The first and most important latency measurement is &lt;strong&gt;end-to-end latency&lt;/strong&gt;. It represents the amount of time it takes for an event or request to be sent from the user and for a response to be received by the user. End-to-end latency is important because the time frame in which the user sends a request and receives an answer defines the value of a real-time product.&lt;/p&gt;

&lt;p&gt;In real-time applications, end-to-end latency has an available &lt;strong&gt;time budget&lt;/strong&gt; of less than a second, and it must be allocated very delicately. This means all components in the software infrastructure participating in the user request must take less than a second. This can include frontend latency, network latency, application latency, database latency etc., and it’s different with each use case. Each mentioned component can be broken down into several smaller subcomponents, which show the exact latency of each subcomponent. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YoWaQ7P8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/end-to-end-latency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YoWaQ7P8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/end-to-end-latency.png" alt="end-to-end-latency" width="880" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the components crucial for real-time analytical applications is &lt;strong&gt;database query latency&lt;/strong&gt;, and querying large volumes of connected data can present technical challenges. &lt;/p&gt;

&lt;p&gt;Let’s consider this example. A real-time application has an available time budget of 500 milliseconds. The total database query latency can accumulate just a fraction of it, let’s assume 10% or 50ms. That’s the worst-case upper limit. It would be perfect if the database could consume even less. If queries would take just 10ms, the rest of the end-to-end latency budget could be used by the other parts of the system or remain unallocated, resulting in more responsive infrastructure. Breaking the upper limit would mean the service is not functioning properly.&lt;/p&gt;

&lt;p&gt;Even though the query latency should be low and under budget, it is not the only metric that indicates database performance. Measuring query latency on several queries without a heavy load on the database can give good latency results but poor scalability. This means the database is fast on serving one or a few users simultaneously. So how do we measure scalability?&lt;/p&gt;

&lt;p&gt;The measurement that represents scalability is &lt;strong&gt;throughput&lt;/strong&gt;. Throughput defines how many requests the database can serve per unit of time. Usually, it’s represented as queries per second (QPS). Simply speaking, throughput will represent how many user requests the database can handle per unit of time. If the database has more throughput, this means it can serve more users. Lower latency is usually correlated with high throughput, but it can also vary a lot due to different database implementations, but we'll discuss that in another post. That’s one of the reasons why it is important to measure throughput concurrently because real-time applications need to serve many users while preserving both latency and throughput. &lt;/p&gt;

&lt;h2&gt;
  
  
  Who is better for real-time software - Memgraph or Neo4j?
&lt;/h2&gt;

&lt;p&gt;Both Memgraph and Neo4j are graph database vendors that you can use for graph analytics, but the question is - what vendor is a better fit for your real-time use case? &lt;/p&gt;

&lt;p&gt;Memgraph and Neo4j are quite similar from an interoperability perspective - both databases use Bolt protocol and Cypher query language. But from the implementation perspective, Memgraph and Neo4j are architecturally completely different systems. What distinguishes them the most is that Neo4j is based on JVM, while Memgraph is written in native C++. To understand the performance limits of each vendor, we have been benchmarking &lt;strong&gt;Memgraph 2.4&lt;/strong&gt; and &lt;strong&gt;Neo4j 5.1 community editions&lt;/strong&gt;. For a full report on all differences and how we executed this test, you can take a look at &lt;a href="https://memgraph.com/blog/memgraph-vs-neo4j-performance-benchmark-comparison?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;the performance comparison&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As mentioned, the most important metrics for real-time software are &lt;strong&gt;latency&lt;/strong&gt; and &lt;strong&gt;throughput&lt;/strong&gt;. It’s important to notice that latency is single-threaded, while throughput is calculated as the workload of 12 concurrent clients querying the database. The current benchmark configuration consists of 23 queries, each being of a different type - read, write, update, aggregate and analytical. &lt;/p&gt;

&lt;h2&gt;
  
  
  Latency
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at latency results in milliseconds for each of the queries: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U3NKRq6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/latency-table.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U3NKRq6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/latency-table.png" alt="memgraph-vs-neo4j-latency-table" width="880" height="1072"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you take a look at the last column, it shows how much faster Memgraph is at this specific query. By looking at the whole table, you can notice that Memgraph is faster across the board. The absolute values of latency across the whole table show that most of the queries executed with Memgraph took from 1.09 to 11.32 milliseconds with few exceptions. Outliers are queries Q9 to Q12, which are complex queries touching most of the dataset’s nodes and edges. With Neo4j, the queries were executed from 13 milliseconds to 135 milliseconds without the mentioned outliers. &lt;/p&gt;

&lt;p&gt;Absolute query latency values on both vendors seem pretty low, but if the total end-to-end latency request budget is under 500 milliseconds and the database latency budget is 50 ms, which is a fair amount of time for real-time software, any query that is executed longer than that is out of budget and not a valid query for this real-time use case. For example, 84 milliseconds query is out of budget and not a valid query for a real-time use case. Just a side note, Google search results return values in approximately 400-700 milliseconds, you want your service to try to be responsive as Google search is. &lt;/p&gt;

&lt;p&gt;One of the most interesting queries in any graph analytical workload is the expansion or K-hop queries. Expansion queries start from the target node and return all the connected nodes that are a defined number of hops away. Expansion queries are data intensive and pose a challenge to databases. Probably the most used expansion query is the one with a single hop. It’s an analytical query that is fairly cheap to execute and used a lot. This is query Q5 in the table: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o2VNPdC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-latency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o2VNPdC---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-latency.png" alt="memgraph-vs-neo4j-latency" width="880" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This specific query takes Memgraph 1.09 milliseconds to execute, while it takes Neo4j 27.96 milliseconds. If the time budget for database latency is 50 milliseconds, this means Memgraph is consuming 2.18% of the budget while Neo4j is consuming 55% of the budget.&lt;/p&gt;

&lt;p&gt;Now let’s take a look at Q7, which is an expansion 2 query. It takes Memgraph 6.10 milliseconds to execute it and Neo4j 88 milliseconds. To execute this query, Memgraph uses 12.2% percent of the available time budget, while Neo4j uses 176% of the available time budget. Memgraph easily stays within the budget of 50ms. This is just an approximation of the possible budget for queries, of course, the budget depends on the real-time use case. But regardless of your budget, Memgraph will provide more space for future latency improvements. &lt;/p&gt;

&lt;h2&gt;
  
  
  Throughput
&lt;/h2&gt;

&lt;p&gt;Obviously, completing a request under the budget is the best-case scenario, but this is just a part of the picture. The database should be able to serve multiple concurrent users at the same time. If there are thousands of users using the application at the same time, the database should perform similarly as in the case of a single user. This is where throughput comes into play. By measuring concurrent throughput, we can estimate how much of a particular query database can serve in a 1-second time frame. Here is the throughput for 4 different workloads. Each workload consists of multiple queries of read, write, update, and analytical types. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f1Wdrvla--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-throughput.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f1Wdrvla--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-throughput.png" alt="memgraph-vs-neo4j-throughput" width="880" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On workload W1, which is made of 20% analytical, 40% read, 10% update, and 30% write queries, Memgraph can handle 3.889 queries per second, while Neo4j can handle 37 queries per second. Handling a higher number of clients that are creating a request in the same time frame shows how capable the database is in handling bigger volumes of data in short bursts. This means that latency is not impacted by more concurrent clients, and performance is in line with latency values. As you can see on, Memgraph has a much higher throughput. This means that Memgraph is more scalable and can handle much faster large volumes of data needed for a real-time graph database. &lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits and cost implications
&lt;/h2&gt;

&lt;p&gt;Running all this software doesn’t cost much since both tested databases are open-source community editions and are available to the public for free, there is no direct cost to use the database. Of course, each vendor has its own enterprise editions of the database that you can pay for, but that is out of the scope of this blog post. &lt;/p&gt;

&lt;p&gt;What you need to pay for is hosting these databases in production, and that can lead to hosting expenses. The hosting cost will correlate with CPU time, memory and storage usage. Take a look at the chart below to see memory usage for mentioned workloads: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NOJjGPvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-memory-usage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NOJjGPvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/how-to-choose-a-graph-database-for-your-real-time-application/memgraph-vs-neo4j-memory-usage.png" alt="memgraph-vs-neo4j-memory-usage.png" width="880" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though Memgraph is a high-performant in-memory database, in some scenarios, Neo4j will actually consume more memory. At these four mixed workloads, Neo4j is paying the price for being based on JVM, which can have a lot of memory overhead. As seen on the chart, Neo4j uses up to 2.2GB of memory, while Memgraph uses around 400MB for the identical task. Neo4j will allocate quite large amounts of memory and use it only partially for caching, which leads to buying more expensive cloud virtual machines and making hosting more expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Low latency and high throughput are key metrics when deciding whether a graph database can be considered capable of delivering real-time graph analytics. As &lt;a href="https://memgraph.com/benchgraph/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost"&gt;benchmarking&lt;/a&gt; results show, Memgraph is quite a capable graph database, while Neo4j is also a capable graph database, just a few gears slower and a bit memory hungry in some scenarios. We would love to see what real-time application you want to develop with Memgraph. Take Memgraph for a test drive, check out our documentation to understand the nuts and bolts of how it works, or read more blogs to see a plethora of use cases using Memgraph. If you have any questions for our engineers or want to interact with other Memgraph users, join the Memgraph Community on &lt;a href="https://discord.com/invite/memgraph"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/memgraph-for-neo4j-developers/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NWKqzkwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://public-assets.memgraph.com/external/memgraph-read-more-gradient-1200.png" alt="Read more about Neo4j and Memgraph on memgraph.com" width="880" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>memgraph</category>
      <category>neo4j</category>
      <category>graphdatabase</category>
    </item>
    <item>
      <title>Memgraph vs. Neo4j: A Performance Comparison</title>
      <dc:creator>Ante Javor</dc:creator>
      <pubDate>Thu, 01 Dec 2022 13:24:13 +0000</pubDate>
      <link>https://forem.com/memgraph/memgraph-vs-neo4j-a-performance-comparison-2kkn</link>
      <guid>https://forem.com/memgraph/memgraph-vs-neo4j-a-performance-comparison-2kkn</guid>
      <description>&lt;p&gt;Over the past few weeks, we have been &lt;a href="https://memgraph.com/benchgraph/" rel="noopener noreferrer"&gt;benchmarking&lt;/a&gt; &lt;strong&gt;Memgraph 2.4&lt;/strong&gt; and &lt;strong&gt;Neo4j 5.1 community editions&lt;/strong&gt;. Graph databases with similar capabilities, but architecturally completely different systems. What distinguishes them the most is that Neo4j is based on the JVM, while Memgraph is written in native C++. &lt;br&gt;
To understand both databases' performance limits, we executed a wide variety of concurrent Cypher queries in different combinations. &lt;/p&gt;

&lt;p&gt;If you prefer a TL;DR, here it is: Memgraph is approximately &lt;strong&gt;120 times faster&lt;/strong&gt; than Neo4j, all while consuming one quarter of the memory and providing snapshot isolation instead of Neo4j’s default of read-committed.&lt;/p&gt;

&lt;p&gt;Benchmarking is &lt;a href="https://users.cs.northwestern.edu/~robby/courses/322-2013-spring/mytkowicz-wrong-data.pdf" rel="noopener noreferrer"&gt;subtle&lt;/a&gt; and must be performed carefully. So, if you are interested in the nuts and bolts of it, see the full &lt;a href="https://memgraph.com/benchgraph/" rel="noopener noreferrer"&gt;benchmark results&lt;/a&gt; or dive straight into the &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench#fire-mgbench-benchmark-for-graph-databases" rel="noopener noreferrer"&gt;methodology&lt;/a&gt; or read on for the summary of results between Memgraph and Neo4j. &lt;/p&gt;
&lt;h2&gt;
  
  
  Benchmark Overview
&lt;/h2&gt;

&lt;p&gt;In its early stages, mgBench was developed to test and maintain Memgraph performance. Upon changing Memgraph’s code, a performance test is run on CI/CD infrastructure to ensure no degradation. Since we already had a full testing infrastructure in place, we thought it would be interesting to run a performance test on various graph databases. Due to its compatibility, Neo4j was first on the integration list. &lt;/p&gt;

&lt;p&gt;Benchmarks are hard to create and often biased, so it’s good practice to understand the benchmark’s main goals and technical details before diving deeper into the results. The primary goal of the benchmark is to measure the performance of any database “out of the box”, that is, without fine-tuning the database. Configuring databases can introduce bias, and we wanted to do a fair comparison. &lt;/p&gt;

&lt;p&gt;To run benchmarks, tested systems needed to support the Bolt protocol and the Cypher query language. So, both databases were queried over the Bolt protocol using a low-overhead C++ client, stabilizing and minimizing the measurement overhead. The shared Cypher query language enables executing the same queries on both databases, but as mgBench evolves and more vendors are added to the benchmark, this requirement will be modified. &lt;/p&gt;

&lt;p&gt;Performance and memory tests can be run by executing three different workloads: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolated&lt;/strong&gt; - Concurrent execution of a single type of query. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed&lt;/strong&gt; - Concurrent execution of a single type of query mixed with a certain percentage of queries from a designated query group.. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realistic&lt;/strong&gt; - Concurrent execution of queries from write, read, update and analyze groups. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our test, all benchmark workloads were executed on a mid-range HP DL360 G6 server, with a 2 x Intel Xeon X5650 6C12T @ 2.67GHz and 144 GB of 1333 MHz DDR3 memory, running Debian 4.19.&lt;/p&gt;

&lt;p&gt;Each workload was executed with 12 concurrent clients querying the database. All results were acquired in both cold and hot run. When performing a hot run, the database was pre-queried before executing any benchmark queries and taking measurements. &lt;/p&gt;

&lt;p&gt;To get the full scope of the benchmark’s technical details, take a look at our &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench#fire-mgbench-benchmark-for-graph-databases" rel="noopener noreferrer"&gt;methodology&lt;/a&gt;. It contains details such as queries performed, the benchmark’s limitations and plans for the future. You can also find reproduction steps to validate the results independently.&lt;/p&gt;
&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The whole benchmark executes 23 representative workloads, each consisting of a write, read, update, aggregate or analytical query. Write, read, update, and aggregate queries are fairly simple and deal with nodes and edges properties, while analytical queries are more complex. &lt;/p&gt;

&lt;p&gt;The key values mgBench measures are &lt;strong&gt;latency&lt;/strong&gt;, &lt;strong&gt;throughput&lt;/strong&gt; and &lt;strong&gt;memory&lt;/strong&gt;. Each of these measurements is vital when deciding what database to use for a certain use case. The results shown below have been acquired by executing queries on a small dataset on a cold run.&lt;/p&gt;
&lt;h2&gt;
  
  
  Latency
&lt;/h2&gt;

&lt;p&gt;Latency is easy to measure and is therefore included in almost every benchmark. It’s a base performance metric representing how long a query takes to execute in milliseconds. Query complexity presents an important factor in absolute values of query latency. Therefore, executing complex analytical queries will take more time and latency will be higher, while more straightforward queries will execute faster, thus having lower latency.&lt;/p&gt;

&lt;p&gt;The latency of the same queries can vary due to query caching. It is expected that running the same query for the first time will result in a higher latency than on the second run. Because of the variable latency, it is necessary to execute a query several times to approximate its latency better. We pay particular attention to the 99th percentile, representing the latency measurement that 99% of all measurements are faster than. This might sound as though we are focusing on outliers, but in practice &lt;a href="https://research.google/pubs/pub40801/" rel="noopener noreferrer"&gt;it is vital to understand the “tail latency” for any system that you will build reliable systems on top of&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the most interesting queries in any graph analytical workload is the expansion or K-hop queries. Expansion queries start from the target node and return all the connected nodes that are a defined number of hops away. Depending on the dataset, expanding several hops away from the target will probably cause the query to touch most of the nodes in the dataset. Expansion queries are data intensive and pose a challenge to databases. In mgBench, there are expansion queries with hops from 1 to 4. Probably the most used expansion query is the one with a single hop. It is an analytical query that is fairly cheap to execute and used a lot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;n.id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FLatency%2520%252899th%2520percentile%2C%2520small%2520dataset%2C%2520cold%2520run%2529.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FLatency%2520%252899th%2520percentile%2C%2520small%2520dataset%2C%2520cold%2520run%2529.png" alt="memgraph-vs-neo4j-a-performance-comparison-latency-99th-percentile"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the bar chart above shows, it takes Memgraph 1.09 milliseconds to execute Expansion 1 query, while it takes Neo4j 27.96 milliseconds. On this particular query, &lt;strong&gt;Memgraph is 25 times faster&lt;/strong&gt;. But this is just one sample on a single query. To get a complete picture of latency performance, here is the latency measured across 23 different queries. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FLatency_summary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FLatency_summary.png" alt="memgraph-vs-neo4j-a-performance-comparison-latency-summary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Memgraph latency is multiple times lower compared to Neo4j on each of the 23 queries. A full &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench#query-list" rel="noopener noreferrer"&gt;query list&lt;/a&gt; is available in the methodology section.  In Memgraph, latency ranges from 1.07 milliseconds to one second on a Q11, Expansion 4 query, the most challenging one in mgBench. Neo4j ranges from 13.73 milliseconds to 3.1 seconds. Lower query latency across the board brings a great head start, but it is not the complete picture of the performance difference between databases. &lt;/p&gt;

&lt;p&gt;Latency is just an end result that can depend on various things, such as query complexity, database workload, query cache, dataset, query,  etc. The database needs to be tested under concurrent load to understand the database performance limits since it imitates the production environment better. &lt;/p&gt;

&lt;h2&gt;
  
  
  Throughput
&lt;/h2&gt;

&lt;p&gt;Throughput represents how many queries you can execute per fixed time frame, expressed in queries per second (QPS). To measure throughput on an isolated workload, each of the 23 queries is executed in isolation concurrently, a fixed number of times, and divided by the total duration of execution. The number of queries executed depends on query latency. If latency is low, more queries are executed. If latency is high, fewer queries are executed. In this case, the number of executed queries is equivalent to an approximation of 10 seconds worth of single-threaded workload. &lt;/p&gt;

&lt;p&gt;More throughput means the database can handle more workload. Let's look at the results of the Expansion 1 query mentioned earlier: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FThroughput_expansion.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FThroughput_expansion.png" alt="memgraph-vs-neo4j-a-performance-comparison-throughput-expansion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memgraph has a concurrent throughput of 32,028 queries per second on Expansion 1, while Neo4j can handle 280 queries per second, which means &lt;strong&gt;Memgraph is 114 times faster than Neo4j&lt;/strong&gt; on this query. But again, this is throughput for just one query. Let's look at all 23 queries in an isolated workload: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FThroughput_summary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FThroughput_summary.png" alt="memgraph-vs-neo4j-a-performance-comparison-throughput-summary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see on the bar chart above, Memgraph has significantly higher throughput on each of the 23 queries. Since the difference in throughput is huge for some queries, it is best to focus on the absolute number for each query. The worst case scenario for Memgraph and the best case scenario for Neo4j is Q4, where &lt;strong&gt;Memgraph is “just” 5.1 times faster than Neo4j&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Database latency and throughput can be significantly influenced by result caching. Executing identical queries several times in a row can yield a superficial latency and throughput. In the production environment, the database is constantly executing write and read queries. Writing into a database can trigger the invalidation of the cache. Writing can become a bottleneck for the database and deteriorate latency and throughput. &lt;/p&gt;

&lt;p&gt;Measuring throughput while writing into the database will yield a more accurate result that reflects realistic caching behavior while serving &lt;strong&gt;mixed workloads&lt;/strong&gt;. To simulate that scenario, a mixed workload executes a fixed number of queries that read, update, aggregate or analyze the data concurrently with a certain percentage of write queries.&lt;/p&gt;

&lt;p&gt;The Expansion 1 query executed before is now concurrently executed mixed with 30% of write queries:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMixed_expansion.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMixed_expansion.png" alt="memgraph-vs-neo4j-a-performance-comparison-mixed-expansion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Memgraph has maintained its performance in a mixed workload and has a &lt;strong&gt;132 times higher throughput&lt;/strong&gt; executing a mixed workload than Neo4j. Here are the full throughput results for the mixed workload: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMixed_summary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMixed_summary.png" alt="memgraph-vs-neo4j-a-performance-comparison-mixed-summary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The total mixed workload didn’t impact overall Memgraph's performance, it is still performing very well on each of the 21 queries. Two write queries used for creating mixed workload are not tested, since it would not be mixed workload. &lt;/p&gt;

&lt;p&gt;Both isolated and mixed workloads have given a glimpse of the possible production performance of the database, while each of the previous measurements can have limitations. &lt;strong&gt;Realistic workload&lt;/strong&gt; is the most challenging test of database performance. Most of the databases in production execute a wide range of queries that differ in complexity, order, and type. The realistic workload consists of writing, reading, updating, and doing analytical work concurrently on the database. The workload is defined by the percentage of each operation. Each workload is generated non-randomly for each vendor and is identical on each run. &lt;br&gt;
Currently, there are 4 realistic workload distributions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;W1 - 20% Analytical, 40% Read, 10% Update and 30% Write&lt;/li&gt;
&lt;li&gt;W2 - 70 % Read and 30 % Write&lt;/li&gt;
&lt;li&gt;W3 - 50 % Read and 50 % Write&lt;/li&gt;
&lt;li&gt;W4 - 30 % Read and 70 % Write&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take a look at the throughput results for each workload: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FRealistic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FRealistic.png" alt="memgraph-vs-neo4j-a-performance-comparison-realistic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memgraph performs well on the mixed workload, with 102 times higher throughput on W1, 122 times higher on W2, 125 times higher on W3, and 121 times higher on W4. Overall, Memgraph is approximately &lt;strong&gt;120 times faster&lt;/strong&gt; than Neo4j. &lt;/p&gt;

&lt;h2&gt;
  
  
  Memory usage
&lt;/h2&gt;

&lt;p&gt;The performance of the database is one of many factors that define which database is appropriate for which use case. However, &lt;strong&gt;memory usage&lt;/strong&gt; is one of the factors that can really make a difference in choosing a database, as it significantly impacts the cost-to-performance ratio. Memory usage in mgBench is calculated as &lt;strong&gt;peak RES&lt;/strong&gt; (resident size) memory for each query or workload execution. The result includes starting the database, executing the query or workload, and stopping the database. The peak RES is extracted from process PID as VmHVM (peak resident set size) before the process is stopped. Peak memory usage defines the worst-case scenario for a given query or workload, while RAM footprint is lower on average. &lt;br&gt;
Since Memgraph is an in-memory graph database, some cost must be attached to that performance. Well, this is where things get interesting. Take a look at the memory usage of realistic workloads: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMemory%2520usage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpublic-assets.memgraph.com%2Fmemgraph-vs-neo4j-a-performance-comparison%2FMemory%2520usage.png" alt="memgraph-vs-neo4j-a-performance-comparison-memory-usage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, &lt;strong&gt;Memgraph uses approximately a quarter of the Neo4j memory&lt;/strong&gt;. Since Neo4j is JVM  based, it is paying the price for the JVM overhead. &lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency
&lt;/h2&gt;

&lt;p&gt;While it is beyond the scope of this blog post to cover &lt;a href="https://github.com/ept/hermitage" rel="noopener noreferrer"&gt;the implications of database isolation levels&lt;/a&gt; and how weaker isolation levels prevent broad classes of applications from being correctly built on top of them at all, the high-level picture is that Memgraph supports snapshot isolation out of the box, while Neo4j provides a much weaker read-committed isolation level by default. Neo4j does support optional manual locking, but this does not protect all queries by default. In general, it is not practical for most engineers to reason about the subtleties of transaction concurrency control by requiring them to implement their own database locking protocol on top of the application that they are responsible for building and operating.&lt;/p&gt;

&lt;p&gt;In practice, this means that a far broader class of applications may be correctly implemented on top of Memgraph out-of-the-box without requiring engineers to understand highly subtle concurrency control semantics. The modern database ecosystem includes many examples of systems that perform extremely well without giving up correctness guarantees from a stronger isolation level, and Memgraph demonstrates that it is simply not necessary to give up the benefits of Snapshot Isolation while achieving great performance for the workloads that we specialize in. In the future we intend to push our correctness guarantees even farther. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As the benchmark shows, Memgraph is performing an &lt;strong&gt;order of magnitudes faster in concurrent workload than Neo4j&lt;/strong&gt;, which can be crucial for running any real-time analytics that relies on getting the correct information exactly when needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/benchgraph/" rel="noopener noreferrer"&gt;Benchmark can be found here&lt;/a&gt; and all the code used for running these benchmarks is publicly available so if you want to reproduce and validate the results by yourself, you can do it by following the instructions from the &lt;a href="https://github.com/memgraph/memgraph/tree/master/tests/mgbench#wrench-mgbenchter/tests/mgbench#reproducibility-and-validation" rel="noopener noreferrer"&gt;methodology&lt;/a&gt;. Otherwise, we would love to see what you could do with Memgraph. Take it for a &lt;a href="https://memgraph.com/download" rel="noopener noreferrer"&gt;test drive&lt;/a&gt;, check out our &lt;a href="https://memgraph.com/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to understand the nuts and bolts of how it works or read more &lt;a href="https://memgraph.com/blog?topics=Use+Cases" rel="noopener noreferrer"&gt;blogs&lt;/a&gt; to see a plethora of use cases using Memgraph. If you have any questions for our engineers or want to interact with other Memgraph users, join the &lt;a href="https://discord.gg/memgraph" rel="noopener noreferrer"&gt;Memgraph Community&lt;/a&gt; on Discord!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://memgraph.com/memgraph-for-neo4j-developers/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_repost&amp;amp;utm_content=banner" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0azgpsgm3wp9w5sd5wu.png" alt="Read more about Neo4j and Memgraph on memgraph.com"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>benchmark</category>
      <category>memgraph</category>
      <category>neo4j</category>
    </item>
  </channel>
</rss>
