Neo4j over bolt 协议具有非常高的延迟

Neo4j over bolt protocol has very high latency

我将 Neo4j 用于一个项目,该项目使用此处提供的适用于 .NET 的官方 Neo4j 驱动程序:

https://www.nuget.org/packages/Neo4j.Driver

这个驱动程序在 bolt 协议上工作,我的假设是一个专门的二进制协议比 HTTP API 更有效。但是自项目开始以来,我注意到 Neo4j 的延迟相对较高,即使是非常简单的操作也是如此。例如当 UserID 是索引字段并且数据库完全为空时,如下所示的匹配需要 30-60 毫秒:

match(n:User { UserID: 1 }) return n.UserID

这种行为在我的本地机器(几乎为零网络开销)和我们的生产环境中都会发生。我今天开始调查这个,发现查询 returns 很快,但实际流进结果需要很长时间。例如,下面的查询在本地主机上调用 returns 之前需要 0.2ms,然后在 result 上调用 ToArray()(缓冲记录,这在这种情况下是单个整数字段)将时间增加到 60ms

using (var driver = GraphDatabase.Driver($"bolt://localhost:7687", AuthTokens.Basic("neo4j", "1")))
{    
    using (var session = driver.Session())
    {
        // 0.2ms to return from this call
        var result = session.Run("match(n:User { ID: 1}) return n.ID"); 

        // Uncommenting this makes the whole thing take 60ms
        // result.ToArray(); 
    }
}

然后我尝试了社区赞助的 Neo4jClient 包,它通过 HTTP 工作:

https://github.com/Readify/Neo4jClient

使用相同的查询,总时间减少到仅 0.5 毫秒:

var client = new GraphClient(new Uri("http://localhost:7474/db/data"), "neo4j", "1");
client.Connect();

client.Cypher.Match("(n:User { ID: 1})").Return<int>("n.ID").Results.ToArray();

运行一个比较官方的benchmark给出了如下结果,bolt-driven的官方驱动和基于HTTP的Neo4jClient差异巨大。

Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4770 CPU 3.40GHz, ProcessorCount=8
Frequency=3312642 ticks, Resolution=301.8739 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1586.0

Type=Neo4jBenchmarks  Mode=Throughput  Platform=X64  
Jit=RyuJit  

      Method |         Median |      StdDev | Scaled | Scaled-SD |
------------- |--------------- |------------ |------- |---------- |
  Neo4jClient |    382.5675 us |   3.3771 us |   1.00 |      0.00 |
Neo4jSession | 61,299.9382 us | 690.1626 us | 160.02 |      2.24 |

因此,当网络开销可以忽略不计时,HTTP 客户端快 160 倍

我还 运行 我们生产环境的基准测试,虽然差别不大,但 HTTP 方法仍然快 6 倍(而且我的生产网络连接速度非常慢)。

完整的基准代码:

public class Neo4jBenchmarks
{
    private readonly IDriver _driver;
    private readonly GraphClient _client;

    public Neo4jBenchmarks()
    {
      _driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "1"));
      _client = new GraphClient(new Uri("http://localhost:7474/db/data"), "neo4j", "1");
      _client.Connect();
    }

    [Benchmark(Baseline = true)]
    public void Neo4jClient()
    {
      _client.Cypher.Match("(n:User { ID: 1})").Return<int>("n.ID").Results.ToArray();
    }

    [Benchmark]
    public void Neo4jSession()
    {
      using (var session = _driver.Session())
      {
        session.Run("match(n:User { ID: 1}) return n.ID").ToArray();
      }
    }
}

我的机器和产品都是 运行 Neo4j CE 3.0.4(目前是社区版),虽然我 运行 它在 Windows 10 上并且生产是 Linux机器。据我所知,我们没有调整任何设置,但我怀疑这能否解释 160 倍的性能差异。

我还尝试重用会话对象(我认为这是一个非常糟糕的主意,因为它不是线程安全的)因为创建会话涉及创建一个 t运行saction,以查看是否创建了一个有区别,但不明显。

我希望我可以使用 Neo4jClient,但我们确实需要执行任意字符串查询的能力,而 Neo4jClient 在很大程度上依赖于流畅的 API,虽然它提供了低级字符串模式,但它弃用和 actively discouraged in the documentation.

进一步挖掘后,我将问题具体追溯到 Neo4j.Driver 包,因为 NodeJS 的驱动程序没有遇到同样的问题。

克隆包的当前 source,构建它并直接引用 DLL 而不是 NuGet 包,完全解决了这个问题。换个角度来看:NuGet (1.0.2) 上的当前版本需要 62 秒 才能对本地主机执行 1000 个简单匹配请求,而当前源在 0.3 秒(甚至比 NodeJS 驱动程序快 10 倍)。

我不太清楚为什么,但我很确定它与当前包的 rda.SocketsForPCL 依赖关系有关,它似乎是一个胶水库,可以使套接字跨-平台。但是,当前源为此引用了 System.Net.Sockets 包。

总而言之,这个问题可以通过引用源的当前版本来解决,并将在发布新版本的包时完全解决。