Node 在连接到 Postgres 方面比 .NET Core 快 20 倍

Node is 20x faster Than .NET Core in Connecting to Postgres

我有两台服务器连接到托管在 Azure 上的 PostgresSQL 9.6 数据库。服务器正在做一件事 - 每 5 秒用 SELECT 1 查询访问 Postgres 数据库。

连接数据库和获取数据的典型时间:

我的问题是我的 .NET Core 应用程序在获取数据方面比 Node 慢 20 倍。我 相信 .NET Core 出于某种原因没有合并连接。在本地 运行 应用程序和在 Azure 应用程序服务上 运行 应用程序时都会出现这种缓慢 - 没有区别。 我想解决 .NET --> Postgres 运行缓慢的问题。

请只浏览相关细节,不要阅读整个内容 - 我相信只有 .NET Core 代码是相关的。

A PsPing 从我的机器到数据库(Node.NET Core 应用程序都在 运行ning:

Connecting to foobarPostGres:5432 (warmup): from someIp: 19.98ms
Connecting to foobarPostGres:5432: from someIp: 1.65ms
Connecting to foobarPostGres:5432 from someIp: 1.18ms
Connecting to foobarPostGres:5432: from someIp: 1.23ms
Connecting to foobarPostGres:5432: from someIp: 1.06ms

为了完整起见,NODE次的样本如下所示(注意第一次建立连接也是"slow"):

Attempting to establish a connection...
Elapsed ms:  644.1334999799728
RESP:  { '?column?': 1 }
Elapsed ms:  22.76109904050827
RESP:  { '?column?': 1 }
Elapsed ms:  21.984400033950806
RESP:  { '?column?': 1 }
Elapsed ms:  26.043799996376038
RESP:  { '?column?': 1 }
Elapsed ms:  22.538798987865448
RESP:  { '?column?': 1 }

.NET Core 的连接时间如下所示:

5:13:32 PM: SLOW QUERY, CONN TIME: 4153, QUERY TIME: 18 
5:13:53 PM: SLOW QUERY, CONN TIME: 707, QUERY TIME: 17 
5:14:14 PM: SLOW QUERY, CONN TIME: 589, QUERY TIME: 16
5:14:35 PM: SLOW QUERY, CONN TIME: 663, QUERY TIME: 18 
5:14:56 PM: SLOW QUERY, CONN TIME: 705, QUERY TIME: 16 

注意超慢的初始连接时间和长时间建立后续请求的连接。

无论如何,因为我很绝望,所以我现在要转储所有代码,并附上解释。连接字符串如下所示:

public static string CONNECTION_STRING {
  get {
    return $"Server={HOST}; User Id={USER}; Database={DB_NAME}; Port={PORT}; Password={PWD}; SSLMode=Prefer";
  }
}

据我了解,如果我使用此连接字符串,我应该开箱即用连接池。请注意,我已尝试在数据库上打开 SSL 并删除该行 - 它没有帮助。

我的健康检查控制器是这样的:

// GET api/health/getdbhealthselectone
[HttpGet]
[Route("getdbhealthselectone")]
public async Task<IActionResult> GetDbHealthSelectOne()
{
  int testData = await _healthCheckRepo.RunHealthCheckSelectOne();
  return Ok(testData);
}

我的健康检查回购方法如下所示:

 public async Task<int> RunHealthCheckSelectOne()
    {

      await using var conn = new NpgsqlConnection(AzureDbConnectionInfo.CONNECTION_STRING);

      var connTimer = System.Diagnostics.Stopwatch.StartNew(); // TODO: Remove this testing line
      await conn.OpenAsync();
      connTimer.Stop(); // TODO: Remove this testing line
      var msToConnect = connTimer.ElapsedMilliseconds; // TODO: Remove this testing line

      int testData = 999;
      var jobsQueryTimer = System.Diagnostics.Stopwatch.StartNew(); // TODO: Remove this testing line0
      await using (var cmd = new NpgsqlCommand("SELECT 1", conn))
      await using (var reader = await cmd.ExecuteReaderAsync())
      while (await reader.ReadAsync()) {
        testData = reader.GetInt32(0);
      };

      jobsQueryTimer.Stop(); // TODO: Remove this testing line
      var msToQuery = jobsQueryTimer.ElapsedMilliseconds; // TODO: Remove this testing line

      LogQueryIfSlow(msToConnect, msToQuery, _logger); // TODO: Remove this testing line

      return testData;
    }

注意这里的计时器 - await conn.OpenAsync(); 是目前为止花费大部分时间的原因,查询本身很快。另外,为了节省时间 - 我之前 运行 这段代码没有 async,没有区别。

最后,如果存在依赖注入问题,存储库位于 class 库中,API 项目引用它,并且:

services.AddSingleton<IHealthCheckRepository, HealthCheckRepository>();

这是它的看法。

我相信这是所有相关信息 - 我一直在 phone 获得 Azure 支持,他们发现数据库配置没有问题。 .NET Core 应用程序超轻,所以它不会超载并且正在测试中,所以除了我的测试之外没有流量。

Extra: For the sake of completeness, here is my WHOLE node app which hits the db and gets the performance posted (conn data taken out).

const { Pool, Client } = require('pg');
const { performance } = require('perf_hooks');

const pool = new Pool({
  user: 'SECRET',
  host: 'SECRET',
  database: 'SECRET',
  password: 'SECRET',
  port: 5432,
})


function runQuery(pool) {
  var t0 = performance.now();
  pool.query('SELECT 1', (err, res) => {
    if (err) {
      console.log('ERROR: ', err.stack)
    } else {
      console.log('RESP: ', res.rows[0])
    }
    var t1 = performance.now();
    console.log('Elapsed ms: ', t1-t0);
    //pool.end()
});

}

setInterval(() => {runQuery(pool)}, 5000);

编辑:为了后代,这里是修复连接池超时后 .NET Core 中的时间 - 它比节点快,除了初始连接,这似乎需要一段时间,但我没有检查一些默认值:

CONN: 1710 QUERY: 18
CONN: 0 QUERY: 16
CONN: 0 QUERY: 16
CONN: 0 QUERY: 17
CONN: 0 QUERY: 16
CONN: 0 QUERY: 23
CONN: 0 QUERY: 16
CONN: 0 QUERY: 16
CONN: 0 QUERY: 23
CONN: 0 QUERY: 16
CONN: 0 QUERY: 16

第一个调用几乎比其他调用长 5 秒。对我来说,这看起来像是 IP 地址解析问题。它首先选择一个对给定服务器有缺陷的方法,然后在 5 秒后它超时并选择一个不同的方法,该方法有效。然后它会被缓存一段时间,并继续正常工作,直到缓存条目过期。

要查看这是否是问题所在,请将数据库主机的 IP 地址硬编码到您的 "hosts" 文件中,然后查看是否可以解决问题。如果是这样,那么根本原因就成了您的网络工程师的问题。

在数据库方面,您可以打开慢查询日志记录,log_min_duration_statement 或更好 auto_explain.log_min_duration。但如果我的理论是正确的,这将不会显示任何内容。数据库不知道您花了多长时间试图查找其 IP 地址。

有可能第一次查询需要将大量数据从磁盘带到内存中,随后的执行发现共享缓冲区中已经存在所有数据。你可以通过运行

知道这个
EXPLAIN (ANALYZE, BUFFERS) <your query>

'read' 和 'hit' 的数量将告诉您从磁盘读取了多少,以及在 RAM 中命中了多少。

您需要设置最小池大小。这样做可确保无论池使用情况如何,此数量的连接都对数据库保持打开状态。

默认情况下(至少NPGSQL),最小大小为0,所以如果连接一段时间不使用,它将被关闭。

在您的测试中,您每 5 秒执行一次调用,这并不多,并且池可能会决定关闭未使用的连接。根据文档,它应该保持打开状态 300 秒,而不仅仅是 15