Транзакции, выполняемые в Spring с использованием внешнего Tomcat, в два раза быстрее, чем встроенный в Spring Tomcat

Простое приложение Spring, которое использует стандартный коннектор Hikari и Eclipselink в качестве поставщика JPA, требует двойного времени (приблизительно), если выполняется с использованием встроенного Tomcat. Я также попробовал спящий режим с аналогичным результатом.

@Entity
@Table(name = "chats")
public class Chat{
    
    @Id    
    private String  id;

    public Chat() {    }

    public String getId() {
        return id;
    }
}


//Repo
public interface ChatRepository extends JpaRepository<Chat, String> {
}

Конфигурация:


@Configuration
public class EclipseLinkJpaConfigurationHikari {

    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("project");        
        em.setJpaVendorAdapter(new EclipseLinkJpaVendorAdapter());
        hace que selecciones uno de persitance.xml
        Map<String, Object> properties = new HashMap<>();
        properties.put("eclipselink.weaving", "false");
        properties.put("eclipselink.logging.level", "INFO");
        properties.put("eclipselink.ddl-generation", "none");
        em.setJpaPropertyMap(properties);

        return em;
    }


    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

}

Контроллер

@RestController
@RequestMapping("/")
public class TestController {

    private final ChatRepository chatRepository;

    @PersistenceContext
    EntityManager em;

    public TestController(ChatRepository chatRepository) {
        this.chatRepository = chatRepository;
    }

    @Transactional
    @GetMapping("/test")
    public String test() {
        long t = System.currentTimeMillis();

        for( int x=1 ; x <1500;x++){
            List<Chat> chats = chatRepository.findAll();
            Chat c = chats.get(0);
        }

        long time = (System.currentTimeMillis() - t);
        return t + "ms";
    }

}

application.properties

spring.application.name=TestConnection


spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.connection.provider_class=com.zaxxer.hikari.hibernate.HikariConnectionProvider
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

spring.datasource.url=jdbc:mysql://localhost/db?useSSL=false
spring.datasource.username=xxx
spring.datasource.password=yyy
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.hikari.connection-timeout=50000
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.pool-name=ConnPool
spring.datasource.hikari.connection-test-query=select 1 from dual

spring.datasource.hikari.data-source-properties.cachePrepStmts=true
spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250
spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=2048
spring.datasource.hikari.data-source-properties.useServerPrepStmts=true
spring.datasource.hikari.data-source-properties.useLocalSessionState=true
spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true
spring.datasource.hikari.data-source-properties.cacheResultSetMetadata=true
spring.datasource.hikari.data-source-properties.cacheServerConfiguration=true
spring.datasource.hikari.data-source-properties.elideSetAutoCommits=true
spring.datasource.hikari.data-source-properties.maintainTimeStats=false

logging.level.com.zaxxer.hikari.HikariConfig=DEBUG
logging.level.com.zaxxer.hikari=TRACE

spring.datasource.type=com.zaxxer.hikari.HikariDataSource

Если я выполню этот запрос, время обработки составит:

Внешний кот -> 494 мс, 232 мс, 217 мс, 193 мс, 198 мс, 214 мс, 155 мс, 155 мс, 107 мс (стабилизирует около 100 мс при любом дальнейшем запросе)

Встроенный кот -> 363 мс, 200 мс, 189 мс, 207 мс, 201 мс, 194 мс, 193 мс, 185 мс, 192 мс (стабилизируется около 195 мс при любом дальнейшем запросе)

Что происходит? Я использую ту же конфигурацию, ту же версию Tomcat и те же библиотеки из Gradle. После первого запроса кеш начинает работать, но, похоже, он работает лучше для внешнего кота. В целом производительность любого запроса, обращающегося к базе данных, в два раза выше при использовании внешнего Tomcat.

Примечание. Если я удалю @Transactional из метода контроллера, время обработки увеличится еще больше: с 200 мс до 280 мс (встроенный) и со 100 мс до 150 мс (внешний кот).

Вы используете не Hikari, а Tomcat JDBC Pool. Как вы использовали встроенный Tomcat? Это приложение Spring Boot или нет?

M. Deinum 10.06.2024 13:19

Это загрузочное приложение Spring. Я использую встроенный Tomcat, включая: реализацию «org.springframework.boot:spring-boot-starter-web». Почему вы говорите, что я использую пул Tomcat JDBC?

vrivon 10.06.2024 13:59

Потому что это то, что ваш метод dataSource возвращает и использует, и вы не используете предварительно настроенный Spring пул Hikari JDBC, указав свойства spring.datasource (даже если они там будут, они бесполезны, поскольку они игнорируются, когда вы указываете источник данных, а затем автоматическая настройка отменяется).

M. Deinum 10.06.2024 14:11

Извините, вы правы, я обновил свой вопрос, указав «правильный?» конфигурация. В любом случае я получаю один и тот же результат: запуск одного и того же метода несколько раз при запуске внешнего tomcat должен активировать какой-то кеш, который сокращает время выполнения, как я уже упоминал в своем вопросе.

vrivon 10.06.2024 14:22

Правильная конфигурация будет содержать только LocalContainerEntityManagerFactoryBean, конфигурация базы данных будет находиться в application.properties с использованием свойств spring.datasource. Это позволит Spring автоматически настроить источник данных и менеджер транзакций JPA. Я также не вижу смысла вводить EntityManager в контроллер. Мне также интересно, как вы используете оба решения. Вы повторно используете один и тот же артефакт? Запуск из IDE или как вы это делаете для встроенной части tomcat.

M. Deinum 10.06.2024 14:35

Я обновил свой вопрос и изменил конфигурацию, используя application.properties. Я уже пробовал это (я пробовал все :(). Чтобы запустить приложение с внешним Tomcat, я не использую тот же артефакт, потому что не знал, что вы можете это сделать, я использую intellij, я создал новый артефакт (веб-приложение взорвалось) , добавил все те же библиотеки и выполнил на внешнем tomcat той же версии.

vrivon 10.06.2024 14:56

Итак, вы используете не один и тот же артефакт, а, по сути, сравниваете яблоки и апельсины. Не используйте spring.datasource.hikari.connection-test-query, он автоматически будет использовать проверку соединения JDBC4. Вам следует использовать тот же артефакт, как это сделать, описано здесь. Теперь вы получите WAR-файл, который можно запустить java -jar your.war, чтобы использовать внутренний кот или развернуть его на внешнем. Вы должны хотя бы сравнивать один и тот же артификат.

M. Deinum 10.06.2024 15:19

Кроме того, вам также не следует устанавливать spring.jpa.hibernate.connection.provider_class и что вы хотите использовать для MySQL, поскольку вы указываете два разных диалекта.

M. Deinum 10.06.2024 15:22

Ты был прав. После некоторого пота мне удалось создать войну и выпустить ее в контейнер tomcat и как отдельное приложение. В обоих случаях производительность была одинаковой и такой же хорошей, как и при запуске внешнего Tomcat из Intellij. Следующий вопрос: что заставляет приложение Spring Boot в Intellij работать медленнее?

vrivon 10.06.2024 22:35

@M.Deinum Я выяснил причину ухудшения производительности, задал себе вопрос. Спасибо за вашу помощь.

vrivon 16.06.2024 19:39
0
10
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Есть несколько вещей, которые приходят на ум, если предположить, что вы работаете на одном компьютере со своими тестами.

Во-первых, используете ли вы ту же JVM с внешним Tomcat, что и со встроенным? Иногда легко отслеживать, какая на самом деле работает JVM, при работе с Spring uber jar.

Во-вторых, используете ли вы одни и те же настройки памяти и процессора? Если вы их не указали, встроенная версия может иметь совершенно другие значения по умолчанию, чем новая установка Tomcat, которая имеет другие значения по умолчанию.

В-третьих, сравните настройки JVM, такие как сборщик мусора или количество активных процессоров. Различные JVM имеют разные конфигурации, которые могут незначительно повлиять на ситуацию, поэтому вам следует дважды проверить, работает ли на вашем Tomcat JVM с некоторыми из этих дополнительных -XX настроек.

В-четвертых, проверьте свойства сервера Spring Boot , поскольку некоторые из них могут иметь значения по умолчанию, отличные от настроек экземпляра Tomcat.

Не должно быть никакой причины, по которой они работают по-разному, поскольку они реализуют один и тот же тип кода, поэтому я готов поспорить, что это связано с JVM.

1. Это тот же компьютер. 2. Это та же самая JVM: 21. Я настроил ее в build.gradle, также дважды проверил, получив значение System.getProperty("java.version"). 3. Параметры JVM одинаковы для обоих: -Xms1g -Xmx2g -XX:+UseG1GC 4. Что касается свойств сервера: я пробовал разные конфигурации, но ничего не нашел. Что меня действительно озадачивает, так это тот факт, что приложение, запущенное во внешнем Tomcat, уменьшает время, необходимое для вычисления метода, составляет от 500 до 100 мс после нескольких запросов.

vrivon 10.06.2024 09:34
Ответ принят как подходящий

После нескольких дней расследования я выяснил причину, по которой приложение имело разную производительность при использовании внешнего Tomcat или конфигурации, предоставленной Intellij Idea.

После включения опции «Отключить оптимизацию запуска» (-XX:TieredStopAtLevel=1) производительность такая же, как у внешнего Tomcat.

Другие вопросы по теме