Задать вопрос
@spirit1984

Неправильное поведение пула соединений в приложении Spring Boot — почему соединения не возвращаются?

У меня есть приложение, написанное на spring boot (версия 3.4.4), в которой выявилась медленная работа одного метода, который делает следующее:

public void method(Request request) {
  databaseService.storeObject(request); // A
  final var response = httpClient.post(request); // B
  databaseService.storeObject(response); // C
}


Метод storeObject в точках A и C помечен как транзакционный, с помощью аннотации Transactional. В точке B задержка ответа около 100 мс.

При нагрузочном тестировании я заметил, что метод выполняется несоразмерно долго, т.е. среднее время ответа не 100 мс. Профилирование показало следующее:

  • В точке А вызов метода может занимать несколько секунд
  • Внутренности метода А выполняются около 1 мс.
  • Запрос к внешней точке B всегда около 100 мс.
  • В точке С вызов метода и внутренности его всегда укладываются в 1 мс


Судя по поведению, которое мы наблюдает, после выполнения точки А соединение не возвращается в пул соединений. Оно остается до окончания выполнения всего метода (который вызывается контроллером). Поэтому в точке С вызов всегда очень быстрый, ведь соединение уже есть. А вот другие запросы не могут войти в точку А, ведь свободных соединений нет, пока у кого-то другого не отработают точки В и С.

Я не могу понять, чем вызвано такое поведение. Под капотом неявно крутится hibernate - мы не используем его явно, только jpa репозитории. Вот зависимость дерева:
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:3.4.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:3.4.4:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:5.1.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:6.2.5:compile
[INFO] |  +- org.hibernate.orm:hibernate-core:jar:6.6.11.Final:compile
[INFO] |  |  +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile


Так же под капотом используется пул соединений hikari. Пробовал менять его на oracle UCP -не помогло особо, такое же поведение. Такое ощущение, что либо hibernate, либо jakarta transaction manager не дают правильно вернуть соединение после первой вставки в базу. Кто-нибудь сталкивался с этим, что с этим можно сделать?
  • Вопрос задан
  • 66 просмотров
Подписаться 1 Средний 4 комментария
Решения вопроса 1
@spirit1984 Автор вопроса
я разрешил эту проблему. Все дело было в настройки spring.jpa.open-in-view, которая по умолчанию в true и которая УДЕРЖИВАЕТ соединение после первого запроса в точке А. Выставление ее в false приводит к тому, что все летает.

Подробнее здесь - https://www.baeldung.com/spring-open-session-in-vi...

В целом, данная настройка кардинально противоречит духу виртуальных потоков (да и приложению без виртуальных потоков дольше) и фактически привязывает количество одновременно обрабатываемых запросов к количеству соединений в пуле.

Представим, что у меня в пуле 50 соединений, и каждый запрос пользователя подразумевает вставку в базу за 1 мс и затем обращение к другому сайту по http, длящееся одну секунду. Тогда, по идее, за 1 секунду я должен уметь обслуживать 1000 пользователей (если вставки в таблицу лочат ее). Да, каждый пользователь будет ждать итоговый ответ 1 секунду из-за другого сайта, но получат его одновременно.

Однако с таким вот подходом spring jpa все иначе, я за секунду обслужу ровно 50 человек, столько, сколько соединений в пуле, потому что соединения останутся висеть у сессий jpa, пока я не закончу с http обращениями. Даром что транзакцию я закоммитил и вроде как даже закрыл соединение сразу после вставки.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы