Postgres и .Net — Объединение пулов соединений — лучшие практики

#.net #postgresql #npgsql

#.net #postgresql #npgsql

Вопрос:

У меня есть приложение .Net Core (C #), которое принимает запросы пользователей через websocket, а затем создает соединение с базой данных PostgreSQL для управления / обработки данных.

Когда пользователь отправляет новый запрос на серверную часть, вызываемая функция конечной точки создает новое SQL-соединение и выполняет запрос:

 // Endpoint available via Websocket
public async Task someRequest(someClass someArg)
{
    /* Create a new SQL connection for this user's request */
    using var conn = new NpgsqlConnection(connstr.getConnStr());
    conn.Open();

    /* Call functions and pass this SQL connection for any queries to process this user request */
    somefunction(conn, someArg);
    anotherFunction(conn, someArg);

    /* Request processing is done */
    /* conn is closed automatically by the "using" statement above */
}
 

Когда этот запрос завершен, соединение закрывается using инструкцией. Однако это соединение по умолчанию возвращается в «пул соединений» Postgres и отображается как незанятое.

Поскольку каждый новый пользовательский запрос здесь создает новое SQL-соединение, эти старые «незанятые» SQL-соединения в пуле соединений больше никогда не используются.

В настоящее время:

  1. Из-за того, что эти незанятые соединения объединяются и достигают максимального размера пула, я временно установил очень низкое время ожидания незанятого соединения. В противном случае эти незанятые соединения накапливаются и достигают искусственного потолка для открытых соединений.
  2. Я также попытался добавить Pooling=false в строку подключения. Насколько я понимаю, это остановило бы соединение на холостом ходу после закрытия.Сетевое приложение, но, похоже, оно все еще работает на холостом ходу.

Вопрос: Какова наилучшая практика для обработки пула соединений Postgres в .Net / C #?

  1. Если я смогу более правильно использовать пул соединений Postgres, было бы более эффективно повторно использовать уже открытые соединения, чем создавать новые для каждого запроса пользователя.
  2. Моя идея заключалась в том, чтобы иметь функцию, которая создает новые соединения Postgres, отслеживает их и передает их вызывающим абонентам, когда пользователь делает новый запрос. Это ужасная идея?
  3. Или мне просто отключить объединение / очень низкий тайм-аут ожидания и создать новое SQL-соединение для каждого запроса, как сейчас?

Я не смог найти много примеров правильного использования пула соединений Postgre, кроме того, что я делаю. Это приложение имеет в среднем 3000-4000 одновременных пользователей в любое время, поэтому я не могу иметь одно статическое соединение, обрабатывающее все. Какова наилучшая практика для обработки этого в .Net?

РЕДАКТИРОВАТЬ Таким образом, похоже, что объединение является родным для NPGSQL, а не для Postgres. Если новое соединение установлено с той же базой данных, пользователем, паролем, оно будет использовать одно из незанятых «объединенных» соединений вместо открытия другого.

Проблема в том, что, похоже, он этого не делал до того, как я отключил объединение. Была отправлена ошибка, из-за которой приложение отключилось на час или более за ночь.

Пул соединений исчерпан, либо увеличьте максимальный размер пула (в настоящее время 100), либо время ожидания (в настоящее время 15 секунд)

Теперь возможно, что ему действительно требовалось более 100 активных подключений одновременно… но я предполагаю, что большинство из них простаивали, как я видел раньше.

Редактировать # 2 Итак, я попробовал разрешить объединение пулов (по умолчанию), и, похоже, он мгновенно запускает незанятые соединения, поскольку они создаются запросами, но не использует эти соединения повторно. Как только он достигает максимального предела пула, приложение блокируется из-за невозможности создания новых подключений / запросов.

DBeaver Img — сеансы сервера: Красным здесь обозначены активные соединения, синим — бездействие.

введите описание изображения здесь

Каждое отдельное SQL-соединение в приложении создается из одной / общей переменной среды строки подключения. Host=IP;Port=somePort;Username=someUser;Password=somePass;Database=someDb;Maximum Pool Size=100 Единственный способ, которым я могу поддерживать работу приложения, — это установить значение idle_in_transaction_session_timeout '10s' для частой очистки незанятых подключений, поскольку объединение пулов, похоже, не работает.

Когда у меня есть postgres, очищающий незанятые соединения, idle_in_transaction_session_timeout и Pooling=false вот как выглядит моя активность в БД:

введите описание изображения здесь

Я также запустил поиск в своем коде, каждый экземпляр создания нового SQL-соединения using также содержит инструкцию. Как показано в примере кода выше. Это должно предотвратить любую утечку соединения.

введите описание изображения здесь

Есть ли какой-то элемент конфигурации postgres, который может вызвать эту проблему? Строка подключения каждый раз одна и та же, и каждое соединение содержит using инструкцию C #. Я не уверен, почему NPGSQL не использует повторно эти незанятые соединения, когда пул включен.

Я тестировал рассылку спама по новым соединениям в цикле на моем сервере разработки, и объединение пулов, похоже, работает просто отлично. Итак, я могу сказать, что формат using statement, который у меня есть, не вызывает проблем. Но если я сейчас включу объединение пулов на своем рабочем сервере, незанятые соединения мгновенно разлетятся вверх, как показано, и достигнут предела, не допускающего новых подключений. Показатели для рабочего сервера показывают ~ 1000 транзакций в секунду и ~ 4-5 активных сеансов / подключений SQL в секунду. Возможно ли, что мне просто действительно нужно увеличить максимальный лимит пула?

Комментарии:

1. Убедитесь, что строка подключения одинакова каждый раз, когда вы открываете соединение; если это так, пул Npgsql должен работать нормально и получать существующее незанятое соединение из пула. Если вы все еще сталкиваетесь с проблемами, вы можете попробовать увеличить maxPoolSize, чтобы посмотреть, поможет ли это (в случае, если ваши фактические потребности выше). Если это все еще не работает, убедитесь, что вы фактически закрываете соединение везде (например, с помощью using) — если вы этого не сделаете, это приведет к утечке, которая объяснила бы это.

2. @ShayRojansky Я ценю ответ. Для меня, похоже, объединение пулов не работает. Все соединения SQL создаются с использованием одной переменной среды, которая не изменяется. Если я включу объединение в пул в этой строке подключения, число незанятых подключений увеличится до 200 (максимум), и новые подключения не будут разрешены.

3. Я создал команду API для изменения строки подключения на лету, чтобы протестировать обмен с объединением и без объединения. Host=IP;Port=5432;Username=someUser;Password=somePass;Database=someDb;Maximum Pool Size=200 Я снова отредактирую основной пост, чтобы показать некоторые подробности.

4. Я тестировал рассылку спама по новым соединениям в цикле на моем сервере разработки, и объединение пулов, похоже, работает просто отлично. Итак, я могу сказать, что формат using statement, который у меня есть, не вызывает проблем. Но если я сейчас включу объединение пулов на своем рабочем сервере, незанятые соединения мгновенно разлетятся вверх, как показано, и достигнут предела, не допускающего новых подключений. Показатели для рабочего сервера показывают ~ 1000 транзакций в секунду и ~ 4-5 активных сеансов / подключений SQL в секунду. Возможно ли, что мне просто действительно нужно увеличить максимальный лимит пула? Я попытался установить значение 200, но это ограничение было достигнуто в течение нескольких секунд.

5. Если вам требуется более 200 подключений в данный момент , тогда да, вам нужно увеличить максимальный размер пула, но это большая нагрузка (или это может означать, что ваши запросы выполняются очень медленно). Тем не менее, я бы дважды проверил, что строка подключения всегда постоянна и т.д… Вы также можете получить более полное представление о том, что происходит, изучив журналы Npgsql .

Ответ №1:

В конечном итоге проблема, по-видимому, заключалась в самой конфигурации базы данных Postgres. Мы получали много pq: could not resize shared memory segment. No space left on device ошибок.

База данных выполняется в контейнере Docker. Я увеличил shm_size емкость контейнера 6gb , чтобы использовать больше ресурсов, на которых он работал.

Оттуда, в postgresql.conf файле для самой базы данных, казалось, было много путаницы относительно того, что устанавливать shared_buffers и max_connections .

Для shared_buffers этого было установлено значение 1/4 из shm_size документов postgres: 1500M . Раньше это значение было установлено на 128M , что было слишком мало для размера памяти контейнера.

max_connections Для этого было возвращено значение по умолчанию. 100 Даже при одновременном использовании ~ 3000-5000 пользователей серверная часть в любой момент времени использовала только 5-10 активных подключений.

Надеюсь, эти элементы конфигурации могут помочь кому-то еще в будущем.