Узел в 20 раз быстрее, чем NET Core при подключении к Postgres - PullRequest
6 голосов
/ 05 января 2020

У меня есть два сервера, подключающихся к PostgresSQL 9.6 дБ, размещенной на Azure. Серверы делают одну вещь - нажимают Postgres db с запросом SELECT 1 каждые 5 секунд.

Типичное время для подключения к базе данных и получения данных:

  • Узел: 25 MS
  • . NET Core 3.1 с использованием N psql 4.1.1 (я тоже пробовал 4.1.2, без различий): 500 MS

My проблема заключается в том, что мое. NET ядро ​​приложения в 20 раз медленнее , чем узел при получении данных. Я верю . NET Ядро по какой-то причине не объединяет соединения. Эта медлительность возникает как при локальном запуске приложения, так и при его запуске в Azure Службах приложений - без разницы. Я хочу решить. NET -> Postgres медлительность.

Пожалуйста, просмотрите только соответствующие детали и не читайте все это дальше - я верю только код .NET Core имеет отношение.

A PsPing к БД с моего компьютера (на котором работают приложения Node и .NET Core:

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 раз выглядит следующим образом (обратите внимание, что при первом установлении соединения он также «медленный»):

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 look как это:

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 

Обратите внимание на очень медленное начальное время соединения и длительное время для установки sh соединения на последующих запросах.

В любом случае, потому что я в отчаянии, я Я собираюсь сбросить весь мой код с пояснениями. Строка подключения выглядит следующим образом:

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 раньше, без разницы.

Наконец, в случае проблем с внедрением зависимостей, хранилище находится в библиотеке классов, ссылки на проект API это, и:

services.AddSingleton<IHealthCheckRepository, HealthCheckRepository>();

Вот как он это видит.

Я считаю, что это вся соответствующая информация - я звонил по телефону с Azure поддержка, и они не нашли никаких проблем с конфигурацией дБ. . NET Базовое приложение очень легкое, поэтому оно не перегружено и не тестируется, поэтому никаких тестов c кроме моих тестов.

Дополнительно: для полноты здесь мое приложение для всего узла, которое попадает в базу данных и публикует результаты (данные сняты).

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 Ядро после исправления тайм-аута пула соединений - оно быстрее, чем узел, за исключением того начального соединения, которое, кажется, занимает некоторое время, но я не проверил некоторые значения по умолчанию:

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

Ответы [ 3 ]

5 голосов
/ 23 января 2020

Вам необходимо установить минимальный размер пула. Это гарантирует, что это количество соединений остается открытым для БД независимо от использования пула.

По умолчанию (по крайней мере, для NPG SQL) минимальный размер равен 0, поэтому, если соединение некоторое время не используется, оно будет закрыто.

В вашем тесте вы делаете один вызов каждые 5 секунд, что немного, и пул может решить закрыть неиспользуемое соединение. Согласно do c он должен оставаться открытым в течение 300 секунд, а не только 15

3 голосов
/ 05 января 2020

Первый звонок почти на 5 секунд длиннее остальных. Это похоже на проблему разрешения IP-адреса для меня. Сначала он выбирает метод, который неисправен для данного сервера, затем через 5 секунд он отключается и выбирает другой метод, который работает. Затем он некоторое время кэшируется и продолжает работать хорошо, пока не истечет срок действия кэшированной записи.

Чтобы проверить, не является ли это проблемой, жестко закодируйте IP-адрес хоста базы данных в файл «hosts» и Посмотрим, решит ли это проблему. Если это так, то причина root становится вопросом для сетевых инженеров.

На стороне базы данных вы можете включить медленное ведение журнала запросов, либо log_min_duration_statement или еще лучше auto_explain.log_min_duration. Но если моя теория верна, это ничего не покажет. База данных не знает, сколько времени вы потратили на поиск своего IP-адреса.

2 голосов
/ 06 января 2020

Вполне возможно, что при первом запросе необходимо перенести много данных с диска в память, а при последующих выполнениях все уже будет в общих буферах. Вы можете узнать это, запустив

EXPLAIN (ANALYZE, BUFFERS) <your query>

Количество «read» и «hit» скажет вам, сколько было прочитано с диска, и сколько было обращено в RAM.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...